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)>>1
    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
}

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 := 0
    sign := -1
    if dividend > 0 && divisor > 0 || dividend < 0 && divisor < 0 {
        sign = 1
    }
    dividendAbs, divisorAbs := abs(dividend), abs(divisor)
    for dividendAbs >= divisorAbs {
        temp := divisorAbs
        multiplier := 1
        for temp<<1 <= dividendAbs {
            temp <<= 1
            multiplier <<= 1
        }
        dividendAbs -= temp
        result += multiplier
    }
    return sign * result
}

Python

class Solution:
    def divide(self, dividend: int, divisor: int) -> int:
        # 递归版
        def recursive_divide(dividend, divisor):
            if dividend < divisor:
                return 0
            quotient = 1
            div = divisor
            while (div + div) <= dividend:
                div += div
                quotient += quotient
            return quotient + recursive_divide(dividend - div, divisor)

        if dividend == 0:
            return 0
        if dividend == -2 ** 31 and divisor == -1:
            return 2 ** 31 - 1

        sign = 1 if (dividend > 0) == (divisor > 0) else -1
        dividend, divisor = abs(dividend), abs(divisor)

        result = recursive_divide(dividend, divisor)
        return sign * result


class Solution:

    # 非递归版
    def divide(self, dividend: int, divisor: int) -> int:
        if dividend == 0:
            return 0
        if dividend == -2 ** 31 and divisor == -1:
            return 2 ** 31 - 1

        sign = 1 if (dividend > 0) == (divisor > 0) else -1
        dividend, divisor = abs(dividend), abs(divisor)
        result = 0

        while dividend >= divisor:
            temp, m = divisor, 1
            while dividend >= (temp << 1):
                temp <<= 1
                m <<= 1
            dividend -= temp
            result += m

        return 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 Counter


class 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 = i
            count = 0
            current_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] += 1
                    count += 1

                    while current_count[word] > word_count[word]:
                        left_word = s[left:left + word_len]
                        current_count[left_word] -= 1
                        count -= 1
                        left += word_len

                    if count == total_words:
                        ans.append(left)
                else:
                    current_count.clear()
                    count = 0
                    left = j + word_len

        return 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,并初始化为 0
    i, j := 0, 0
    // 从倒数第二个元素开始向前遍历整数切片 nums,寻找第一个满足 nums[i] < nums[i+1] 的 i
    for i = len(nums) - 2; i >= 0; i-- {
        if nums[i] < nums[i+1] {
            break
        }
    }
    // 如果找到了 i,表示存在下一个排列
    if i >= 0 {
        // 从最后一个元素开始向前遍历整数切片 nums,寻找第一个满足 nums[j] > nums[i] 的 j
        for 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) - 2
        while 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) - 1
            while 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 i
        left, right = i + 1, len(nums) - 1
        while left < right:
            nums[left], nums[right] = nums[right], nums[left]
            left += 1
            right -= 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 i
        int 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 i
        int 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, 0
    for 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, 0
    for 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 b

        left, right, maxLength = 0, 0, 0

        # 从左向右遍历字符串
        for char in s:
            if char == '(':
                left += 1
            else:
                right += 1

            if left == right:
                maxLength = max(maxLength, 2 * right)
            elif right > left:
                left, right = 0, 0

        left, right = 0, 0

        # 从右向左遍历字符串
        for i in range(len(s) - 1, -1, -1):
            char = s[i]
            if char == '(':
                left += 1
            else:
                right += 1

            if left == right:
                maxLength = max(maxLength, 2 * left)
            elif left > right:
                left, right = 0, 0

        return 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 {
    // 检查数组是否为空,如果是空数组则直接返回-1
    if 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--
            }
        }
    }
    // 如果未找到目标值,则返回-1
    return -1
}

Python

class Solution:
    def search(self, nums, target: int):
        # 如果数组为空,直接返回-1
        if len(nums) == 0:
            return -1
        # 如果数组只有一个元素,分两种情况判断
        elif len(nums) == 1:
            if nums[0] != target:
                return -1
            else:
                return 0
        
        # 找到旋转点的位置
        for i in range(len(nums) - 1):
            if nums[i] > nums[i + 1]:
                flag = i + 1
                break
        
        # 在旋转点左边进行二分查找
        left = self.binary_search(nums, target, 0, flag - 1)
        if left != -1:
            return left
        else:
            # 如果左边没有找到,就在旋转点右边进行二分查找
            right = self.binary_search(nums, target, flag, len(nums) - 1)
            if right == -1:
                return -1
            else:
                return right
    
    def binary_search(self, nums, target, left, right):
        l, r = left, right
        while l <= r:
            mid = l + (r - l) // 2
            if nums[mid] == target:
                return mid
            elif nums[mid] < target:
                l = mid + 1
            else:
                r = mid - 1
        return -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)
    // 初始化左指针为 0
    l_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] = mid
            ans[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) - 1
            res = -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) - 1
            res = -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;
        // 初始化左指针为0
        int 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
        }
    }
    
    // 如果循环结束仍然没有找到合适的位置,说明目标值应该插入到数组的开头,返回0
    return 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 - 1
            else:
                # 如果中间元素小于目标值
                # 检查中间元素的下一个元素是否大于等于目标值,或者是否已经到达数组末尾
                if mid == len(nums) - 1 or nums[mid + 1] >= target:
                    # 如果是,说明目标值应该插入到当前位置的后面,返回当前位置的下一个索引
                    return mid + 1
                # 否则,将low指针移动到中间元素的下一个位置
                low = mid + 1

        # 如果循环结束仍然没有找到合适的位置,说明目标值应该插入到数组的开头,返回0
        return 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;
            }
        }
        
        // 如果循环结束仍然没有找到合适的位置,说明目标值应该插入到数组的开头,返回0
        return 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;
            }
        }
        
        // 如果循环结束仍然没有找到合适的位置,说明目标值应该插入到数组的开头,返回0
        return 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++ 中的类和方法的定义方式。在这个版本中,代码被封装在一个类中。
  • 二分查找算法:理解二分查找算法的工作原理对于理解此代码非常重要。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

friklogff

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值