209. 长度最小的子数组(二分查找、双指针、Python)

学习二分查找和双指针的应用

题目描述

方法一:前缀和 + 二分查找

使用二分查找,则可以将时间优化到 O ( log ⁡ n ) O(\log n) O(logn)

为了使用二分查找,需要额外创建一个数组 sums \text{sums} sums 用于存储数组 nums \text{nums} nums 的前缀和,其中 sums [ i ] \text{sums}[i] sums[i] 表示从 nums [ 0 ] \text{nums}[0] nums[0] nums [ i − 1 ] \text{nums}[i-1] nums[i1] 的元素和。得到前缀和之后,对于每个开始下标 i i i,可通过二分查找得到大于或等于 i i i 的最小下标 bound \textit{bound} bound,使得 sums [ bound ] − sums [ i ] ≥ s \text{sums}[\textit{bound}]-\text{sums}[i] \ge s sums[bound]sums[i]s,并更新子数组的最小长度(此时子数组的长度是 bound − i \textit{bound}-i boundi)。

因为这道题保证了数组中每个元素都为正,所以前缀和一定是递增的,这一点保证了二分的正确性。如果题目没有说明数组中每个元素都为正,这里就不能使用二分来查找这个位置了。

在很多语言中,都有现成的库和函数来为我们实现这里二分查找大于等于某个数的第一个位置的功能,比如 C++ 的 lower_bound,Java 中的 Arrays.binarySearch,C# 中的 Array.BinarySearch,Python 中的 bisect.bisect_left

import bisect
class Solution:
    def minSubArrayLen(self, s: int, nums: List[int]) -> int:
        if not nums:
            return 0
        n = len(nums)
        minLen = n + 1
        # 累加数组
        sums = [0]
        for i in range(n):
            sums.append(sums[-1] + nums[i])

        for i in range(n):
            target = s + sums[i]
            bound = bisect.bisect_left(sums, target)
            if bound != len(sums):
                minLen = min(minLen, bound - i)
        return minLen if minLen != n + 1 else 0

在这里插入图片描述
复杂度分析

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n n n 是数组的长度。需要遍历每个下标作为子数组的开始下标,遍历的时间复杂度是 O ( n ) O(n) O(n),对于每个开始下标,需要通过二分查找得到长度最小的子数组,二分查找得时间复杂度是 O ( log ⁡ n ) O(\log n) O(logn),因此总时间复杂度是 O ( n log ⁡ n ) O(n \log n) O(nlogn)
  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组的长度。额外创建数组 sums \text{sums} sums 存储前缀和。

方法二:双指针

定义两个指针 start \textit{start} start end \textit{end} end 分别表示子数组的开始位置和结束位置,维护变量 sum \textit{sum} sum 存储子数组中的元素和(即从 nums [ start ] \text{nums}[\textit{start}] nums[start] nums [ end ] \text{nums}[\textit{end}] nums[end] 的元素和)。

初始状态下, start \textit{start} start end \textit{end} end 都指向下标 0 0 0 sum \textit{sum} sum 的值为 00。

每一轮迭代,将 nums [ e n d ] \text{nums}[end] nums[end] 加到 sum \textit{sum} sum,如果 sum ≥ s \textit{sum} \ge s sums,则更新子数组的最小长度(此时子数组的长度是 end − start + 1 \textit{end}-\textit{start}+1 endstart+1),然后将 nums [ s t a r t ] \text{nums}[start] nums[start] sum \textit{sum} sum 中减去并将 start \textit{start} start 右移,直到 sum < s \textit{sum} < s sum<s,在此过程中同样更新子数组的最小长度。在每一轮迭代的最后,将 end \textit{end} end 右移。

自己写的代码,运行效率不高,可能是由于多次切片求和操作

class Solution:
    def minSubArrayLen(self, s: int, nums: List[int]) -> int:
        left = 0
        right = 1
        n = len(nums)
        minLen = 2**31
        while (left < right) and (left < n) and (right <= n):
            cur_sum = sum(nums[left:right])
            if cur_sum >= s:
                if right - left < minLen:
                    minLen = right - left
                left += 1
            else:
                right += 1
        return minLen if minLen != 2**31 else 0

在这里插入图片描述
官方代码

class Solution:
    def minSubArrayLen(self, s: int, nums: List[int]) -> int:
        left = 0
        right = 0
        n = len(nums)
        minLen = n + 1
        cur_sum = 0
        while right < n:
            cur_sum += nums[right]
            while cur_sum >= s:
                minLen = min(minLen, right - left + 1)
                cur_sum -= nums[left]
                left += 1
            right += 1
        return minLen if minLen != n + 1 else 0

在这里插入图片描述
复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组的长度。指针 start \textit{start} start end \textit{end} end 最多各移动 n n n 次。
  • 空间复杂度: O ( 1 ) O(1) O(1)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值