长度最小的子数组

标签: 数组 二分查找 前缀和 滑动窗口

难度: Medium

给定一个含有 n 个正整数的数组和一个正整数 target

找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度如果不存在符合条件的子数组,返回 0

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:

输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

提示:

  • 1 <= target <= 109
  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 105

进阶:

  • 如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。

Submission

运行时间: 29 ms

内存: 26.2 MB

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        """双指针滑动窗口"""
        left, sums = 0, 0  # 初始化 左指针、总和
        res = float('inf')  # 把连续子数组的长度初始化为 无穷大

        # 右指针右移
        for right in range(len(nums)):
            sums += nums[right]  # 从左到右,把元素累加起来
            while sums >= target:  # 直到总和 大于等于 target
                # 若 子数组的长度 新值小于旧值,更新res
                if right - left + 1 < res:
                    res = right - left + 1  # 连续子数组的长度
                
                sums -= nums[left]  # 总和减去左指针
                left += 1  # 左指针向右滑动
        return 0 if res == float('inf') else res

Explain

该题解使用双指针滑动窗口的思路来解决问题。通过左右两个指针构成一个窗口,右指针不断向右移动扩大窗口,累加窗口内的元素总和,当总和大于等于目标值时,开始收缩窗口,移动左指针缩小窗口,并更新最小子数组长度,直到窗口内的总和小于目标值,然后继续扩大窗口,重复上述过程,直到右指针到达数组末尾。最终返回找到的最小子数组长度。

时间复杂度: O(n)

空间复杂度: O(1)

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        """
        双指针滑动窗口
        """
        left, sums = 0, 0  # 初始化左指针和总和
        res = float('inf')  # 初始化最小子数组长度为无穷大

        # 右指针向右移动
        for right in range(len(nums)):
            sums += nums[right]  # 累加右指针指向的元素
            while sums >= target:  # 当总和大于等于目标值时
                # 更新最小子数组长度
                if right - left + 1 < res:
                    res = right - left + 1
                
                sums -= nums[left]  # 减去左指针指向的元素
                left += 1  # 左指针向右移动
        
        # 如果最小子数组长度无穷大,说明不存在符合条件的子数组,返回 0;否则返回最小子数组长度
        return 0 if res == float('inf') else res

Explore

当累积总和大于等于目标值时,移动左指针来缩小窗口的目的是为了寻找可能存在的更小长度的符合条件的子数组。这是因为当前的窗口已经满足了条件,进一步缩小这个窗口可能会找到一个更短的满足条件的子数组,从而更新最小子数组的长度。此外,这也有助于在总和超过目标值很多的情况下,去除不必要的部分,使窗口的长度尽可能地小。

此算法的主要目的是找到满足条件的最小长度的子数组,而不是找出所有可能的满足条件的子数组。虽然算法会遍历并检查每个可能的窗口,但它只记录最小长度。因此,如果存在多个长度相同的符合条件的子数组,该算法不保证会记录所有这些子数组,而只保证会找到至少一个这样的子数组。

在算法开始时,最小子数组长度被初始化为无穷大(使用float('inf'))。这是一个标记值,用来表示尚未找到任何满足条件的子数组。如果在遍历整个数组后,这个值仍然是无穷大,意味着没有任何子数组的总和达到或超过目标值。在这种情况下,返回0表示不存在符合条件的子数组。

使用`float('inf')`在大多数现代编程环境中是支持的,它提供了一种便捷的方式来表示无穷大,使得任何实际长度的子数组都会小于这个值,从而可以在找到第一个满足条件的子数组时立即更新这个最小长度。虽然在极少数环境中可能不支持这种表示,但这种情况非常罕见。在这些环境中,可以考虑使用一个足够大的数(如数组长度加一)来代替无穷大。