Swift 数据结构与算法(15 ) 数组 + M_Leetcode209长度最小的子数组(滑动窗口)

90 阅读4分钟

错误注意

注意审题, >= target , 不是等于 target

理解 while 循环的逻辑while 循环会一直执行,只要它后面的条件是真的。你可以想象成 while 循环是一个不断重复的问答:条件是真的吗?是的话,就执行循环体;否的话,就跳出循环。理解这一点非常重要。

个人错误修改:

return minTime : 如果在遍历完整个数组后,还没有找到满足条件的子数组(即 minTime 仍然是 Int.max),应该返回 0,而不是 Int.max,因为题目要求在不存在符合条件的子数组时返回 0

return minTime == Int.max ? 0 : minTime

// 移动窗口左边的值 left += 1

注意 += 的顺序.

概念

滑动窗口(Sliding Window)是一种处理数组/链表问题的常用技巧,尤其是处理子数组或子序列问题时,例如求最长连续子序列,最短连续子序列,满足某种条件的子序列等问题。

滑动窗口,顾名思义,就像是在数组或链表上滑动的窗口,通过改变窗口的位置和大小,来处理和解决问题。滑动窗口通常由两个变量维护,一个是窗口的左端(通常用 leftstart 表示),一个是窗口的右端(通常用 rightend 表示)。在遍历过程中,通过改变这两个变量的值,实现窗口的滑动。

滑动窗口的优点在于,当窗口滑动时,不需要每次都去计算窗口中所有元素的结果,而是通过之前的计算结果,快速得到新窗口的计算结果。这样就大大降低了计算复杂度,从而提高了算法的效率。

题目

给定一个含有 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
class Solution {
    func minSubArrayLen(_ target: Int, _ nums: [Int]) -> Int {

    }
}

解题思路🙋🏻‍ ♀️

输入数组 `nums = [2,3,1,2,4,3]` 和目标值 `target = 7`,将使用这个输入来步骤化解释上面的 Swift 代码。

初始状态:

nums = [2,3,1,2,4,3] target = 7 minTime = Int.max left = 0 right = 0 sum = 0


Step 1:

累加 `nums[right]``sum`,然后 `right` 右移一位:

sum = 0 + 2 = 2 right = 0 + 1 = 1


现在的状态:

nums = [2,3,1,2,4,3] target = 7 minTime = Int.max left = 0 right = 1 sum = 2


由于 `sum < target`,跳过内部的 `while` 循环,回到外部的 `while` 循环。

Step 2:

继续累加 `nums[right]``sum`,然后 `right` 右移一位:

sum = 2 + 3 = 5 right = 1 + 1 = 2


现在的状态:

nums = [2,3,1,2,4,3] target = 7 minTime = Int.max left = 0 right = 2 sum = 5


同样,由于 `sum < target`,继续外部的 `while` 循环。

Step 3:

继续累加 `nums[right]``sum`,然后 `right` 右移一位:

sum = 5 + 1 = 6 right = 2 + 1 = 3


现在的状态:

nums = [2,3,1,2,4,3] target = 7 minTime = Int.max left = 0 right = 3 sum = 6


同样,由于 `sum < target`,继续外部的 `while` 循环。

Step 4:

继续累加 `nums[right]``sum`,然后 `right` 右移一位:

sum = 6 + 2 = 8 right = 3 + 1 = 4


现在的状态:

nums = [2,3,1,2,4,3] target = 7 minTime = Int.max left = 0 right = 4 sum = 8


由于 `sum >= target`,进入内部的 `while` 循环。

Step 5:

在内部的 `while` 循环中,因为 `sum >= target`,更新 `minTime`

minTime = min(Int.max, 4 - 0) = 4


然后,从 `sum` 中减去 `nums[left]`,并将 `left` 右移一位:

sum = 8 - 2 = 6 left = 0 + 1 = 1


现在的状态:

nums = [2,3,1,2,4,3] target = 7 minTime = 4 left = 1 right = 4 sum = 6


由于 `sum < target`,跳出内部的 `while` 循环,回到外部的 `while` 循环。

接下来的步骤类似,直到 `right` 超出数组的范围。在这个过程中,我们一直保持 `sum >= target`,并试图通过移动 `left` 指针来找到最小的子数组。

最后,如果 `minTime` 仍然是 `Int.max`,说明没有找到满足条件的子数组,返回 `0`;否则返回 `minTime`。在这个例子中,找到了和为 `7` 的最小子数组 `[4,3]`,长度为 `2`,所以返回 `2`

边界思考🤔

错误的部分.

代码

import Foundation

class Solution {
    func minSubArrayLen(_ target: Int, _ nums: [Int]) -> Int {
        // 初始化最小子数组长度为最大整数
        var minTime = Int.max
        
        // 初始化左右指针和子数组之和
        var left = 0
        var right = 0
        var sum = 0
        
        // 当右指针没有超出数组范围时,继续执行循环
        while right < nums.count {
            // 累加右指针指向的元素到子数组之和
            sum += nums[right]
            // 右指针向右移动一位
            right += 1
            
            // 当子数组之和大于等于目标值时,继续执行循环
            while sum >= target {
                // 如果当前子数组长度小于已找到的最小长度,更新最小长度
                if right - left < minTime {
                    minTime = right - left
                }
                // 从子数组之和中减去左指针指向的元素
                sum -= nums[left]
                // 左指针向右移动一位,即缩小子数组的长度
                left += 1
            }
        }
        // 如果最小长度仍然是最大整数,说明没有找到满足条件的子数组,返回 0
        // 否则,返回找到的最小子数组长度
        return minTime == Int.max ? 0 : minTime
    }
}

时空复杂度分析

O (n) 这个算法的时间复杂度是 O(n),空间复杂度是 O(1)。

时间复杂度是 O(n)的原因是,虽然我们在代码中看到两个嵌套的while 循环,但实际上每个元素最多只被访问两次。外部的while 循环是根据右指针 right进行的,“right 从O遍历到nums.count ;内部的while 循环是根据左指针“left 进行的,left也是从0遍历到 nums.count 。因此,总的时间复杂度是 O(n)。

空间复杂度是 O(1)的原因是,只使用了几个固定的变量,没有使用额外的数据结构来存储数据,所以空间复杂度是常数级别的。