LeetCode 算法学习笔记-面试篇(上)- 第 013 ~ 025 题(第 05 ~ 08 天)

06.02.01 练习题目(第 05 天)

1. 0033. 搜索旋转排序数组

1.1 题目大意

描述:给定一个整数数组 nums,数组中值互不相同。给定的 nums 是经过升序排列后的又进行了「旋转」操作的。再给定一个整数 target

要求:从 nums 中找到 target 所在位置,如果找到,则返回对应下标,找不到则返回 -1

说明

  • 旋转操作:升序排列的数组 nums 在预先未知的第 k 个位置进行了右移操作,变成了 [nums[k]], nums[k+1], ... , nums[n-1], ... , nums[0], nums[1], ... , nums[k-1]

示例

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4


输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1

1.2 题解: 修改后的二分法 (ChatGPT解释代码)

关键:二分后的左右两侧必有一侧是升序的[left, mid] 或者[mid, right]

搜索旋转排序数组的问题可以使用修改后的二分查找算法来解决。以下是解题思路和代码:

  1. 初始化左右指针,分别指向数组的起始和结束位置:left = 0right = len(nums) - 1

  2. 在循环中,计算中间位置 midmid = (left + right) // 2

  3. 判断中间元素是否等于目标值,若是则直接返回中间位置。

  4. 判断左半部分是否是有序的,即 nums[left] <= nums[mid],如果是的话,检查目标值是否在左半部分,即 nums[left] <= target <= nums[mid],如果是,则将右指针移到 mid - 1 处,否则将左指针移到 mid + 1 处。

  5. 如果左半部分不是有序的,那么右半部分必然是有序的。检查目标值是否在右半部分,即 nums[mid] <= target <= nums[right],如果是,则将左指针移到 mid + 1 处,否则将右指针移到 mid - 1 处。

  6. 不断缩小搜索范围,直到找到目标值或者左指针大于右指针为止。

以下是代码实现:

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums) - 1
        
        while left <= right:
            mid = (left + right) // 2
            
            # 判断中间元素是否等于目标值
            if nums[mid] == target:
                return mid
            
            # 判断左半部分是否是有序的
            if nums[left] <= nums[mid]:
                # 检查目标值是否在左半部分
                if nums[left] <= target <= nums[mid]:
                    right = mid - 1
                else:
                    left = mid + 1
            else:
                # 右半部分必然是有序的
                # 检查目标值是否在右半部分
                if nums[mid] <= target <= nums[right]:
                    left = mid + 1
                else:
                    right = mid - 1
        
        # 循环结束,未找到目标值,返回 -1
        return -1

这个算法的时间复杂度为 O(log n),其中 n 是数组的长度。

2. 0162. 寻找峰值

2.1 题目大意

描述:给定一个整数数组 nums

要求:找到峰值元素并返回其索引。必须实现时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn) 的算法来解决此问题。

说明

  • 峰值元素:指其值严格大于左右相邻值的元素。
  • 数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。
  • 可以假设 n u m s [ − 1 ] = n u m s [ n ] = − ∞ nums[-1] = nums[n] = -∞ nums[1]=nums[n]=
  • 1 ≤ n u m s . l e n g t h ≤ 1000 1 \le nums.length \le 1000 1nums.length1000
  • − 2 31 ≤ n u m s [ i ] ≤ 2 31 − 1 -2^{31} \le nums[i] \le 2^{31} - 1 231nums[i]2311
  • 对于所有有效的 i i i 都有 n u m s [ i ] ! = n u m s [ i + 1 ] nums[i] != nums[i + 1] nums[i]!=nums[i+1]

示例

输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。


输入:nums = [1,2,1,3,5,6,4]
输出:15 
解释:你的函数可以返回索引 1,其峰值元素为 2;或者返回索引 5, 其峰值元素为 6

2.2 题解: 使用二分查找

参考:https://leetcode.cn/problems/find-peak-element/solutions/2570331/python3javacgotypescript-yi-ti-yi-jie-er-9xk5
在这里插入图片描述

2.3 ChatGPT解释代码

这段代码实现了在数组中查找峰值元素的算法。以下是代码的中文解释:

class Solution:
    def findPeakElement(self, nums: List[int]) -> int:
        # 初始化左右指针,分别指向数组的起始和结束位置
        left, right = 0, len(nums) - 1
        
        # 在循环中进行二分查找
        while left < right:
            # 计算中间位置, 等价于mid = (left + right) // 2
            mid = (left + right) >> 1
            
            # 判断中间元素与其右侧元素的大小关系
            if nums[mid] > nums[mid + 1]:
                # 如果中间元素大于右侧元素,说明峰值在左侧,将右指针移到中间位置
                right = mid 
            else:
                # 如果中间元素小于等于右侧元素,说明峰值在右侧,将左指针移到中间位置的右侧
                left = mid + 1
        
        # 循环结束时,左指针指向的位置即为峰值元素的索引
        return left

这个算法利用了二分查找的思想,不断缩小搜索范围,直到找到峰值元素。在每一步中,判断中间元素与其右侧元素的大小关系,根据关系调整左右指针,最终得到峰值元素的索引。

补充
mid = (left + right) >> 1 是使用位运算右移一位来计算中间位置的一种写法。在二分查找中,计算中间位置是通过取左右指针的平均值,而右移一位相当于除以2。这是一种常见的位运算技巧,用于提高代码的执行效率。

mid = (left + right) // 2 相比,这两种写法在结果上是等价的,都能正确计算中间位置。使用位运算右移一位可能在某些情况下更高效,因为计算机底层对于位运算的支持更好。

总的来说,两者在功能上是相同的,选择哪一种写法通常取决于个人或团队的编码风格和习惯。

3. 0004. 寻找两个正序数组的中位数

3.1 题目大意

描述:给定两个正序(从小到大排序)数组 nums1nums2

要求:找出并返回这两个正序数组的中位数。

说明

  • 算法的时间复杂度应该为 O ( log ⁡ ( m + n ) ) O(\log (m + n)) O(log(m+n))
  • n u m s 1. l e n g t h = = m nums1.length == m nums1.length==m
  • n u m s 2. l e n g t h = = n nums2.length == n nums2.length==n
  • 0 ≤ m ≤ 1000 0 \le m \le 1000 0m1000
  • 0 ≤ n ≤ 1000 0 \le n \le 1000 0n1000
  • 1 ≤ m + n ≤ 2000 1 \le m + n \le 2000 1m+n2000
  • − 1 0 6 ≤ n u m s 1 [ i ] , n u m s 2 [ i ] ≤ 1 0 6 -10^6 \le nums1[i], nums2[i] \le 10^6 106nums1[i],nums2[i]106

示例

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

3.2 题解:使用二分查找

参考视频:【LeetCode004-两个有序数组的中位数-最优算法代码讲解】
找了多个视频看,这个讲的比较清晰,容易理解。第k小的方法也有很多视频,但个人感觉k/2的形式不太容易理解(第k小的方法时间复杂度是O(log (m+n)),刚好符合题目要求)。
在这里插入图片描述
Python实现
这种解法的特点是将两个数组分别看做左右两部分后,两个左边看成一个整体,会有一个最大值;两个数组右边看成一个整体,会有一个最小值。也就是提前算了max(x_left, y_left)min(x_right, y_right)

from typing import List

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        # 1. 保证 nums1 是较短的数组
        if len(nums1) > len(nums2):
            return self.findMedianSortedArrays(nums2, nums1)
        
        m, n = len(nums1), len(nums2)
        left, right = 0, m

        # 2. 二分查找
        while left <= right:
            x = (left + right) // 2
            y = (m + n + 1) // 2 - x

            # 3. 检查当前分割点是否满足条件
            if x != 0 and y != n and nums1[x - 1] > nums2[y]:
                right = x - 1
            elif y != 0 and x != m and nums2[y - 1] > nums1[x]:
                left = x + 1
            else:
                # 4. 计算左侧和右侧的最大和最小值
                if x == 0:
                    max_left = nums2[y - 1]
                elif y == 0:
                    max_left = nums1[x - 1]
                else:
                    max_left = max(nums1[x - 1], nums2[y - 1])   

                # 5. 根据数组长度之和的奇偶性返回中位数
                if (m + n) % 2 == 1:
                    return max_left

                if x == m:
                    min_right = nums2[y]
                elif y == n:
                    min_right = nums1[x]
                else:
                    min_right = min(nums1[x], nums2[y])
        
                # 6. 返回最终的中位数
                return (max_left + min_right) / 2

3.3 ChatGPT提供 (另一种解法)

这种方法提前判断了四个边界的情况,并赋值最大最小值的形式,具体解释见代码注释。时间复杂度是O(log min(m, n))

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        # 保证 nums1 是较短的数组
        if len(nums1) > len(nums2):
            nums1, nums2 = nums2, nums1

        m, n = len(nums1), len(nums2)
        # 中位数在合并后的正序数组中是第z个值,奇数偶数都是这个公式,向下取整。
        z = (m + n + 1) // 2 
        # 根据较短的数组做二分查找
        left, right = 0, m

        while left <= right:
            # 在 nums1 中找到一个分割点x
            x = (left + right) // 2
            # 通过分割点x计算在 nums2 中的分割点y
            y = z - x

            # 分别计算两个数组分割点左右两侧的值
            # 考虑nums1的左边界,即x == 0 时,将x_left设为负无穷,这样就一定小于y_right
            x_left = float('-inf') if x == 0 else nums1[x - 1]
            # 考虑nums1的右边界,即x == m 时,将x_right设为正无穷,这样就一定大于y_left
            x_right = float('inf') if x == m else nums1[x]

            # 考虑nums2的左边界,即y == 0 时,将y_left设为负无穷,这样就一定小于x_right
            y_left = float('-inf') if y == 0 else nums2[y - 1]
            # 考虑nums2的右边界,即y == n 时,将y_right设为负无穷,这样就一定大于x_left
            y_right = float('inf') if y == n else nums2[y]

            # 判断是否找到了合适的分割点
            if x_left <= y_right and y_left <= x_right:
                if (m + n) % 2 == 0:
                    # 如果数组长度之和为偶数,取中间两个数的平均值
                    return (max(x_left, y_left) + min(x_right, y_right)) / 2
                else:
                    # 如果数组长度之和为奇数,取较大的那个数
                    return max(x_left, y_left)
            elif x_left > y_right:
                # 如果 x_left 大于 y_right,说明当前分割点过大,需要减小
                right = x - 1
            else:
                # 如果 x_left 小于等于 y_right,说明当前分割点过小,需要增加
                left = x + 1

        # 如果循环结束仍未找到合适的分割点,说明输入数组未按顺序排列
        raise ValueError("Input arrays are not sorted.")

这个算法使用二分查找,通过在较短的数组中找到一个分割点,然后在另一个数组中找到相应的位置,来判断是否找到了中位数的位置。根据分割点左右两侧的值,计算得到中位数。


06.02.02 练习题目(第 06 天)

1. 0240. 搜索二维矩阵 II

1.1 题目大意

描述:给定一个 m × n m \times n m×n 大小的有序整数矩阵 matrixmatrix 中的每行元素从左到右升序排列,每列元素从上到下升序排列。再给定一个目标值 target

要求:判断矩阵中是否可以找到 target,如果可以找到 target,返回 True,否则返回 False

说明

  • m = = m a t r i x . l e n g t h m == matrix.length m==matrix.length
  • n = = m a t r i x [ i ] . l e n g t h n == matrix[i].length n==matrix[i].length
  • 1 ≤ n , m ≤ 300 1 \le n, m \le 300 1n,m300
  • − 1 0 9 ≤ m a t r i x [ i ] [ j ] ≤ 1 0 9 -10^9 \le matrix[i][j] \le 10^9 109matrix[i][j]109
  • 每行的所有元素从左到右升序排列。
  • 每列的所有元素从上到下升序排列。
  • − 1 0 9 ≤ t a r g e t ≤ 1 0 9 -10^9 \le target \le 10^9 109target109

示例

img

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:True

img

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20
输出:False

1.2 题解:转化为图形式(易于理解)

参考240. 搜索二维矩阵 II(贪心,清晰图解)
在这里插入图片描述

这段代码是在一个行列有序的二维矩阵中搜索目标值 target 的位置。具体步骤如下:

  1. 初始化 i 为 0,j 为矩阵的最右边(n-1),即从矩阵的右上角开始。
  2. 进入循环,判断当前位置 matrix[i][j] 与目标值 target 的关系:
    • 如果当前值等于目标值,返回 True,表示找到了目标值。
    • 如果当前值大于目标值,说明目标值可能在当前值的左边,将 j 减一(向左移动)。
    • 如果当前值小于目标值,说明目标值可能在当前值的下边,将 i 加一(向下移动)。
  3. 循环直到找到目标值或者越过矩阵边界。
  4. 如果循环结束,仍未找到目标值,返回 False

这个算法采用了从矩阵的右上角(或左下角)开始的策略,通过对当前元素的比较,逐步缩小搜索范围。这样的时间复杂度是 O(m + n),其中 m 是矩阵的行数,n 是矩阵的列数。

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        m = len(matrix) 
        n = len(matrix[0])

        # i, j = m - 1, 0 # 从矩阵的左下角开始
        # while j < n and i >= 0:
        #     if matrix[i][j] == target:
        #         return True
        #     elif matrix[i][j] > target:
        #         i -= 1 # 向上移动
        #     else:
        #         j += 1 # 向右移动
        # return  False


        i, j = 0, n - 1 # 从矩阵右上角开始
        while i < m and j >= 0: 
            if matrix[i][j] == target:
                return True
            elif matrix[i][j] < target:
                i += 1 # 向下移动
            else:
                j -= 1 # 向左移动
              
        return False
        

2. 0069. x 的平方根

2.1 题目大意

要求:实现 int sqrt(int x) 函数。计算并返回 x 的平方根(只保留整数部分),其中 x 是非负整数。

说明

  • 0 ≤ x ≤ 2 31 − 1 0 \le x \le 2^{31} - 1 0x2311

示例

输入:x = 4
输出:2


输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

2.2 题解:使用二分查找

class Solution:
    def mySqrt(self, x: int) -> int:
        # 初始化搜索范围的左右边界
        left, right = 0, x
        
        # 开始二分查找
        while left <= right:
            # 计算中间值
            mid = (left + right) // 2
            # 判断中间值的平方与目标值的关系
            if mid * mid <= x:
                # 如果中间值的平方小于等于目标值,可能是满足条件的整数平方根
                # 将当前中间值存储在result中,并将搜索范围缩小到mid的右侧
                result = mid   # 关键一步,单独存储结果
                left = mid + 1
            else:
                # 如果中间值的平方大于目标值,将搜索范围缩小到mid的左侧
                right = mid - 1
        
        # 循环结束时,result中存储的是满足条件的整数平方根
        return result

这段代码是一个求解非负整数平方根的二分查找算法,通过注释提供了对每个关键步骤的中文解释。
时间复杂度:
二分查找的时间复杂度是 O(log x),其中 x 是输入的非负整数。因为每一轮都将搜索范围减半,所以时间复杂度是对数级别的。

空间复杂度:
这个算法的空间复杂度是 O(1),因为只使用了有限个变量(left、right、mid、result)来存储中间结果,而不随输入 x 的增加而线性增长。

3. 0283. 移动零

3.1 题目大意

描述:给定一个数组 nums

要求:将所有 0 移动到末尾,并保持原有的非 0 数字的相对顺序。

说明

  • 只能在原数组上进行操作。
  • 1 ≤ n u m s . l e n g t h ≤ 1 0 4 1 \le nums.length \le 10^4 1nums.length104
  • − 2 31 ≤ n u m s [ i ] ≤ 2 31 − 1 -2^{31} \le nums[i] \le 2^{31} - 1 231nums[i]2311

示例

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]


输入: nums = [0]
输出: [0]

3.2 题解:使用双指针

参考图解:
https://leetcode.cn/problems/move-zeroes/solutions/494449/shuang-zhi-zhen-xiang-xi-tu-jie-by-xueliangwang

这是一个将数组中的零移动到末尾的函数。下面是中文解释:

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        不返回任何值,直接在原数组上进行修改。

        参数:
        nums -- 整数数组

        """
        j = 0  # j指针用来指示第一个0所在的位置
        for i in range(len(nums)):  # i指针用来遍历数组
            if nums[i] != 0:
                # 当发现非零元素时,将其交换到数组的开头(通过与nums[j]交换位置)
                nums[j], nums[i] = nums[i], nums[j]
                j += 1

该函数通过使用两个指针 ij,其中 i 用于遍历数组,j 用于指示当前数组中第一个零的位置。当发现非零元素时,通过与 nums[j] 交换位置,将非零元素置于数组的开头。这样,遍历完成后,所有的零都会被移动到数组的末尾。


06.02.05 练习题目(第 07 天)

1. 0415. 字符串相加

1.1 题目大意

描述:给定两个字符串形式的非负整数 num1num2

要求:计算它们的和,并同样以字符串形式返回。

说明

  • 1 ≤ n u m 1. l e n g t h , n u m 2. l e n g t h ≤ 1 0 4 1 \le num1.length, num2.length \le 10^4 1num1.length,num2.length104
  • n u m 1 num1 num1 n u m 2 num2 num2 都只包含数字 0 ∼ 9 0 \sim 9 09
  • n u m 1 num1 num1 n u m 2 num2 num2 都不包含任何前导零。
  • 你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式。

示例

输入:num1 = "11", num2 = "123"
输出:"134"


输入:num1 = "456", num2 = "77"
输出:"533"

1.2 题解:双指针,清晰图解

参考https://leetcode.cn/problems/add-strings/solutions/1/add-strings-shuang-zhi-zhen-fa-by-jyd

class Solution:
    def addStrings(self, num1: str, num2: str) -> str:
        """
        返回两个字符串形式的数字的和。

        参数:
        num1 -- 第一个字符串形式的数字
        num2 -- 第二个字符串形式的数字

        返回:
        str -- 两个字符串形式数字的和
        """
        i, j = len(num1) - 1, len(num2) - 1
        res = ''  # 存储结果的字符串
        carry = 0  # 进位标志

        while i >= 0 or j >= 0:
            # 取当前位置上的数字,如果超过字符串长度则为0
            m = int(num1[i]) if i >= 0 else 0
            n = int(num2[j]) if j >= 0 else 0

            # 将当前位置的数字与进位相加
            tmp = m + n + carry
            carry = tmp // 10  # 计算进位
            res = str(tmp % 10) + res  # 将当前位置的结果添加到res字符串的开头
            i, j = i - 1, j - 1

        # 处理最高位的进位
        return '1' + res if carry else res

2. 0239. 滑动窗口最大值

2.1 题目大意

描述:给定一个整数数组 nums,再给定一个整数 k,表示为大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。我们只能看到滑动窗口内的 k 个数字,滑动窗口每次只能向右移动一位。

要求:返回滑动窗口中的最大值。

说明

  • 1 ≤ n u m s . l e n g t h ≤ 1 0 5 1 \le nums.length \le 10^5 1nums.length105
  • − 1 0 4 ≤ n u m s [ i ] ≤ 1 0 4 -10^4 \le nums[i] \le 10^4 104nums[i]104
  • 1 ≤ k ≤ n u m s . l e n g t h 1 \le k \le nums.length 1knums.length

示例

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

 
输入:nums = [1], k = 1
输出:[1]

2.2 题解(双端队列-deque)

数据结构–队列是什么?
动画题解ACM 选手图解 LeetCode 滑动窗口最大值 | 编程文青李狗蛋

python代码实现

from collections import deque


class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        # 如果数组为空
        if not nums:
            return []
        
        # 初始化队列和结果,队列存储数组的下标
        res = []
        dq= deque()

        for i in range(len(nums)):
            # 如果当前队列最左侧存储的下标等于 i-k 的值,代表目前队列已满。
            # 但是新元素需要进来,所以列表最左侧的下标出队列
            if dq and dq[0] < i - k + 1:
                dq.popleft()
            
            # 对于新进入的元素,如果队列前面的数比它小,那么前面的都出队列
            while dq and nums[dq[-1]] < nums[i]:
                dq.pop() 
            # 新元素入队列
            dq.append(i)

            # 当前的大值加入到结果数组中,从窗口中的数据个数达到k个时开始添加
            if i >= k - 1 :
                res.append(nums[dq[0]])
        
        return res

思路
使用一个双端队列(deque)来保存当前窗口中可能的最大值的索引。双端队列的特点是我们可以从两端进行添加和删除操作。

解释

  1. 初始化

    • deq 是一个双端队列,用于存储当前窗口中元素的索引。
    • result 是一个列表,用于存储每个窗口的最大值。
  2. 遍历数组

    • 对于每个元素 nums[i],我们执行以下操作:
    1. 移除不在当前窗口的元素

      • 检查双端队列的第一个元素是否在当前窗口范围内。如果不在,则移除。
    2. 移除所有小于当前元素的元素

      • 从双端队列的末尾开始,移除所有小于当前元素 nums[i] 的元素,因为它们不可能在当前或未来窗口中成为最大值。
    3. 将当前元素的索引添加到双端队列

      • 将当前元素的索引 i 添加到双端队列的末尾。
    4. 记录当前窗口的最大值

      • 当索引 i 大于等于 k - 1 时,表示窗口已经形成,此时双端队列的第一个元素就是当前窗口的最大值,将其添加到结果列表中。

这样,我们就能够高效地找到每个滑动窗口的最大值。整个算法的时间复杂度是 (O(n)),其中 (n) 是数组的长度。

3. 0003. 无重复字符的最长子串

3.1 题目大意

描述:给定一个字符串 s

要求:找出其中不含有重复字符的最长子串的长度。

说明

  • 0 ≤ s . l e n g t h ≤ 5 ∗ 1 0 4 0 \le s.length \le 5 * 10^4 0s.length5104
  • s 由英文字母、数字、符号和空格组成。

示例

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。


输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1

这是一道经典的滑动窗口问题。下面是详细的解决方法和步骤。

3.2 题解(双指针)

使用滑动窗口技术来解决这个问题。滑动窗口是数组/字符串问题中常用的抽象概念。窗口是数组/字符串中的一部分,可以被定义为两个指针的范围。

详细步骤

  1. 初始化:使用两个指针 leftright 来表示当前窗口的左右边界,left 初始化为 0,right 从 0 开始遍历字符串。
  2. 哈希集合:使用一个哈希集合 chars 来存储窗口内的字符,用于检测重复字符。
  3. 滑动窗口
    • 如果 s[right] 不在 chars 中,表示当前字符不重复,加入集合中,并更新窗口的最大长度。
    • 如果 s[right]chars 中,表示当前字符重复,从 left 开始移动左指针,直到 s[right] 不在 chars 中为止,同时移除相应的字符。
  4. 更新窗口长度:每次更新窗口的最大长度为 right - left + 1

代码实现

def lengthOfLongestSubstring(s: str) -> int:
    char_set = set()
    left = 0
    max_length = 0
    
    for right in range(len(s)):
        while s[right] in char_set:
            char_set.remove(s[left])
            left += 1
        char_set.add(s[right])
        max_length = max(max_length, right - left + 1)
    
    return max_length

# 示例
print(lengthOfLongestSubstring("abcabcbb"))  # 输出: 3
print(lengthOfLongestSubstring("bbbbb"))     # 输出: 1
print(lengthOfLongestSubstring("pwwkew"))    # 输出: 3

解释

  1. 初始化

    • char_set 是一个集合,用于存储当前窗口内的字符。
    • left 是左指针,初始化为 0。
    • max_length 用于记录最长无重复子串的长度,初始化为 0。
  2. 遍历字符串

    • 使用右指针 right 从 0 开始遍历字符串。
    • 如果 s[right] 不在 char_set 中,将其加入集合,更新 max_length 为当前窗口的长度(right - left + 1)。
    • 如果 s[right]char_set 中,表示有重复字符,则从 left 开始移动左指针,直到 s[right] 不在 char_set 中为止,同时移除相应的字符。
  3. 更新最大长度

    • 在每次遍历右指针时,更新 max_length 为当前窗口的长度。

这样,我们就能够找到字符串中无重复字符的最长子串的长度。滑动窗口技术保证了我们能够高效地找到解法,时间复杂度为 O(n),其中 n 是字符串的长度。

06.02.04 练习题目(第 08 天)

1. 0076. 最小覆盖子串

1.1 题目大意

描述:给定一个字符串 s、一个字符串 t

要求:返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

说明

  • 1 ≤ s . l e n g t h , t . l e n g t h ≤ 1 0 5 1 \le s.length, t.length \le 10^5 1s.length,t.length105
  • st 由英文字母组成。

示例

  • 示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
  • 示例 2:
输入:s = "a", t = "a"
输出:"a"

2. 0718. 最长重复子数组

2.1 题目大意

描述:给定两个整数数组 nums1nums2

要求:计算两个数组中公共的、长度最长的子数组长度。

说明

  • 1 ≤ n u m s 1. l e n g t h , n u m s 2. l e n g t h ≤ 1000 1 \le nums1.length, nums2.length \le 1000 1nums1.length,nums2.length1000
  • 0 ≤ n u m s 1 [ i ] , n u m s 2 [ i ] ≤ 100 0 \le nums1[i], nums2[i] \le 100 0nums1[i],nums2[i]100

示例

  • 示例 1:
输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
输出:3
解释:长度最长的公共子数组是 [3,2,1]
  • 示例 2:
输入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0]
输出:5

3. 0083. 删除排序链表中的重复元素

3.1 题目大意

描述:给定一个已排序的链表的头 head

要求:删除所有重复的元素,使每个元素只出现一次。返回已排序的链表。

说明

  • 链表中节点数目在范围 [ 0 , 300 ] [0, 300] [0,300] 内。
  • − 100 ≤ N o d e . v a l ≤ 100 -100 \le Node.val \le 100 100Node.val100
  • 题目数据保证链表已经按升序排列。

示例

  • 示例 1:
输入:head = [1,1,2,3,3]
输出:[1,2,3]

4. 0082. 删除排序链表中的重复元素 II

4.1 题目大意

描述:给定一个已排序的链表的头 head

要求:删除原始链表中所有重复数字的节点,只留下不同的数字。返回已排序的链表。

说明

  • 链表中节点数目在范围 [ 0 , 300 ] [0, 300] [0,300] 内。
  • − 100 ≤ N o d e . v a l ≤ 100 -100 \le Node.val \le 100 100Node.val100
  • 题目数据保证链表已经按升序排列。

示例

  • 示例 1:
输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]
  • 21
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值