当前位置: 首页 > news >正文

Go-Python-Java-C-LeetCode高分解法-第五周合集

前言

本题解Go语言部分基于 LeetCode-Go
其他部分基于本人实践学习
个人题解GitHub连接:LeetCode-Go-Python-Java-C
Go-Python-Java-C-LeetCode高分解法-第一周合集
Go-Python-Java-C-LeetCode高分解法-第二周合集
Go-Python-Java-C-LeetCode高分解法-第三周合集
Go-Python-Java-C-LeetCode高分解法-第四周合集
本文部分内容来自网上搜集与个人实践。如果任何信息存在错误,欢迎读者批评指正。本文仅用于学习交流,不用作任何商业用途。

29. Divide Two Integers

题目

Given two integersdividendanddivisor, divide two integers without using multiplication, division and mod operator.

Return the quotient after dividingdividendbydivisor.

The integer division should truncate toward zero.

Example 1:

Input: dividend = 10, divisor = 3
Output: 3

Example 2:

Input: dividend = 7, divisor = -3
Output: -2

Note:

  • Both dividend and divisor will be 32-bit signed integers.
  • The divisor will never be 0.
  • Assume we are dealing with an environment which could only store integers within the 32-bit signed integer
    range: [−2^31, 2^31 − 1]. For the purpose of this problem, assume that your function returns 2^31 − 1 when the
    division result overflows.

题目大意

给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。返回被除数 dividend 除以除数
divisor 得到的商。

说明:

  • 被除数和除数均为 32 位有符号整数。
  • 除数不为 0。
  • 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−2^31,  2^31 − 1]。本题中,如果除法结果溢出,则返回 2^31 − 1。

解题思路

  • 给出除数和被除数,要求计算除法运算以后的商。注意值的取值范围在 [−2^31, 2^31 − 1] 之中。超过范围的都按边界计算。
  • 这一题可以用二分搜索来做。要求除法运算之后的商,把商作为要搜索的目标。商的取值范围是 [0, dividend],所以从 0
    到被除数之间搜索。利用二分,找到(商 + 1 ) * 除数 > 被除数并且 商 * 除数 ≤ 被除数 或者 (商+1)* 除数 ≥ 被除数并且商 *
    除数 < 被除数的时候,就算找到了商,其余情况继续二分即可。最后还要注意符号和题目规定的 Int32 取值范围。
  • 二分的写法常写错的 3 点:
    1. low ≤ high (注意二分循环退出的条件是小于等于)
    2. mid = low + (high-low)>>1 (防止溢出)
    3. low = mid + 1 ; high = mid - 1 (注意更新 low 和 high 的值,如果更新不对就会死循环)
      以下是每个版本的解题思路的详细介绍:

Go 版本

Go 版本的解题思路如下:

  1. 首先,处理特殊情况,包括被除数(dividend)等于 0,除数(divisor)等于 1,以及避免溢出的情况(例如,被除数为math.MinInt32,除数为-1)。

  2. 确定最终结果的符号(正数或负数),并将被除数和除数都转换为正数,以便进行位运算。

  3. 使用二分搜索来找到商。二分搜索的目标是找到一个整数 x,使得 (x + 1) * divisor > dividendx * divisor ≤ dividend,或者 (x + 1) * divisor ≥ dividendx * divisor < dividend。这个 x 就是我们要找的商。

  4. 在二分搜索的过程中,确保更新搜索的范围和值,同时处理可能的溢出情况。

  5. 最后,根据符号和边界情况返回最终的商。

Python 版本

Python 版本的解题思路如下:

  1. 处理特殊情况,包括被除数等于 0,除数等于 1,以及避免溢出的情况(例如,被除数为-2^31,除数为-1)。

  2. 确定最终结果的符号(正数或负数),并将被除数和除数都转换为正数,以便进行位运算。

  3. 使用递归的二分搜索方法来找到商。递归函数的目标是找到一个整数 x,使得 (x + 1) * divisor > dividendx * divisor ≤ dividend,或者 (x + 1) * divisor ≥ dividendx * divisor < dividend。这个 x 就是我们要找的商。

  4. 在递归的过程中,确保更新搜索的范围和值,同时处理可能的溢出情况。

  5. 最后,根据符号和边界情况返回最终的商。

Java 版本

Java 版本的解题思路如下:

  1. 处理特殊情况,包括被除数等于 0,除数等于 1,以及避免溢出的情况(例如,被除数为Integer.MIN_VALUE,除数为-1)。

  2. 确定最终结果的符号(正数或负数),并将被除数和除数都转换为正数,以便进行位运算。

  3. 使用递归的二分搜索方法来找到商。递归函数的目标是找到一个整数 x,使得 (x + 1) * divisor > dividendx * divisor ≤ dividend,或者 (x + 1) * divisor ≥ dividendx * divisor < dividend。这个 x 就是我们要找的商。

  4. 在递归的过程中,确保更新搜索的范围和值,同时处理可能的溢出情况。

  5. 最后,根据符号和边界情况返回最终的商。

C++ 版本

C++ 版本的解题思路如下:

  1. 处理特殊情况,包括被除数等于 0,除数等于 1,以及避免溢出的情况(例如,被除数为INT_MIN,除数为-1)。

  2. 确定最终结果的符号(正数或负数),并将被除数和除数都转换为正数,以便进行位运算。

  3. 使用递归的二分搜索方法来找到商。递归函数的目标是找到一个整数 x,使得 (x + 1) * divisor > dividendx * divisor ≤ dividend,或者 (x + 1) * divisor ≥ dividendx * divisor < dividend。这个 x 就是我们要找的商。

  4. 在递归的过程中,确保更新搜索的范围和值,同时处理可能的溢出情况。

  5. 最后,根据符号和边界情况返回最终的商。

总之,这些解决方案的核心思想都是使用二分搜索来找到商,同时处理特殊情况和溢出情况,以确保最终的计算结果正确。递归函数在实现中被广泛用于搜索过程。

代码

Go

func abs(x int) int {if x < 0 {return -x}return x
}// 解法一 递归版的二分搜索
func divide(dividend int, divisor int) int {sign, res := -1, 0// low, high := 0, abs(dividend)if dividend == 0 {return 0}if divisor == 1 {return dividend}if dividend == math.MinInt32 && divisor == -1 {return math.MaxInt32}if dividend > 0 && divisor > 0 || dividend < 0 && divisor < 0 {sign = 1}if dividend > math.MaxInt32 {dividend = math.MaxInt32}// 调用二分搜索函数计算商res = binarySearchQuotient(0, abs(dividend), abs(divisor), abs(dividend))// 处理溢出情况if res > math.MaxInt32 {return sign * math.MaxInt32}if res < math.MinInt32 {return sign * math.MinInt32}return sign * res
}// 二分搜索函数,用于计算商
func binarySearchQuotient(low, high, val, dividend int) int {quotient := low + (high-low)>>1if ((quotient+1)*val > dividend && quotient*val <= dividend) || ((quotient+1)*val >= dividend && quotient*val < dividend) {if (quotient+1)*val == dividend {return quotient + 1}return quotient}if (quotient+1)*val > dividend && quotient*val > dividend {return binarySearchQuotient(low, quotient-1, val, dividend)}if (quotient+1)*val < dividend && quotient*val < dividend {return binarySearchQuotient(quotient+1, high, val, dividend)}return 0
}func abs(x int) int {if x < 0 {return -x}return x
}// 解法二 非递归版的二分搜索
func divide(dividend int, divisor int) int {if dividend == math.MinInt32 && divisor == -1 {return math.MaxInt32}result := 0sign := -1if dividend > 0 && divisor > 0 || dividend < 0 && divisor < 0 {sign = 1}dividendAbs, divisorAbs := abs(dividend), abs(divisor)for dividendAbs >= divisorAbs {temp := divisorAbsmultiplier := 1for temp<<1 <= dividendAbs {temp <<= 1multiplier <<= 1}dividendAbs -= tempresult += multiplier}return sign * result
}

Python

class Solution:def divide(self, dividend: int, divisor: int) -> int:# 递归版def recursive_divide(dividend, divisor):if dividend < divisor:return 0quotient = 1div = divisorwhile (div + div) <= dividend:div += divquotient += quotientreturn quotient + recursive_divide(dividend - div, divisor)if dividend == 0:return 0if dividend == -2 ** 31 and divisor == -1:return 2 ** 31 - 1sign = 1 if (dividend > 0) == (divisor > 0) else -1dividend, divisor = abs(dividend), abs(divisor)result = recursive_divide(dividend, divisor)return sign * resultclass Solution:# 非递归版def divide(self, dividend: int, divisor: int) -> int:if dividend == 0:return 0if dividend == -2 ** 31 and divisor == -1:return 2 ** 31 - 1sign = 1 if (dividend > 0) == (divisor > 0) else -1dividend, divisor = abs(dividend), abs(divisor)result = 0while dividend >= divisor:temp, m = divisor, 1while dividend >= (temp << 1):temp <<= 1m <<= 1dividend -= tempresult += mreturn sign * result

Java

class Solution {public int divide(int dividend, int divisor) {// 递归版if (dividend == 0) {return 0;}if (dividend == Integer.MIN_VALUE && divisor == -1) {return Integer.MAX_VALUE;}int sign = (dividend > 0) == (divisor > 0) ? 1 : -1;long dvd = Math.abs((long) dividend);long dvs = Math.abs((long) divisor);int result = recursiveDivide(dvd, dvs);return sign * result;}private int recursiveDivide(long dividend, long divisor) {if (dividend < divisor) {return 0;}int quotient = 1;long div = divisor;while ((div + div) <= dividend) {div += div;quotient += quotient;}return quotient + recursiveDivide(dividend - div, divisor);}}
class Solution {// 非递归版public int divide(int dividend, int divisor) {if (dividend == 0) {return 0;}if (dividend == Integer.MIN_VALUE && divisor == -1) {return Integer.MAX_VALUE;}int sign = (dividend > 0) == (divisor > 0) ? 1 : -1;long dvd = Math.abs((long) dividend);long dvs = Math.abs((long) divisor);int result = 0;while (dvd >= dvs) {long temp = dvs;int m = 1;while (dvd >= (temp << 1)) {temp <<= 1;m <<= 1;}dvd -= temp;result += m;}return sign * result;}
}

Cpp

class Solution {
public:int divide(int dividend, int divisor) {// 处理特殊情况if (dividend == 0) {return 0;}if (divisor == 1) {return dividend;}if (dividend == INT_MIN && divisor == -1) {return INT_MAX;}int sign = (dividend > 0 && divisor > 0) || (dividend < 0 && divisor < 0) ? 1 : -1;// 处理溢出情况if (dividend > INT_MAX) {dividend = INT_MAX;}return sign * binarySearchQuotient(0, abs((long)dividend), abs((long)divisor), abs((long)dividend));}private:int binarySearchQuotient(long low, long high, long val, long dividend) {long quotient = low + (high - low) / 2;if (((quotient + 1) * val > dividend && quotient * val <= dividend) || ((quotient + 1) * val >= dividend && quotient * val < dividend)) {if ((quotient + 1) * val == dividend) {return quotient + 1;}return quotient;}if ((quotient + 1) * val > dividend && quotient * val > dividend) {return binarySearchQuotient(low, quotient - 1, val, dividend);}if ((quotient + 1) * val < dividend && quotient * val < dividend) {return binarySearchQuotient(quotient + 1, high, val, dividend);}return 0;}
};class Solution {
public:int divide(int dividend, int divisor) {// 处理特殊情况if (dividend == INT_MIN && divisor == -1) {return INT_MAX;}int result = 0;int sign = (dividend > 0 && divisor > 0) || (dividend < 0 && divisor < 0) ? 1 : -1;long dvd = abs((long)dividend);long dvs = abs((long)divisor);while (dvd >= dvs) {long temp = dvs;long m = 1;while (temp << 1 <= dvd) {temp <<= 1;m <<= 1;}dvd -= temp;// 处理溢出情况if (result > INT_MAX - m) {return sign == 1 ? INT_MAX : INT_MIN;}result += m;}return sign * result;}
};

当讨论每个版本的解决方案时,我将分开介绍所需的详细基础知识。

Go 版本

  1. Go 语言基础: 在理解 Go 版本的解决方案之前,您需要熟悉 Go 语言的基础知识,包括变量、函数、条件语句、循环等。

  2. 递归: Go 版本的解决方案中使用了递归来实现二分搜索,因此需要了解递归的概念和用法。

  3. 位运算: Go 版本中使用位运算来处理细节,如符号、边界情况等。因此,您需要了解 Go 中的位运算操作符(如<<>>)和位运算的基本原理。

  4. 边界情况处理: 了解如何处理边界情况是解决这个问题的关键之一,因为输入和输出受到了限制。需要考虑最小和最大的 32 位有符号整数。

Python 版本

  1. Python 语言基础: 您需要熟悉 Python 语言的基础知识,包括变量、函数、条件语句、循环以及整数溢出处理。

  2. 递归: Python 版本中的递归解决方案使用了递归函数来实现二分搜索。您需要了解如何编写递归函数以及递归的工作原理。

  3. 位运算: 位运算在 Python 版本中也被用于处理符号和边界情况。了解 Python 中的位运算操作符(如<<>>)和位运算的基本原理将有助于理解解决方案。

Java 版本

  1. Java 语言基础: 在理解 Java 版本的解决方案之前,您需要熟悉 Java 语言的基础知识,包括类、方法、条件语句、循环以及整数溢出处理。

  2. 递归: Java 版本的解决方案使用了递归函数来实现二分搜索。您需要了解如何编写递归函数以及递归的工作原理。

  3. 位运算: 位运算在 Java 版本中用于处理符号和边界情况。了解 Java 中的位运算操作符(如<<>>)和位运算的基本原理将有助于理解解决方案。

  4. 整数溢出处理: Java 版本考虑了整数溢出情况,并采取了措施来防止溢出。您需要了解 Java 中整数溢出的特点以及如何处理它。

C++ 版本

  1. C++ 语言基础: 在理解 C++ 版本的解决方案之前,您需要熟悉 C++ 语言的基础知识,包括类、函数、条件语句、循环以及整数溢出处理。

  2. 递归: C++ 版本的解决方案使用了递归函数来实现二分搜索。您需要了解如何编写递归函数以及递归的工作原理。

  3. 位运算: 位运算在 C++ 版本中用于处理符号和边界情况。了解 C++ 中的位运算操作符(如<<>>)和位运算的基本原理将有助于理解解决方案。

  4. 整数溢出处理: C++ 版本考虑了整数溢出情况,并采取了措施来防止溢出。您需要了解 C++ 中整数溢出的特点以及如何处理它。

总之,理解每个版本的解决方案需要对编程语言的基础知识、递归、位运算和整数溢出处理有一定的了解。此外,了解问题的特殊要求和边界情况也是解决这个问题的关键。

30. Substring with Concatenation of All Words

题目

You are given a string, s, and a list of words, words, that are all of the same length. Find all starting indices of
substring(s) in s that is a concatenation of each word in words exactly once and without any intervening characters.

Example 1:

Input:s = "barfoothefoobarman",words = ["foo","bar"]
Output: [0,9]
Explanation: Substrings starting at index 0 and 9 are "barfoor" and "foobar" respectively.
The output order does not matter, returning [9,0] is fine too.

Example 2:

Input:s = "wordgoodgoodgoodbestword",words = ["word","good","best","word"]
Output: []

题目大意

给定一个源字符串 s,再给一个字符串数组,要求在源字符串中找到由字符串数组各种组合组成的连续串的起始下标,如果存在多个,在结果中都需要输出。

解题思路

这一题看似很难,但是有 2 个限定条件也导致这题不是特别难。1. 字符串数组里面的字符串长度都是一样的。2.
要求字符串数组中的字符串都要连续连在一起的,前后顺序可以是任意排列组合。

解题思路,先将字符串数组里面的所有字符串都存到 map
中,并累计出现的次数。然后从源字符串从头开始扫,每次判断字符串数组里面的字符串时候全部都用完了(计数是否为 0)
,如果全部都用完了,并且长度正好是字符串数组任意排列组合的总长度,就记录下这个组合的起始下标。如果不符合,就继续考察源字符串的下一个字符,直到扫完整个源字符串。
下面我将分别介绍每个版本的解题思路:

Go 版本

  1. 初始化变量

    • 获取输入字符串 s 的长度 ls,单词数组 words 的长度 m,以及单词的长度 n
    • 初始化一个空的整数切片 ans 用于存储结果。
  2. 主循环

    • 从字符串 s 的前 n 个字符开始,循环遍历起始位置。
    • 创建一个映射 differ 用于跟踪当前窗口内每个单词的出现次数。
  3. 遍历单词列表 words

    • 对于单词列表中的每个单词,在 differ 中增加其出现次数。
    • 如果出现次数增加后变为 0,从 differ 中删除该单词。
  4. 主循环

    • 从当前起始位置开始,每次移动一个单词的长度 n,检查子串是否包含所有单词。
    • 如果不包含,继续移动窗口。
    • 如果 differ 中不包含任何单词,说明子串包含了所有单词,将当前起始位置添加到结果中。
  5. 返回结果:返回存储结果的切片 ans

Python 版本

  1. 初始化变量

    • 初始化一个空列表 ans 用于存储结果。
    • 计算单词的长度 word_len,单词数组的长度 total_words,单词数组的元素计数 word_count,以及输入字符串 s 的长度 s_len
  2. 循环遍历单词的起始位置

    • 外层循环迭代单词的起始位置,从 0 到 word_len-1
    • 内部维护两个指针 leftj,以及一个计数器 count 和一个单词计数器 current_count
  3. 遍历字符串 s

    • 内部循环遍历字符串 s 从当前起始位置开始,每次移动一个单词的长度 word_len
    • 在内部循环中,检查当前子串是否是单词数组的一个有效组合。
    • 如果是有效组合,将起始位置 left 添加到结果列表中。
  4. 返回结果:返回存储结果的列表 ans

Java 版本

  1. 初始化变量

    • 初始化一个空列表 res 用于存储结果。
    • 创建一个映射 map 用于存储单词以及它们的出现次数。
    • 获取单词数组的长度 m
  2. 循环遍历可能的单词起始位置

    • 外层循环迭代单词的起始位置,从 0 到 len(words[0])-1
  3. 遍历字符串 s

    • 内部循环遍历字符串 s,从当前起始位置开始,每次移动一个单词的长度。
    • 在内部循环中,检查当前子串是否是单词数组的一个有效组合。
    • 如果是有效组合,将起始位置添加到结果列表 res 中。
  4. 返回结果:返回存储结果的列表 res

C++ 版本

  1. 初始化变量

    • 初始化一个空向量 ans 用于存储结果。
    • 获取单词数组的长度 m 和单词的长度 wordsize
    • 创建一个映射 mp 用于存储单词以及它们的出现次数。
    • 获取输入字符串 s 的长度 n
  2. 循环遍历可能的单词起始位置

    • 外层循环迭代单词的起始位置,从 0 到 wordsize-1
  3. 遍历字符串 s

    • 内部循环遍历字符串 s,从当前起始位置开始,每次移动一个单词的长度。
    • 在内部循环中,检查当前子串是否是单词数组的一个有效组合。
    • 如果是有效组合,将起始位置添加到结果向量 ans 中。
  4. 返回结果:返回存储结果的向量 ans

这些是每个版本的基本解题思路。它们都采用了滑动窗口技巧,从不同的起始位置开始,逐步移动窗口并检查子串是否满足条件。同时,它们还利用了数据结构(如映射或计数器)来统计单词的出现次数以进行比较。不同的编程语言在实现细节上有所不同,但整体思路是一致的。

代码

Go

func findSubstring(s string, words []string) (ans []int) {// 获取输入字符串 `s` 的长度ls, m, n := len(s), len(words), len(words[0])// 遍历字符串 `s` 的前 n 个字符for i := 0; i < n && i+m*n <= ls; i++ {// 使用 map `differ` 来跟踪子串中每个单词的出现次数differ := map[string]int{}// 遍历单词列表 `words`for j := 0; j < m; j++ {// 将子串中的每个单词加入到 `differ` 中,并统计出现次数differ[s[i+j*n:i+(j+1)*n]]++}// 遍历单词列表 `words`for _, word := range words {// 减少 `differ` 中对应单词的出现次数differ[word]--// 如果出现次数减少到 0,从 `differ` 中删除这个单词if differ[word] == 0 {delete(differ, word)}}// 从当前位置 `i` 开始,每次移动一个单词长度 `n`,检查子串是否包含所有单词for start := i; start < ls-m*n+1; start += n {if start != i {// 更新 `differ`,增加新单词的出现次数,减少旧单词的出现次数word := s[start+(m-1)*n : start+m*n]differ[word]++if differ[word] == 0 {delete(differ, word)}word = s[start-n : start]differ[word]--if differ[word] == 0 {delete(differ, word)}}// 如果 `differ` 中不包含任何单词,说明子串包含了所有单词if len(differ) == 0 {ans = append(ans, start)}}}return
}

Python

from collections import Counterclass Solution:def findSubstring(self, s: str, words: List[str]) -> List[int]:if not s or not words:return []ans = []word_len = len(words[0])total_words = len(words)word_count = Counter(words)s_len = len(s)for i in range(word_len):left = icount = 0current_count = Counter()for j in range(i, s_len - word_len + 1, word_len):word = s[j:j + word_len]if word in word_count:current_count[word] += 1count += 1while current_count[word] > word_count[word]:left_word = s[left:left + word_len]current_count[left_word] -= 1count -= 1left += word_lenif count == total_words:ans.append(left)else:current_count.clear()count = 0left = j + word_lenreturn ans

Java

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class Solution {public List<Integer> findSubstring(String s, String[] words) {Map<String, Integer> map = new HashMap<>();// 将单词数组中的单词以及它们的出现次数存储在 map 中for (String str : words) {map.put(str, map.getOrDefault(str, 0) + 1);}int len = words[0].length(); // 单词的长度List<Integer> res = new ArrayList<>(); // 存储结果的列表// 遍历字符串 s 中每个可能的起始位置for (int i = 0; i < len; i++) {Map<String, Integer> newmap = new HashMap<>(); // 存储当前窗口内的单词出现次数int count = 0; // 记录窗口内匹配的单词数量for (int j = i; j <= s.length() - len;) {String cur = s.substring(j, j + len); // 当前窗口内的单词// 如果当前单词在单词数组中且未超出其出现次数限制if (map.containsKey(cur) && (!newmap.containsKey(cur) || newmap.get(cur) < map.get(cur))) {newmap.put(cur, newmap.getOrDefault(cur, 0) + 1); // 更新窗口内单词出现次数count++; // 增加匹配的单词数量// 如果窗口内匹配的单词数量等于单词数组的长度,表示找到一个满足条件的子串if (count == words.length) {res.add(j - len * (words.length - 1)); // 记录子串的起始位置count--;String pre = s.substring(j - len * (words.length - 1), j - len * (words.length - 2));newmap.put(pre, newmap.get(pre) - 1); // 更新窗口内单词出现次数}j += len; // 移动窗口} // 如果当前单词不在单词数组中else if (!map.containsKey(cur)) {count = 0;newmap.clear(); // 清空窗口内的单词记录j += len;} // 如果当前单词在单词数组中但超出其出现次数限制else {String pre = s.substring(j - len * count, j - len * (count - 1));newmap.put(pre, newmap.get(pre) - 1); // 更新窗口内单词出现次数count--; // 减少匹配的单词数量}}}return res; // 返回结果列表}
}

Cpp

class Solution {
public:vector<int> findSubstring(string s, vector<string>& words) {const int n = s.length();            // 输入字符串的长度const int m = words.size();          // 单词数组的大小const int wordsize = words.front().length(); // 单词的长度unordered_map<string, int> mp;       // 用于存储单词以及它们的出现次数for (auto &word : words)mp[word]++;vector<int> ans;                     // 存储结果的向量for (int i = 0; i < wordsize; ++i) { // 对于每个可能的起始位置unordered_map<string, int> cnt;  // 用于存储当前窗口内的单词出现次数int start = i;                  // 记录符合条件的字符串的起始位置for (int j = i; j < n; j += wordsize) { // 遍历字符串 s 中所有单词string word = s.substr(j, wordsize);if (!mp.count(word)) {       // 如果遇到不在单词数组中的单词,直接清空前面所有的计数cnt.clear();start = j + wordsize;} else {cnt[word] += 1;while (cnt[word] > mp[word]) { // 某个单词的计数超过了在单词数组中的出现次数,从左边减cnt[s.substr(start, wordsize)]--;start += wordsize;}if (j - start == (m - 1) * wordsize) // 如果窗口内匹配的单词数量等于单词数组的长度,表示找到一个满足条件的子串ans.push_back(start);}}}return ans;}
};

每个版本的代码中所需要掌握的基础知识。

Go 版本

  1. Go 语言基础:

    • 了解 Go 语言的基本语法,包括变量、循环、条件语句、函数等。
  2. 字符串处理:

    • 了解如何使用字符串切片和字符串拼接操作来处理字符串。
    • 理解字符串长度的计算方法。
  3. 映射(map):

    • 了解 Go 中的映射数据结构,即 map,以及如何向映射中添加、删除和检索元素。
    • 熟悉如何使用映射来实现单词计数和比较。
  4. 循环和条件语句:

    • 了解如何使用 for 循环和 if 条件语句来实现迭代和条件判断。
  5. 切片操作:

    • 了解如何使用切片来操作数组或字符串的子集。
    • 理解如何通过切片进行迭代。

Python 版本

  1. Python 语言基础:

    • 熟悉 Python 语言的基本语法,包括变量、循环、条件语句、函数等。
  2. 字符串处理:

    • 了解如何使用字符串切片和字符串拼接操作来处理字符串。
    • 知道如何获取字符串的长度。
  3. Counter 类:

    • 了解 Python 中的 collections.Counter 类,用于统计元素出现的次数。
    • 知道如何使用 Counter 对象来进行单词计数。
  4. 循环和条件语句:

    • 理解如何使用 for 循环和 if 条件语句来实现迭代和条件判断。
  5. 列表(List):

    • 熟悉 Python 中的列表数据结构,以及如何使用列表来存储和操作数据。

Java 版本

  1. Java 语言基础:

    • 熟悉 Java 语言的基本语法,包括变量、循环、条件语句、函数等。
  2. 字符串处理:

    • 了解如何使用字符串切片和字符串拼接操作来处理字符串。
    • 知道如何获取字符串的长度。
  3. Map(映射):

    • 理解 Java 中的 java.util.Map 接口,以及如何使用 HashMap 实现映射。
    • 知道如何向映射中添加、删除和检索元素。
  4. 循环和条件语句:

    • 了解如何使用 for 循环和 if 条件语句来实现迭代和条件判断。
  5. 列表(List):

    • 熟悉 Java 中的列表数据结构,即 java.util.List,以及如何使用列表来存储和操作数据。

C++ 版本

  1. C++ 语言基础:

    • 理解 C++ 语言的基本语法,包括变量、循环、条件语句、函数等。
  2. 字符串处理:

    • 了解如何使用字符串切片和字符串拼接操作来处理字符串。
    • 知道如何获取字符串的长度。
  3. STL(标准模板库):

    • 理解 C++ STL 中的 std::unordered_map,用于实现映射。
    • 知道如何向映射中添加、删除和检索元素。
  4. 循环和条件语句:

    • 了解如何使用 for 循环和 if 条件语句来实现迭代和条件判断。
  5. 向量(Vector):

    • 熟悉 C++ 中的向量数据结构,即 std::vector,以及如何使用向量来存储和操作数据。

这些是理解和编写提供的不同语言版本的代码所需的基本知识。每个版本都使用了相似的算法和数据结构,但具体的语法和库函数可能会有所不同。

31. Next Permutation

题目

Implementnext permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.

If such an arrangement is not possible, it must rearrange it as the lowest possible order (i.e., sorted in ascending
order).

The replacement must be**in place**and use only constant extra
memory.

Example 1:

Input: nums = [1,2,3]
Output: [1,3,2]

Example 2:

Input: nums = [3,2,1]
Output: [1,2,3]

Example 3:

Input: nums = [1,1,5]
Output: [1,5,1]

Example 4:

Input: nums = [1]
Output: [1]

Constraints:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 100

题目大意

实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。必须
原地 修改,只允许使用额外常数空间。

解题思路

Go 版本解题思路:

  1. 寻找下一个排列:首先,我们要找到下一个排列,即重新排列给定数字序列,使其成为字典序中的下一个更大的排列。

  2. 寻找较小数的位置:从右往左遍历整数切片 nums,寻找第一个满足 nums[i] < nums[i+1] 的下标 i。这个位置代表了较小数的位置。

  3. 寻找较大数的位置:如果找到了 i,则在下降区间 [i+1, n) 中从右往左找到第一个满足 nums[i] < nums[j] 的下标 j,这个位置代表了较大数的位置。

  4. 交换较小数和较大数:交换 nums[i]nums[j],这一步完成后,区间 [i+1, n) 一定是一个降序区间。

  5. 翻转子数组:对 [i+1, n) 区间内的元素进行原地翻转,使其变为升序,这样才能生成下一个排列。

Python 版本解题思路:

Python 版本的解题思路与 Go 版本相似,只是使用了 Python 的列表和对象。

  1. 寻找下一个排列:与 Go 版本相同,我们首先要找到下一个排列,即重新排列给定数字序列,使其成为字典序中的下一个更大的排列。

  2. 寻找较小数的位置:从右往左遍历整数列表 nums,寻找第一个满足 nums[i] < nums[i+1] 的下标 i,代表了较小数的位置。

  3. 寻找较大数的位置:如果找到了 i,则在下降区间 [i+1, n) 中从右往左找到第一个满足 nums[i] < nums[j] 的下标 j,代表了较大数的位置。

  4. 交换较小数和较大数:交换 nums[i]nums[j],这一步完成后,区间 [i+1, n) 一定是一个降序区间。

  5. 翻转子数组:对 [i+1, n) 区间内的元素进行原地翻转,使其变为升序,这样才能生成下一个排列。

Java 版本解题思路:

Java 版本的解题思路与 Go 和 Python 版本相似,只是使用了 Java 的数组和类。

  1. 寻找下一个排列:与其他版本相同,首先要找到下一个排列,即重新排列给定数字序列,使其成为字典序中的下一个更大的排列。

  2. 寻找较小数的位置:从右往左遍历整数数组 nums,寻找第一个满足 nums[i] < nums[i+1] 的下标 i,代表了较小数的位置。

  3. 寻找较大数的位置:如果找到了 i,则在下降区间 [i+1, n) 中从右往左找到第一个满足 nums[i] < nums[j] 的下标 j,代表了较大数的位置。

  4. 交换较小数和较大数:交换 nums[i]nums[j],这一步完成后,区间 [i+1, n) 一定是一个降序区间。

  5. 翻转子数组:对 [i+1, n) 区间内的元素进行原地翻转,使其变为升序,这样才能生成下一个排列。

C++ 版本解题思路:

C++ 版本的解题思路与 Go、Python 和 Java 版本相似,只是使用了 C++ 的数组和类。

  1. 寻找下一个排列:与其他版本相同,首先要找到下一个排列,即重新排列给定数字序列,使其成为字典序中的下一个更大的排列。

  2. 寻找较小数的位置:从右往左遍历整数数组 nums,寻找第一个满足 nums[i] < nums[i+1] 的下标 i,代表了较小数的位置。

  3. 寻找较大数的位置:如果找到了 i,则在下降区间 [i+1, n) 中从右往左找到第一个满足 nums[i] < nums[j] 的下标 j,代表了较大数的位置。

  4. 交换较小数和较大数:交换 nums[i]nums[j],这一步完成后,区间 [i+1, n) 一定是一个降序区间。

  5. 翻转子数组:对 [i+1, n) 区间内的元素进行原地翻转,使其变为升序,这样才能生成下一个排列。

代码

Go

// 解法一
// 定义一个函数 nextPermutation,用于生成下一个排列
func nextPermutation(nums []int) {// 定义两个变量 i 和 j,并初始化为 0i, j := 0, 0// 从倒数第二个元素开始向前遍历整数切片 nums,寻找第一个满足 nums[i] < nums[i+1] 的 ifor i = len(nums) - 2; i >= 0; i-- {if nums[i] < nums[i+1] {break}}// 如果找到了 i,表示存在下一个排列if i >= 0 {// 从最后一个元素开始向前遍历整数切片 nums,寻找第一个满足 nums[j] > nums[i] 的 jfor j = len(nums) - 1; j > i; j-- {if nums[j] > nums[i] {break}}// 交换 nums[i] 和 nums[j]swap(&nums, i, j)}// 对从 i+1 到末尾的部分进行翻转,以获得下一个排列reverse(&nums, i+1, len(nums)-1)
}// 定义一个函数 reverse,用于翻转整数切片 nums 中从位置 i 到 j 的元素
func reverse(nums *[]int, i, j int) {// 使用双指针将元素从两端向中间逐个交换for i < j {swap(nums, i, j)i++j--}
}// 定义一个函数 swap,用于交换整数切片 nums 中位置 i 和 j 的元素
func swap(nums *[]int, i, j int) {// 使用指针访问和交换切片中的元素值(*nums)[i], (*nums)[j] = (*nums)[j], (*nums)[i]
}

Python

class Solution:def nextPermutation(self, nums: List[int]) -> None:"""Do not return anything, modify nums in-place instead."""# Step 1: Find the first decreasing element from right to left (i)i = len(nums) - 2while i >= 0 and nums[i] >= nums[i + 1]:i -= 1# Step 2: Find the first element larger than nums[i] from right to left (j)if i >= 0:j = len(nums) - 1while j > i and nums[j] <= nums[i]:j -= 1# Step 3: Swap nums[i] and nums[j]nums[i], nums[j] = nums[j], nums[i]# Step 4: Reverse the subarray to the right of ileft, right = i + 1, len(nums) - 1while left < right:nums[left], nums[right] = nums[right], nums[left]left += 1right -= 1

Java

class Solution {public void nextPermutation(int[] nums) {// Step 1: Find the first decreasing element from right to left (i)int i = nums.length - 2;while (i >= 0 && nums[i] >= nums[i + 1]) {i--;}// Step 2: Find the first element larger than nums[i] from right to left (j)if (i >= 0) {int j = nums.length - 1;while (j > i && nums[j] <= nums[i]) {j--;}// Step 3: Swap nums[i] and nums[j]int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}// Step 4: Reverse the subarray to the right of iint left = i + 1, right = nums.length - 1;while (left < right) {int temp = nums[left];nums[left] = nums[right];nums[right] = temp;left++;right--;}}
}

Cpp

class Solution {
public:void nextPermutation(vector<int>& nums) {// Step 1: Find the first decreasing element from right to left (i)int i = nums.size() - 2;while (i >= 0 && nums[i] >= nums[i + 1]) {i--;}// Step 2: Find the first element larger than nums[i] from right to left (j)if (i >= 0) {int j = nums.size() - 1;while (j > i && nums[j] <= nums[i]) {j--;}// Step 3: Swap nums[i] and nums[j]swap(nums[i], nums[j]);}// Step 4: Reverse the subarray to the right of iint left = i + 1, right = nums.size() - 1;while (left < right) {swap(nums[left], nums[right]);left++;right--;}}
};

基础知识

Go 版本:

  1. Go 语言基础: 了解 Go 语言的基本语法、数据类型、函数定义和使用、切片(slice)等相关知识。

  2. 指针: 了解指针的概念以及如何在 Go 中使用指针。

  3. 函数: 理解如何定义和调用函数,以及函数的参数和返回值。

  4. 数组切片: 了解 Go 中的切片概念和切片操作,包括切片的创建和修改。

Python 版本:

  1. Python 基础: 了解 Python 的基本语法、列表(list)、条件语句和循环。

  2. 类和对象: 理解如何定义类和创建对象(在 Python 类中定义方法)。

  3. 列表操作: 了解如何操作列表,包括索引、切片、迭代和修改列表元素。

Java 版本:

  1. Java 基础: 熟悉 Java 的基本语法、数组、循环和条件语句。

  2. 类和方法: 了解如何定义类和方法,并且如何在类中使用成员变量和方法。

  3. 数组操作: 熟悉 Java 中数组的创建、遍历和修改操作。

C++ 版本:

  1. C++ 基础: 了解 C++ 的基本语法、数组、循环和条件语句。

  2. 函数: 理解如何定义和调用函数,以及函数的参数和返回值。

  3. 数组操作: 了解如何操作数组,包括索引、遍历和修改数组元素。

32. Longest Valid Parentheses

题目

Given a string containing just the characters'('and')', find the length of the longest valid (well-formed)
parentheses substring.

Example 1:

Input: s = "(()"
Output: 2
Explanation: The longest valid parentheses substring is "()".

Example 2:

Input: s = ")()())"
Output: 4
Explanation: The longest valid parentheses substring is "()()".

Example 3:

Input: s = ""
Output: 0

Constraints:

  • 0 <= s.length <= 3 * 104
  • s[i]is'(', or')'.

题目大意

给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

解题思路

以下是每个版本的解题思路的详细介绍:

Go 版本解题思路:

Go 版本的解题思路是利用栈来处理括号匹配问题,并采用了双指针的方法来计算最长有效括号子串的长度。主要步骤如下:

  1. 定义一个辅助函数 max(a, b int) int 用于返回两个整数中的较大值。

  2. 初始化 leftrightmaxLength 变量,它们分别用于记录左括号数量、右括号数量和最长有效括号子串的长度,初始值都为 0。

  3. 遍历输入字符串 s,从左到右:

    • 如果当前字符是 ‘(’, 则增加 left 计数器。
    • 如果当前字符是 ‘)’, 则增加 right 计数器。
    • 如果 leftright 计数器相等,说明找到了一个有效的括号子串,计算当前有效括号子串的长度,并更新 maxLength
    • 如果 right 大于 left,则重置 leftright 计数器为 0,因为当前的括号串无法匹配。
  4. 重置 leftrightmaxLength 变量为 0,再进行一次从右到左的遍历,处理右括号数量多于左括号数量的情况。

  5. 返回 maxLength,它表示最长有效括号子串的长度。

Python 版本解题思路:

Python 版本的解题思路与 Go 版本相似,也是利用栈来处理括号匹配问题,并采用了双指针的方法来计算最长有效括号子串的长度。主要步骤如下:

  1. 定义一个辅助函数 max(a, b) 用于返回两个数中的较大值。

  2. 初始化 leftrightmaxLength 变量,它们分别用于记录左括号数量、右括号数量和最长有效括号子串的长度,初始值都为 0。

  3. 遍历输入字符串 s,从左到右:

    • 如果当前字符是 ‘(’, 则增加 left 计数器。
    • 如果当前字符是 ‘)’, 则增加 right 计数器。
    • 如果 leftright 计数器相等,说明找到了一个有效的括号子串,计算当前有效括号子串的长度,并更新 maxLength
    • 如果 right 大于 left,则重置 leftright 计数器为 0,因为当前的括号串无法匹配。
  4. 重置 leftrightmaxLength 变量为 0,再进行一次从右到左的遍历,处理右括号数量多于左括号数量的情况。

  5. 返回 maxLength,它表示最长有效括号子串的长度。

Java 版本解题思路:

Java 版本的解题思路与 Go 和 Python 版本相似,也是利用栈来处理括号匹配问题,并采用了双指针的方法来计算最长有效括号子串的长度。主要步骤如下:

  1. 初始化 leftrightmaxLength 变量,它们分别用于记录左括号数量、右括号数量和最长有效括号子串的长度,初始值都为 0。

  2. 遍历输入字符串 s,从左到右:

    • 如果当前字符是 ‘(’, 则增加 left 计数器。
    • 如果当前字符是 ‘)’, 则增加 right 计数器。
    • 如果 leftright 计数器相等,说明找到了一个有效的括号子串,计算当前有效括号子串的长度,并更新 maxLength
    • 如果 right 大于 left,则重置 leftright 计数器为 0,因为当前的括号串无法匹配。
  3. 重置 leftrightmaxLength 变量为 0,再进行一次从右到左的遍历,处理右括号数量多于左括号数量的情况。

  4. 返回 maxLength,它表示最长有效括号子串的长度。

C++ 版本解题思路:

C++ 版本的解题思路与 Go、Python 和 Java 版本相似,也是利用栈来处理括号匹配问题,并采用了双指针的方法来计算最长有效括号子串的长度。主要步骤如下:

  1. 初始化 leftrightmaxLength 变量,它们分别用于记录左括号数量、右括号数量和最长有效括号子串的长度,初始值都为 0。

  2. 遍历输入字符串 s,从左到右:

    • 如果当前字符是 ‘(’, 则增加 left 计数器。
    • 如果当前字符是 ‘)’, 则增加 right 计数器。
    • 如果 leftright 计数器相等,说明找到了一个有效的括号子串,计算当前有效括号子串的长度,并更新 maxLength
    • 如果 right 大于 left,则重置 leftright 计数器为 0,因为当前的括号串无法匹配。
  3. 重置 leftrightmaxLength 变量为 0,再进行一次从右到左的遍历,处理右括号数量多于左括号数量的情况。

  4. 返回 maxLength,它表示最长有效括号子串的长度。

代码

Go

func max(a, b int) int {// 返回两个整数中的较大值if a > b {return a}return b
}// 解法二 双指针
func longestValidParentheses(s string) int {// 初始化左右指针和最大有效括号子串长度left, right, maxLength := 0, 0, 0for i := 0; i < len(s); i++ {// 如果当前字符是左括号 '(',增加左括号计数if s[i] == '(' {left++} else {// 如果当前字符是右括号 ')',增加右括号计数right++}// 如果左右括号计数相等,说明找到了一个有效的括号子串if left == right {// 计算当前有效括号子串的长度并更新最大长度maxLength = max(maxLength, 2*right)} else if right > left {// 如果右括号计数大于左括号计数,重置左右指针left, right = 0, 0}}// 重置左右指针left, right = 0, 0for i := len(s) - 1; i >= 0; i-- {// 从右向左遍历字符串,处理与上面相同的逻辑if s[i] == '(' {left++} else {right++}if left == right {maxLength = max(maxLength, 2*left)} else if left > right {left, right = 0, 0}}// 返回最大有效括号子串的长度return maxLength
}

Python

class Solution:def longestValidParentheses(self, s: str) -> int:def max(a, b):return a if a > b else bleft, right, maxLength = 0, 0, 0# 从左向右遍历字符串for char in s:if char == '(':left += 1else:right += 1if left == right:maxLength = max(maxLength, 2 * right)elif right > left:left, right = 0, 0left, right = 0, 0# 从右向左遍历字符串for i in range(len(s) - 1, -1, -1):char = s[i]if char == '(':left += 1else:right += 1if left == right:maxLength = max(maxLength, 2 * left)elif left > right:left, right = 0, 0return maxLength

Java

class Solution {public int longestValidParentheses(String s) {int left = 0, right = 0, maxLength = 0;// 从左向右遍历字符串for (char c : s.toCharArray()) {if (c == '(') {left++;} else {right++;}if (left == right) {maxLength = Math.max(maxLength, 2 * right);} else if (right > left) {left = 0;right = 0;}}left = 0;right = 0;// 从右向左遍历字符串for (int i = s.length() - 1; i >= 0; i--) {char c = s.charAt(i);if (c == '(') {left++;} else {right++;}if (left == right) {maxLength = Math.max(maxLength, 2 * left);} else if (left > right) {left = 0;right = 0;}}return maxLength;}
}

Cpp

class Solution {
public:int longestValidParentheses(string s) {int left = 0, right = 0, maxLength = 0;// 从左向右遍历字符串for (char c : s) {if (c == '(') {left++;} else {right++;}if (left == right) {maxLength = max(maxLength, 2 * right);} else if (right > left) {left = 0;right = 0;}}left = 0;right = 0;// 从右向左遍历字符串for (int i = s.length() - 1; i >= 0; i--) {char c = s[i];if (c == '(') {left++;} else {right++;}if (left == right) {maxLength = max(maxLength, 2 * left);} else if (left > right) {left = 0;right = 0;}}return maxLength;}
};

Go 版本:

  1. Go 语言基础

    • 变量声明和初始化
    • 循环(for 循环)
    • 条件语句(if-else)
    • 函数声明和调用
    • 数组和切片(slices)的基本操作
  2. 栈的概念

    • Go 中可以使用切片(slices)来模拟栈的行为

Python 版本:

  1. Python 语言基础

    • 变量声明和初始化
    • 循环(for 循环)
    • 条件语句(if-else)
    • 函数声明和调用
    • 字符串的基本操作
  2. 栈的概念

    • Python 中可以使用列表(list)来模拟栈的行为

Java 版本:

  1. Java 语言基础

    • 类和对象的概念
    • 方法声明和调用
    • 循环(for 循环)
    • 条件语句(if-else)
    • 字符串的基本操作
  2. 栈的概念

    • Java 中可以使用集合类(如 ArrayList 或 LinkedList)来模拟栈的行为

C++ 版本:

  1. C++ 语言基础

    • 变量声明和初始化
    • 函数声明和调用
    • 循环(for 循环)
    • 条件语句(if-else)
    • 字符串的基本操作
  2. 栈的概念

    • C++ 中可以使用标准库中的容器(如 std::vector 或 std::deque)来模拟栈的行为

33. Search in Rotated Sorted Array

题目

Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand.

(i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]).

You are given a target value to search. If found in the array return its index, otherwise return -1.

You may assume no duplicate exists in the array.

Your algorithm’s runtime complexity must be in the order of O(log n).

Example 1:

Input: nums = [4,5,6,7,0,1,2], target = 0
Output: 4

Example 2:

Input: nums = [4,5,6,7,0,1,2], target = 3
Output: -1

题目大意

假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

解题思路

下面将分别介绍每个版本的解题思路:

Go 版本解题思路:

  1. 初始化两个指针 lowhigh,分别指向数组的开头和结尾。
  2. 使用二分查找的循环来搜索目标值。
  3. 在每次迭代中,计算中间元素的索引 mid
  4. 检查中间元素与目标值的关系:
    • 如果中间元素等于目标值,则直接返回中间元素的索引。
    • 如果中间元素大于最左边的元素(说明左半段是有序的),则比较目标值与中间元素的大小关系:
      • 如果目标值大于中间元素且小于等于最右边的元素,说明目标值在右半段有序部分,更新 low = mid + 1
      • 否则,目标值在左半段无序部分,更新 high = mid - 1
    • 如果中间元素小于等于最右边的元素(说明右半段是有序的),则比较目标值与中间元素的大小关系:
      • 如果目标值大于中间元素且小于等于最右边的元素,说明目标值在右半段有序部分,更新 low = mid + 1
      • 否则,目标值在左半段无序部分,更新 high = mid - 1
    • 如果中间元素与最左边的元素相等,说明可能存在重复元素,可以将最左边的指针 low 向右移动一位。
    • 如果中间元素与最右边的元素相等,说明可能存在重复元素,可以将最右边的指针 high 向左移动一位。
  5. 重复步骤 3 和步骤 4,直到 low 大于 high,此时未找到目标值,返回 -1。

Python 版本解题思路:

  1. 初始化两个指针 lowhigh,分别指向数组的开头和结尾。
  2. 使用二分查找的循环来搜索目标值。
  3. 在每次迭代中,计算中间元素的索引 mid
  4. 检查中间元素与目标值的关系:
    • 如果中间元素等于目标值,则直接返回中间元素的索引。
    • 如果中间元素大于最左边的元素(说明左半段是有序的),则比较目标值与中间元素的大小关系:
      • 如果目标值大于中间元素且小于等于最右边的元素,说明目标值在右半段有序部分,更新 low = mid + 1
      • 否则,目标值在左半段无序部分,更新 high = mid - 1
    • 如果中间元素小于等于最右边的元素(说明右半段是有序的),则比较目标值与中间元素的大小关系:
      • 如果目标值大于中间元素且小于等于最右边的元素,说明目标值在右半段有序部分,更新 low = mid + 1
      • 否则,目标值在左半段无序部分,更新 high = mid - 1
    • 如果中间元素与最左边的元素相等,说明可能存在重复元素,可以将最左边的指针 low 向右移动一位。
    • 如果中间元素与最右边的元素相等,说明可能存在重复元素,可以将最右边的指针 high 向左移动一位。
  5. 重复步骤 3 和步骤 4,直到 low 大于 high,此时未找到目标值,返回 -1。

Java 版本解题思路:

  1. 初始化两个指针 lowhigh,分别指向数组的开头和结尾。
  2. 使用二分查找的循环来搜索目标值。
  3. 在每次迭代中,计算中间元素的索引 mid
  4. 检查中间元素与目标值的关系:
    • 如果中间元素等于目标值,则直接返回中间元素的索引。
    • 如果中间元素大于最左边的元素(说明左半段是有序的),则比较目标值与中间元素的大小关系:
      • 如果目标值大于中间元素且小于等于最右边的元素,说明目标值在右半段有序部分,更新 low = mid + 1
      • 否则,目标值在左半段无序部分,更新 high = mid - 1
    • 如果中间元素小于等于最右边的元素(说明右半段是有序的),则比较目标值与中间元素的大小关系:
      • 如果目标值大于中间元素且小于等于最右边的元素,说明目标值在右半段有序部分,更新 low = mid + 1
      • 否则,目标值在左半段无序部分,更新 high = mid - 1
    • 如果中间元素与最左边的元素相等,说明可能存在重复元素,可以将最左边的指针 low 向右移动一位。
    • 如果中间元素与最右边的元素相等,说明可能存在重复元素,可以将最右边的指针 high 向左移动一位。
  5. 重复步骤 3 和步骤 4,直到 low 大于 high,此时未找到目标值,返回 -1。

C++ 版本解题思路:

  1. 初始化两个指针 lowhigh,分别指向数组的开头和结尾。
  2. 使用二分查找的循环来搜索目标值。
  3. 在每次迭代中,计算中间元素的索引 mid
  4. 检查中间元素与目标值的关系:
    • 如果中间元素等于目标值,则直接返回中间元素的索引。
    • 如果中间元素大于最左边的元素(说明左半段是有序的),则比较目标值与中间元素的大小关系:
      • 如果目标值大于中间元素且小于等于最右边的元素,说明目标值在右半段有序部分,更新 low = mid + 1
      • 否则,目标值在左半段无序部分,更新 high = mid - 1
    • 如果中间元素小于等于最右边的元素(说明右半段是有序的),则比较目标值与中间元素的大小关系:
      • 如果目标值大于中间元素且小于等于最右边的元素,说明目标值在右半段有序部分,更新 low = mid + 1
      • 否则,目标值在左半段无序部分,更新 high = mid - 1
    • 如果中间元素与最左边的元素相等,说明可能存在重复元素,可以将最左边的指针 low 向右移动一位。
    • 如果中间元素与最右边的元素相等,说明可能存在重复元素,可以将最右边的指针 high 向左移动一位。
  5. 重复步骤 3 和步骤 4,直到 low 大于 high,此时未找到目标值,返回 -1。

这四个版本的解题思路基本一致,都是使用二分查找的变种来在旋转有序数组中搜索目标值,关键是正确处理不同情况下指针的更新以及边界条件的处理。算法的时间复杂度是 O(log n),因为每次迭代都将搜索范围减半。不同版本的代码实现方式有所不同,但核心逻辑相似。

代码

Go

func search(nums []int, target int) int {// 检查数组是否为空,如果是空数组则直接返回-1if len(nums) == 0 {return -1}// 初始化两个指针,分别指向数组的开头和结尾low, high := 0, len(nums)-1// 使用二分查找的循环来搜索目标值for low <= high {// 计算中间元素的索引mid := low + (high-low)>>1// 如果中间元素等于目标值,则直接返回中间元素的索引if nums[mid] == target {return mid} else if nums[mid] > nums[low] { // 如果中间元素在数值大的一部分区间里// 检查目标值是否在左半部分区间内,如果是则更新高指针if nums[low] <= target && target < nums[mid] {high = mid - 1} else {// 否则更新低指针low = mid + 1}} else if nums[mid] < nums[high] { // 如果中间元素在数值小的一部分区间里// 检查目标值是否在右半部分区间内,如果是则更新低指针if nums[mid] < target && target <= nums[high] {low = mid + 1} else {// 否则更新高指针high = mid - 1}} else {// 处理中间元素等于边界元素的情况,移动边界指针以去除重复元素if nums[low] == nums[mid] {low++}if nums[high] == nums[mid] {high--}}}// 如果未找到目标值,则返回-1return -1
}

Python

class Solution:def search(self, nums, target: int):# 如果数组为空,直接返回-1if len(nums) == 0:return -1# 如果数组只有一个元素,分两种情况判断elif len(nums) == 1:if nums[0] != target:return -1else:return 0# 找到旋转点的位置for i in range(len(nums) - 1):if nums[i] > nums[i + 1]:flag = i + 1break# 在旋转点左边进行二分查找left = self.binary_search(nums, target, 0, flag - 1)if left != -1:return leftelse:# 如果左边没有找到,就在旋转点右边进行二分查找right = self.binary_search(nums, target, flag, len(nums) - 1)if right == -1:return -1else:return rightdef binary_search(self, nums, target, left, right):l, r = left, rightwhile l <= r:mid = l + (r - l) // 2if nums[mid] == target:return midelif nums[mid] < target:l = mid + 1else:r = mid - 1return -1

Java

class Solution {public int search(int[] nums, int target) {if (nums == null || nums.length == 0) {return -1;}int low = 0, high = nums.length - 1;while (low <= high) {int mid = low + (high - low) / 2;if (nums[mid] == target) {return mid;} else if (nums[mid] > nums[low]) {if (nums[low] <= target && target < nums[mid]) {high = mid - 1;} else {low = mid + 1;}} else if (nums[mid] < nums[high]) {if (nums[mid] < target && target <= nums[high]) {low = mid + 1;} else {high = mid - 1;}} else {if (nums[low] == nums[mid]) {low++;}if (nums[high] == nums[mid]) {high--;}}}return -1;}
}

Cpp

class Solution {
public:int search(vector<int>& nums, int target) {if (nums.empty()) {return -1;}int low = 0, high = nums.size() - 1;while (low <= high) {int mid = low + (high - low) / 2;if (nums[mid] == target) {return mid;} else if (nums[mid] > nums[low]) {if (nums[low] <= target && target < nums[mid]) {high = mid - 1;} else {low = mid + 1;}} else if (nums[mid] < nums[high]) {if (nums[mid] < target && target <= nums[high]) {low = mid + 1;} else {high = mid - 1;}} else {if (nums[low] == nums[mid]) {low++;}if (nums[high] == nums[mid]) {high--;}}}return -1;}
};

当用不同编程语言实现算法时,需要掌握以下基础知识:

Go 版本:

  1. 函数定义与调用:了解如何定义和调用函数,包括函数的参数和返回值。

  2. 数组与切片:Go 中没有传统的数组,而是使用切片来处理动态数组。需要了解如何声明、初始化和操作切片。

  3. 循环与条件语句:了解如何使用 forif 语句来实现循环和条件判断。

  4. 二分查找:理解二分查找的原理和实现方法,包括如何计算中间元素的索引和更新指针。

  5. 算法复杂度:理解算法复杂度的概念,包括时间复杂度和空间复杂度,并了解如何分析算法的性能。

Python 版本:

  1. 类和方法:了解如何定义类和类方法,并如何创建类的实例。

  2. 列表:Python 中的列表类似于动态数组,需要了解如何声明、初始化和操作列表。

  3. 循环与条件语句:了解如何使用 forif 语句来实现循环和条件判断。

  4. 二分查找:理解二分查找的原理和实现方法,包括如何计算中间元素的索引和更新指针。

  5. 函数递归:理解函数递归的概念,以及如何在递归中解决问题。

Java 版本:

  1. 类和方法:了解如何定义类和类方法,并如何创建类的实例。

  2. 数组:Java 中有静态数组,需要了解如何声明、初始化和操作数组。

  3. 循环与条件语句:了解如何使用 forif 语句来实现循环和条件判断。

  4. 二分查找:理解二分查找的原理和实现方法,包括如何计算中间元素的索引和更新指针。

  5. 算法复杂度:理解算法复杂度的概念,包括时间复杂度和空间复杂度,并了解如何分析算法的性能。

C++ 版本:

  1. 类和方法:了解如何定义类和类方法,并如何创建类的实例。

  2. 向量:C++ 中的向量类似于动态数组,需要了解如何声明、初始化和操作向量。

  3. 循环与条件语句:了解如何使用 forif 语句来实现循环和条件判断。

  4. 二分查找:理解二分查找的原理和实现方法,包括如何计算中间元素的索引和更新指针。

  5. 算法复杂度:理解算法复杂度的概念,包括时间复杂度和空间复杂度,并了解如何分析算法的性能。

以上是实现搜索旋转排序数组算法所需要掌握的基础知识,包括语言特性、数据结构操作、循环和条件判断、二分查找算法和算法复杂度分析等方面的知识。不同编程语言有不同的语法和库函数,但核心的算法思想和逻辑都是相似的。

34. Find First and Last Position of Element in Sorted Array

题目

Given an array of integers nums sorted in ascending order, find the starting and ending position of a given target value.

Your algorithm’s runtime complexity must be in the order of O(log n).

If the target is not found in the array, return [-1, -1].

Example 1:

Input: nums = [5,7,7,8,8,10], target = 8
Output: [3,4]

Example 2:

Input: nums = [5,7,7,8,8,10], target = 6
Output: [-1,-1]

题目大意

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。你的算法时间复杂度必须是 O(log n) 级别。如果数组中不存在目标值,返回 [-1, -1]。

解题思路

Go 版本的解题思路:

  1. 首先,初始化两个指针 l_ptrr_ptr,它们分别指向数组的起始位置和结束位置。

  2. 初始化一个整数切片 ans,用于存储目标值的范围,初始值为 [-1, -1]

  3. 使用二分搜索的思想,在循环中不断缩小搜索范围,直到找到目标值的范围或确定目标值不存在。

  4. 在每次迭代中,计算中间位置的索引 mid

  5. 如果中间位置的元素等于目标值,则更新 ans[0]ans[1]mid。然后,通过循环向左和向右扩展,找到目标值的第一个和最后一个位置。

  6. 如果中间位置的元素大于目标值,则将右指针 r_ptr 移动到 mid - 1,以缩小搜索范围到左半部分。

  7. 如果中间位置的元素小于目标值,则将左指针 l_ptr 移动到 mid + 1,以缩小搜索范围到右半部分。

  8. 最终,返回包含目标值范围的 ans 数组。

Python 版本的解题思路:

Python 版本的解题思路与 Go 版本类似,但使用了函数和模块的结构来组织代码:

  1. 首先,定义了两个辅助函数 find_leftfind_right,它们分别用于查找目标值的左边界和右边界。

  2. 在主函数 searchRange 中,初始化两个整数,用于存储目标值的左边界和右边界。这些值是通过调用辅助函数来找到的。

  3. 辅助函数 find_leftfind_right 采用二分搜索的思想,不断缩小搜索范围,直到找到目标值的边界或确定目标值不存在。

  4. 当找到左边界时,继续向右搜索,找到右边界。

  5. 最终,返回包含目标值范围的结果列表。

Java 版本的解题思路:

Java 版本的解题思路与 Go 版本类似,但使用了类和方法的结构来组织代码:

  1. Solution 类中,定义了一个方法 searchRange 来解决问题。

  2. searchRange 方法中,初始化两个整数,用于存储目标值的左边界和右边界。

  3. 使用 while 循环,在循环中不断缩小搜索范围,直到找到目标值的范围或确定目标值不存在。

  4. 在每次迭代中,计算中间位置的索引 mid

  5. 如果中间位置的元素等于目标值,则更新左边界和右边界,然后通过循环向左和向右扩展,找到目标值的第一个和最后一个位置。

  6. 如果中间位置的元素大于目标值,则缩小搜索范围到左半部分,将右指针 r_ptr 移动到 mid - 1

  7. 如果中间位置的元素小于目标值,则缩小搜索范围到右半部分,将左指针 l_ptr 移动到 mid + 1

  8. 最终,返回包含目标值范围的整数数组。

C++ 版本的解题思路:

C++ 版本的解题思路与 Java 版本类似,但使用了类和方法的结构来组织代码:

  1. Solution 类中,定义了一个方法 searchRange 来解决问题。

  2. searchRange 方法中,初始化两个整数,用于存储目标值的左边界和右边界。

  3. 使用 while 循环,在循环中不断缩小搜索范围,直到找到目标值的范围或确定目标值不存在。

  4. 在每次迭代中,计算中间位置的索引 mid

  5. 如果中间位置的元素等于目标值,则更新左边界和右边界,然后通过循环向左和向右扩展,找到目标值的第一个和最后一个位置。

  6. 如果中间位置的元素大于目标值,则缩小搜索范围到左半部分,将右指针 r_ptr 移动到 mid - 1

  7. 如果中间位置的元素小于目标值,则缩小搜索范围到右半部分,将左指针 l_ptr 移动到 mid

  8. 最终,返回包含目标值范围的整数数组。

这些是每个版本的解题思路的详细说明,希望这能帮助您更好地理解每个解决方案的工作原理。如果您有任何进一步的问题,请随时提问。

代码

Go

func searchRange(nums []int, target int) []int {// 获取数组的长度n := len(nums)// 初始化左指针为 0l_prt := 0// 初始化右指针为数组长度减一r_prt := n - 1// 初始化答案数组,用于存储目标值的范围ans := []int{-1, -1}  // 在左指针小于等于右指针的条件下进行循环for l_prt <= r_prt {// 计算中间位置的索引mid := ((r_prt - l_prt) >> 1) + l_prt// 如果中间位置的元素等于目标值if nums[mid] == target {// 更新答案的左边界和右边界为中间位置ans[0] = midans[1] = mid// 从中间位置向左扩展,找到目标值的第一个位置for ans[0] > 0 && nums[ans[0]-1] == target {ans[0]--}// 从中间位置向右扩展,找到目标值的最后一个位置for ans[1] < n - 1 && nums[ans[1] + 1] == target {ans[1]++}// 跳出循环,因为已经找到了目标值的范围break} else if nums[mid] > target {// 如果中间位置的元素大于目标值,缩小搜索范围到左半部分r_prt = mid - 1} else {// 如果中间位置的元素小于目标值,缩小搜索范围到右半部分l_prt = mid + 1}}// 返回包含目标值范围的答案数组return ans
}

Python

class Solution:def searchRange(self, nums: List[int], target: int) -> List[int]:# 辅助函数:查找目标值的左边界def find_left():start, end = 0, len(nums) - 1res = -1  # 初始化结果为-1,表示未找到while start <= end:mid = (start + end) // 2  # 计算中间位置的索引if nums[mid] < target:start = mid + 1  # 如果中间值小于目标值,缩小搜索范围到右半部分elif nums[mid] > target:end = mid - 1  # 如果中间值大于目标值,缩小搜索范围到左半部分else:res = mid  # 找到目标值,更新结果为当前位置end = mid - 1  # 继续向左搜索更左边的位置return res# 辅助函数:查找目标值的右边界def find_right():start, end = 0, len(nums) - 1res = -1  # 初始化结果为-1,表示未找到while start <= end:mid = (start + end) // 2  # 计算中间位置的索引if nums[mid] < target:start = mid + 1  # 如果中间值小于目标值,缩小搜索范围到右半部分elif nums[mid] > target:end = mid - 1  # 如果中间值大于目标值,缩小搜索范围到左半部分else:res = mid  # 找到目标值,更新结果为当前位置start = mid + 1  # 继续向右搜索更右边的位置return res# 返回包含目标值范围的结果数组,左边界和右边界分别由辅助函数找到return [find_left(), find_right()]

Java

class Solution {public int[] searchRange(int[] nums, int target) {// 获取数组的长度int n = nums.length;// 初始化左指针为0int l_ptr = 0;// 初始化右指针为数组长度减一int r_ptr = n - 1;// 初始化答案数组,用于存储目标值的范围int[] ans = {-1, -1};// 在左指针小于等于右指针的条件下进行循环while (l_ptr <= r_ptr) {// 计算中间位置的索引int mid = (r_ptr - l_ptr) / 2 + l_ptr;// 如果中间位置的元素等于目标值if (nums[mid] == target) {// 更新答案的左边界和右边界为中间位置ans[0] = mid;ans[1] = mid;// 从中间位置向左扩展,找到目标值的第一个位置while (ans[0] > 0 && nums[ans[0] - 1] == target) {ans[0]--;}// 从中间位置向右扩展,找到目标值的最后一个位置while (ans[1] < n - 1 && nums[ans[1] + 1] == target) {ans[1]++;}// 跳出循环,因为已经找到了目标值的范围break;} else if (nums[mid] > target) {// 如果中间位置的元素大于目标值,缩小搜索范围到左半部分r_ptr = mid - 1;} else {// 如果中间位置的元素小于目标值,缩小搜索范围到右半部分l_ptr = mid + 1;}}// 返回包含目标值范围的答案数组return ans;}
}

Cpp

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {int ret;  // 用于存储目标值的起始位置int l = 0, r = nums.size() - 1;  // 初始化左右指针,表示搜索范围// 第一个循环:查找目标值的起始位置while (l < r) {int mid = (l + r) / 2;  // 计算中间位置的索引if (nums[mid] < target) {l = mid + 1;  // 如果中间值小于目标值,缩小搜索范围到右半部分} else {r = mid;  // 否则,缩小搜索范围到左半部分}}// 如果找到的位置超出数组范围或者该位置的值不等于目标值,返回{-1, -1}if (l == nums.size() || nums[l] != target) {return {-1, -1};}ret = l;  // 记录目标值的起始位置l = 0;r = nums.size() - 1;// 第二个循环:查找目标值的结束位置while (l < r) {int mid = (l + r + 1) / 2;  // 计算中间位置的索引if (nums[mid] > target) {r = mid - 1;  // 如果中间值大于目标值,缩小搜索范围到左半部分} else {l = mid;  // 否则,缩小搜索范围到右半部分}}// 返回包含目标值范围的结果数组,包括起始位置和结束位置return {ret, l};}
};

每个版本的所需基础知识:

Go 版本的基础知识:

  1. 切片(Slices):Go 语言中的切片是动态数组,非常常用于处理数组数据。在这个解决方案中,nums 是一个整数切片,而 ans 也是一个整数切片,用于存储目标值的范围。

  2. 循环和条件语句:解决方案中使用了 for 循环和 if 条件语句来实现逻辑控制,例如,在二分搜索中不断缩小搜索范围。

  3. 二分搜索:这个问题的关键是使用二分搜索算法。需要了解二分搜索的基本原理,即通过比较中间元素与目标值的大小,将搜索范围缩小一半,直到找到目标或确定目标不存在。

Python 版本的基础知识:

  1. 列表(Lists):Python 中的列表类似于数组,用于存储一组元素。在这个解决方案中,nums 是一个整数列表,而 ans 也是一个整数列表,用于存储目标值的范围。

  2. 函数和模块:解决方案中定义了两个辅助函数 find_leftfind_right,并使用了函数来组织代码。还需要了解 Python 模块的概念,例如 List 类型在 typing 模块中的导入。

  3. 循环和条件语句:与 Go 版本一样,Python 版本也使用了 for 循环和 if 条件语句来实现逻辑控制。

  4. 二分搜索:同样需要了解二分搜索的基本原理和实现方式。

Java 版本的基础知识:

  1. 数组:Java 使用数组来存储一组元素。在这个解决方案中,nums 是一个整数数组,而 ans 也是一个整数数组,用于存储目标值的范围。

  2. 类和方法:Java 版本使用了一个类 Solution,其中包含一个方法 searchRange 来解决问题。需要了解如何定义类和方法,并调用它们。

  3. 循环和条件语句:与其他版本一样,Java 版本也使用了 while 循环和 if 条件语句来实现逻辑控制。

  4. 二分搜索:了解二分搜索的基本原理和实现方式。

C++ 版本的基础知识:

  1. 向量(Vectors):C++ 中的向量类似于动态数组,用于存储一组元素。在这个解决方案中,nums 是一个整数向量,而 ans 也是一个整数向量,用于存储目标值的范围。

  2. 类和方法:与 Java 版本一样,C++ 版本也使用了一个类 Solution,其中包含一个方法 searchRange 来解决问题。需要了解如何定义类和方法,并调用它们。

  3. 循环和条件语句:与其他版本一样,C++ 版本也使用了 while 循环和 if 条件语句来实现逻辑控制。

  4. 二分搜索:了解二分搜索的基本原理和实现方式。

35. Search Insert Position

题目

Given a sorted array and a target value, return the index if the target is found. If not, return the index where it
would be if it were inserted in order.

You may assume no duplicates in the array.

Example 1:

Input: [1,3,5,6], 5
Output: 2

Example 2:

Input: [1,3,5,6], 2
Output: 1

Example 3:

Input: [1,3,5,6], 7
Output: 4

Example 4:

Input: [1,3,5,6], 0
Output: 0

题目大意

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。

解题思路

以下是每个版本的解题思路:

Go 版本解题思路:

  1. 定义两个指针 lowhigh,它们分别指向数组的起始位置和末尾位置。

  2. 使用循环执行二分查找:

    • 计算中间元素的索引 mid,使用 (low + (high - low) >> 1) 计算。
    • 如果中间元素 nums[mid] 大于或等于目标值 target,将 high 指针移动到 mid - 1,表示在左半部分继续搜索。
    • 否则,如果中间元素小于目标值 target
      • 检查中间元素的下一个元素是否大于等于目标值 target 或者是否已经到达数组末尾。
      • 如果是,说明目标值应该插入到当前位置的后面,返回 mid + 1 作为插入位置的索引。
      • 否则,将 low 指针移动到 mid + 1,表示在右半部分继续搜索。
  3. 如果循环结束仍然没有找到合适的位置,说明目标值应该插入到数组的开头,返回 0。

Python 版本解题思路:

  1. 定义两个指针 lowhigh,它们分别指向数组的起始位置和末尾位置。

  2. 使用 while 循环执行二分查找:

    • 计算中间元素的索引 mid,使用 (low + (high - low) // 2) 计算。
    • 如果中间元素 nums[mid] 大于或等于目标值 target,将 high 指针移动到 mid - 1,表示在左半部分继续搜索。
    • 否则,如果中间元素小于目标值 target
      • 检查中间元素的下一个元素是否大于等于目标值 target 或者是否已经到达数组末尾。
      • 如果是,说明目标值应该插入到当前位置的后面,返回 mid + 1 作为插入位置的索引。
      • 否则,将 low 指针移动到 mid + 1,表示在右半部分继续搜索。
  3. 如果循环结束仍然没有找到合适的位置,说明目标值应该插入到数组的开头,返回 0。

Java 版本解题思路:

  1. 定义两个指针 lowhigh,它们分别指向数组的起始位置和末尾位置。

  2. 使用 while 循环执行二分查找:

    • 计算中间元素的索引 mid,使用 (low + (high - low) / 2) 计算。
    • 如果中间元素 nums[mid] 大于或等于目标值 target,将 high 指针移动到 mid - 1,表示在左半部分继续搜索。
    • 否则,如果中间元素小于目标值 target
      • 检查中间元素的下一个元素是否大于等于目标值 target 或者是否已经到达数组末尾。
      • 如果是,说明目标值应该插入到当前位置的后面,返回 mid + 1 作为插入位置的索引。
      • 否则,将 low 指针移动到 mid + 1,表示在右半部分继续搜索。
  3. 如果循环结束仍然没有找到合适的位置,说明目标值应该插入到数组的开头,返回 0。

C++ 版本解题思路:

  1. 定义两个指针 lowhigh,它们分别指向向量(动态数组)的起始位置和末尾位置。

  2. 使用 while 循环执行二分查找:

    • 计算中间元素的索引 mid,使用 (low + (high - low) / 2) 计算。
    • 如果中间元素 nums[mid] 大于或等于目标值 target,将 high 指针移动到 mid - 1,表示在左半部分继续搜索。
    • 否则,如果中间元素小于目标值 target
      • 检查中间元素的下一个元素是否大于等于目标值 target 或者是否已经到达向量末尾。
      • 如果是,说明目标值应该插入到当前位置的后面,返回 mid + 1 作为插入位置的索引。
      • 否则,将 low 指针移动到 mid + 1,表示在右半部分继续搜索。
  3. 如果循环结束仍然没有找到合适的位置,说明目标值应该插入到向量的开头,返回 0。

总的来说,每个版本的解题思路都是基于二分查找算法的变种,通过不断更新 lowhigh 指针来逼近目标值的插入位置。根据不同的编程语言,语法和数据结构的细节会有所不同,但基本思路保持一致。

代码

Go

func searchInsert(nums []int, target int) int {// 定义两个指针,low 指向数组的起始位置,high 指向数组的末尾位置low, high := 0, len(nums)-1// 使用循环执行二分查找for low <= high {// 计算中间元素的索引mid := low + (high-low)>>1// 如果中间元素大于等于目标值if nums[mid] >= target {// 将 high 指针移动到中间元素的前一个位置high = mid - 1} else {// 如果中间元素小于目标值// 检查中间元素的下一个元素是否大于等于目标值,或者是否已经到达数组末尾if (mid == len(nums)-1) || (nums[mid+1] >= target) {// 如果是,说明目标值应该插入到当前位置的后面,返回当前位置的下一个索引return mid + 1}// 否则,将 low 指针移动到中间元素的下一个位置low = mid + 1}}// 如果循环结束仍然没有找到合适的位置,说明目标值应该插入到数组的开头,返回0return 0
}

Python

class Solution:def searchInsert(self, nums: List[int], target: int) -> int:# 定义两个指针,low指向数组的起始位置,high指向数组的末尾位置low, high = 0, len(nums) - 1# 使用循环执行二分查找while low <= high:# 计算中间元素的索引mid = low + (high - low) // 2# 如果中间元素大于等于目标值if nums[mid] >= target:# 将high指针移动到中间元素的前一个位置high = mid - 1else:# 如果中间元素小于目标值# 检查中间元素的下一个元素是否大于等于目标值,或者是否已经到达数组末尾if mid == len(nums) - 1 or nums[mid + 1] >= target:# 如果是,说明目标值应该插入到当前位置的后面,返回当前位置的下一个索引return mid + 1# 否则,将low指针移动到中间元素的下一个位置low = mid + 1# 如果循环结束仍然没有找到合适的位置,说明目标值应该插入到数组的开头,返回0return 0

Java

class Solution {public int searchInsert(int[] nums, int target) {// 定义两个指针,low指向数组的起始位置,high指向数组的末尾位置int low = 0, high = nums.length - 1;// 使用循环执行二分查找while (low <= high) {// 计算中间元素的索引int mid = low + (high - low) / 2;// 如果中间元素大于等于目标值if (nums[mid] >= target) {// 将high指针移动到中间元素的前一个位置high = mid - 1;} else {// 如果中间元素小于目标值// 检查中间元素的下一个元素是否大于等于目标值,或者是否已经到达数组末尾if (mid == nums.length - 1 || nums[mid + 1] >= target) {// 如果是,说明目标值应该插入到当前位置的后面,返回当前位置的下一个索引return mid + 1;}// 否则,将low指针移动到中间元素的下一个位置low = mid + 1;}}// 如果循环结束仍然没有找到合适的位置,说明目标值应该插入到数组的开头,返回0return 0;}
}

Cpp

class Solution {
public:int searchInsert(vector<int>& nums, int target) {// 定义两个指针,low指向数组的起始位置,high指向数组的末尾位置int low = 0, high = nums.size() - 1;// 使用循环执行二分查找while (low <= high) {// 计算中间元素的索引int mid = low + (high - low) / 2;// 如果中间元素大于等于目标值if (nums[mid] >= target) {// 将high指针移动到中间元素的前一个位置high = mid - 1;} else {// 如果中间元素小于目标值// 检查中间元素的下一个元素是否大于等于目标值,或者是否已经到达数组末尾if (mid == nums.size() - 1 || nums[mid + 1] >= target) {// 如果是,说明目标值应该插入到当前位置的后面,返回当前位置的下一个索引return mid + 1;}// 否则,将low指针移动到中间元素的下一个位置low = mid + 1;}}// 如果循环结束仍然没有找到合适的位置,说明目标值应该插入到数组的开头,返回0return 0;}
};

当理解和编写每个版本的代码时,你需要掌握以下基础知识:

  1. Go 版本:
  • 切片 (Slices):Go 中的数组和切片是非常重要的数据结构。切片是动态数组,用于存储一系列相同类型的元素。
  • 循环和条件语句:你需要了解 Go 中的 for 循环和 if 条件语句的用法,以便编写代码中的循环和条件判断。
  • 函数:你需要了解如何定义和调用函数,以及函数的参数和返回值。
  • 二分查找算法:理解二分查找算法的工作原理对于理解此代码非常重要。
  1. Python 版本:
  • 列表 (Lists):Python 中的列表是有序的数据结构,可以存储多种数据类型。在此问题中,列表用于存储整数元素。
  • 循环和条件语句:你需要了解 Python 中的 while 循环和 if 条件语句的使用方法,以便编写代码中的循环和条件判断。
  • 类和方法:Python 中的 class 和方法定义方式。在这个版本中,代码被封装在一个类中。
  • 二分查找算法:理解二分查找算法的工作原理对于理解此代码非常重要。
  1. Java 版本:
  • 数组:Java 中的数组是一种静态数据结构,你需要了解如何声明、初始化和访问数组元素。
  • 循环和条件语句:你需要了解 Java 中的 while 循环和 if 条件语句的使用方法,以便编写代码中的循环和条件判断。
  • 类和方法:Java 中的类和方法的定义方式。在这个版本中,代码被封装在一个类中。
  • 二分查找算法:理解二分查找算法的工作原理对于理解此代码非常重要。
  1. C++ 版本:
  • 向量 (Vectors):C++ 中的向量是一种动态数组,类似于 Python 中的列表或 Go 中的切片。
  • 循环和条件语句:你需要了解 C++ 中的 while 循环和 if 条件语句的使用方法,以便编写代码中的循环和条件判断。
  • 类和方法:C++ 中的类和方法的定义方式。在这个版本中,代码被封装在一个类中。
  • 二分查找算法:理解二分查找算法的工作原理对于理解此代码非常重要。

总的来说,无论选择哪种编程语言的版本,都需要熟悉数组、循环、条件语句和二分查找算法。此外,了解特定编程语言的语法和标准库函数也是非常重要的,以便能够正确地编写和理解代码。如果你不熟悉某种特定语言,建议先学习该语言的基础知识,然后再尝试理解和编写相应版本的代码。

相关文章:

Go-Python-Java-C-LeetCode高分解法-第五周合集

前言 本题解Go语言部分基于 LeetCode-Go 其他部分基于本人实践学习 个人题解GitHub连接&#xff1a;LeetCode-Go-Python-Java-C Go-Python-Java-C-LeetCode高分解法-第一周合集 Go-Python-Java-C-LeetCode高分解法-第二周合集 Go-Python-Java-C-LeetCode高分解法-第三周合集 G…...

【前端知识】前端加密算法(base64、md5、sha1、escape/unescape、AES/DES)

前端加密算法 一、base64加解密算法 简介&#xff1a;Base64算法使用64个字符&#xff08;A-Z、a-z、0-9、、/&#xff09;来表示二进制数据的64种可能性&#xff0c;将每3个字节的数据编码为4个可打印字符。如果字节数不是3的倍数&#xff0c;将会进行填充。 优点&#xff1…...

leetcode 925. 长按键入

2023.9.7 我的基本思路是两数组字符逐一对比&#xff0c;遇到不同的字符&#xff0c;判断一下typed与上一字符是否相同&#xff0c;不相同返回false&#xff0c;相同则继续对比。 最后要分别判断name和typed分别先遍历完时的情况。直接看代码&#xff1a; class Solution { p…...

[CMake教程] 循环

目录 一、foreach()二、while()三、break() 与 continue() 作为一个编程语言&#xff0c;CMake也少不了循环流程控制&#xff0c;他提供两种循环foreach() 和 while()。 一、foreach() 基本语法&#xff1a; foreach(<loop_var> <items>)<commands> endfo…...

Mojo安装使用初体验

一个声称比python块68000倍的语言 蹭个热度&#xff0c;安装试试 系统配置要求&#xff1a; 不支持Windows系统 配置要求: 系统&#xff1a;Ubuntu 20.04/22.04 LTSCPU&#xff1a;x86-64 CPU (with SSE4.2 or newer)内存&#xff1a;8 GiB memoryPython 3.8 - 3.10g or cla…...

艺术与AI:科技与艺术的完美融合

文章目录 艺术创作的新工具生成艺术艺术与数据 AI与互动艺术虚拟现实&#xff08;VR&#xff09;与增强现实&#xff08;AR&#xff09;机器学习与互动性 艺术与AI的伦理问题结语 &#x1f389;欢迎来到AIGC人工智能专栏~艺术与AI&#xff1a;科技与艺术的完美融合 ☆* o(≧▽≦…...

Android常用的工具“小插件”——Widget机制

Widget俗称“小插件”&#xff0c;是Android系统中一个很常用的工具。比如我们可以在Launcher中添加一个音乐播放器的Widget。 在Launcher上可以添加插件&#xff0c;那么是不是说只有Launcher才具备这个功能呢&#xff1f; Android系统并没有具体规定谁才能充当“Widget容器…...

探索在云原生环境中构建的大数据驱动的智能应用程序的成功案例,并分析它们的关键要素。

文章目录 1. Netflix - 个性化推荐引擎2. Uber - 实时数据分析和决策支持3. Airbnb - 价格预测和优化5. Google - 自然语言处理和搜索优化 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;CSDN新晋作者 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 ✨收录专…...

jupyter 添加中文选项

文章目录 jupyter 添加中文选项1. 下载中文包2. 选择中文重新加载一下&#xff0c;页面就变成中文了 jupyter 添加中文选项 1. 下载中文包 pip install jupyterlab-language-pack-zh-CN2. 选择中文 重新加载一下&#xff0c;页面就变成中文了 这才是设置中文的正解&#xff…...

系列十、Java操作RocketMQ之批量消息

一、概述 RocketMQ可以一次性发送一组消息&#xff0c;那么这一组消息会被当做一个消息进行消费。 二、案例代码 2.1、pom 同系列五 2.2、RocketMQConstant 同系列五 2.3、BatchConsumer package org.star.batch.consumer;import cn.hutool.core.util.StrUtil; import lom…...

leetcode1两数之和

题目&#xff1a; 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你…...

近年GDC服务器分享合集(四): 《火箭联盟》:为免费游玩而进行的扩展

如今&#xff0c;网络游戏采用免费游玩&#xff08;Free to Play&#xff09;加内购的比例要远大于买断制&#xff0c;这是因为前者能带来更低的用户门槛。甚至有游戏为了获取更多的用户&#xff0c;选择把原来的买断制改为免费游玩&#xff0c;一个典型的例子就是最近的网易的…...

android反射详解

1&#xff0c;反射的定义 一般情况下&#xff0c;我们使用某个类时必定知道它是什么类&#xff0c;是用来做什么的&#xff0c;并且能够获得此类的引用。于是我们直接对这个类进行实例化&#xff0c;之后使用这个类对象进行操作。 反射则是一开始并不知道我要初始化的类对象是…...

Python 反射和动态执行

反射主要应用于类的对象上&#xff0c;在运行时&#xff0c;将对象中的属性和方法反射出来&#xff0c;通过字符串对对象成员&#xff08;属性、方法&#xff09;进行查找、获取、删除、添加成员等动作&#xff0c;是一种基于字符串的事件驱动技术。 python是一门动态语言&…...

计算机网络常见端口号

端口号标识了一个主机上进行通信的不同的应用程序。比如网站服务器80端口一般都是开启的&#xff0c;等你来连接。 端口划分&#xff1a; &#xff08;1&#xff09;常用端口&#xff0c;公共端口&#xff08;保留给公共服务所使用&#xff09;&#xff0c;端口号为0-1023之间…...

SpringBoot / Vue 对SSE的基本使用(简单上手)

一、SSE是什么&#xff1f; SSE技术是基于单工通信模式&#xff0c;只是单纯的客户端向服务端发送请求&#xff0c;服务端不会主动发送给客户端。服务端采取的策略是抓住这个请求不放&#xff0c;等数据更新的时候才返回给客户端&#xff0c;当客户端接收到消息后&#xff0c;…...

Qt串口基本设置与协议收发

前言 1.一直都想要做一个Qt上位机&#xff0c;趁着这个周末有时间&#xff0c;动手写一下 2.comboBox没有点击的信号&#xff0c;所以做了一个触发的功能 3.Qt的数据类型很奇怪&#xff0c;转来转去的我也搞得很迷糊 4.给自己挖个坑&#xff0c;下一期做一个查看波形的上位…...

interview3-微服务与MQ

一、SpringCloud篇 &#xff08;1&#xff09;服务注册 常见的注册中心&#xff1a;eureka、nacos、zookeeper eureka做服务注册中心&#xff1a; 服务注册&#xff1a;服务提供者需要把自己的信息注册到eureka&#xff0c;由eureka来保存这些信息&#xff0c;比如服务名称、…...

kafka详解一

kafka详解一 1、消息引擎背景 根据维基百科的定义&#xff0c;消息引擎系统是一组规范。企业利用这组规范在不同系统之间传递语义准确的消息&#xff0c;实现松耦合的异步式数据传递. 即&#xff1a;系统 A 发送消息给消息引擎系统&#xff0c;系统 B 从消息引擎系统中读取 A…...

Flutter yuv 转 rgb

1、引用yuv_converter库 yuv_converter: ^0.0.1 2、导入头文件&#xff1a; import package:yuv_converter/yuv_converter.dart;3、yuv转rgb YuvConverter.yuv420NV21ToRgba8888(yuvRawData, 512, 512) 根据yuv格式选择不同的api。 举个例子&#xff1a; void initState() …...

MySQL——子查询

2023.9.8 相关学习笔记&#xff1a; #子查询 /* 含义&#xff1a; 出现在其他语句中的select语句&#xff0c;称为子查询或内查询 外部的查询语句&#xff0c;称为主查询或外查询分类&#xff1a; 按子查询出现的位置&#xff1a;select后面&#xff1a;仅仅支持标量子查询fro…...

Java学习笔记---多态

面向对象三大特征之一&#xff08;继承&#xff0c;封装&#xff0c;多态&#xff09; 多态的应用场景&#xff1a;根据传递对象的不同&#xff0c;调用不同的show方法 一、多态的定义 同类型的对象&#xff0c;表现出的不同形态&#xff08;对象的多种形态&#xff09; 二…...

2023-09-10 LeetCode每日一题(课程表 II)

2023-09-10每日一题 一、题目编号 210. 课程表 II二、题目链接 点击跳转到题目位置 三、题目描述 现在你总共有 numCourses 门课需要选&#xff0c;记为 0 到 numCourses - 1。给你一个数组 prerequisites &#xff0c;其中 prerequisites[i] [ai, bi] &#xff0c;表示在…...

合并区间【贪心算法】

合并区间 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&#xff0c;并返回 一个不重叠的区间数组&#xff0c;该数组需恰好覆盖输入中的所有区间 。 class Solution {public int[][] merge(int[…...

2023,软件测试人的未来在哪里?

2023年&#xff0c;IT行业出现空前的萧条&#xff0c;首先是年初一开始各大厂像着了魔似的不约而同的纷纷裁员、降薪、奖金包缩水&#xff0c;随之而来的是需求萎缩&#xff0c;HC减少或封锁等等。 而有幸未被列入裁员名单的在职人员&#xff0c;庆幸之余也心有余悸&#xff0…...

Python中的Numpy向量计算(R与Python系列第三篇)

目录 一、什么是Numpy? 二、如何导入NumPy? 三、生成NumPy数组 3.1利用序列生成 3.2使用特定函数生成NumPy数组 &#xff08;1&#xff09;使用np.arange() &#xff08;2&#xff09;使用np.linspace() 四、NumPy数组的其他常用函数 &#xff08;1&#xff09;np.z…...

LeetCode刷题笔记【27】:贪心算法专题-5(无重叠区间、划分字母区间、合并区间)

文章目录 前置知识435. 无重叠区间题目描述参考<452. 用最少数量的箭引爆气球>, 间接求解直接求"重叠区间数量" 763.划分字母区间题目描述贪心 - 建立"最后一个当前字母"数组优化marker创建的过程 56. 合并区间题目描述解题思路代码① 如果有重合就合…...

nvidia-smi 命令详解

nvidia-smi 命令详解 1. nvidia-smi 面板解析2. 显存与GPU的区别 Reference: nvidia-smi命令详解 相关文章&#xff1a; nvidia-smi nvcc -V 及 CUDA、cuDNN 安装 nvidia-smi(NVIDIA System Management Interface) 是一种命令行实用程序&#xff0c;用于监控和管理 NVIDIA G…...

fork()函数的返回值

在程序中&#xff0c;int pd fork() 是一个典型的 fork() 调用。fork() 函数会创建一个新的进程&#xff0c;然后在父进程中返回子进程的进程ID&#xff08;PID&#xff09;&#xff0c;在子进程中返回0。所以 pd 的值会根据当前进程是父进程还是子进程而有所不同&#xff1a;…...

Stable Diffusion WebUI挂VPN不能跑图解决办法(Windows)

如何解决SD在打开VPN的状态不能运行的问题 在我们开VPN的时候会出现无法生成图片&#xff0c;也无法做其他任何事&#xff0c;这个时候是不是很着急呢&#xff1f; 别急&#xff0c;我这里会说明如何解决。 就像这样&#xff0c;运行半天生成不了图&#xff0c;有时还会出现…...

高质量的邯郸网站建设/seo教程书籍

17Tech 消息&#xff1a;瑞星程序员自曝内幕&#xff1a;“瑞星2007程序核心部分内部机密&#xff0c;现在告诉大家&#xff0c;我辞职了” 瑞星2007程序核心部分内部机密&#xff0c;现在告诉大家&#xff0c;我辞职了 我辞职的原因是因为瑞星总部,已经没有精英在了&#xff0…...

wordpress的手机客户端/一键制作免费网站的app

文章目录0 效果1 题目2 思路3 代码0 效果 1 题目 2 思路 简单的数组或向量操作。 3 代码 C: class Solution { public:vector<int> shuffle(vector<int>& nums, int n) {vector<int> c;for (int i 0; i < n; i) {c.push_back(nums[i]);c.push_ba…...

给公司做网站需要华多少钱/我为什么不建议年轻人做运营

一&#xff0c;题目&#xff1a;输入两个整数序列。其中一个序列表示栈的push顺序&#xff0c;判断另一个序列有没有可能是对应的pop顺序。 如果我们希望pop的数字正好是栈顶数字&#xff0c;直接pop出栈即可&#xff1b; 如果希望pop的数字目前不在栈顶&#xff0c;我们就到pu…...

acg二次元wordpress主题/好看的网站ui

还是参考了上篇文章的文档和视频(GEE官方的视频教程)。 一、谐波分析 将回归模型进一步改进,以凸显NDVI的季节性。从下图的等式可以看出,要在最初回归模型的基础上加入cos2Πt和sin2Πt,所要求的系数就从原来的2个变成4个,所以最后得到的结果是4*1的矩阵。 下面这段代码…...

成都哪里有网络营销活动/搜索引擎优化的内容有哪些

两周前&#xff08;202.02.17&#xff09;&#xff0c;vite2.0 发布了&#xff0c;作为使用了浏览器原生 ESM 为下一代前端工具&#xff0c;vite 2.0 相较于 1.0 更加成熟。在此之前笔者就开始关注这类「新型」的前端工具。这次趁着 vite 2.0 发布&#xff0c;也成功将一个基于…...

国内免费发布产品的平台/太原seo排名公司

【小程序开发】常见的内置组件 文章目录【小程序开发】常见的内置组件写在前面一、Text文本组件二、Button按钮组件2.1 type属性2.2 open-type属性三、View视图组件三、Image图片组件3.1 mode属性3.2 wx.chooseMedia四、ScrollView滚动组件五、组件的共同属性写在前面 &#x1…...