跳转至

滑动窗口

滑动窗口指的是这样一类问题的求解方法,在数组上通过双指针同向移动而解决的一类问题。其实这样的问题我们可以不必为它们专门命名一个名字,它们的解法其实是很自然的。

使用滑动窗口解决的问题通常是暴力解法的优化,掌握这一类问题最好的办法就是练习,然后思考清楚为什么可以使用滑动窗口。

应用场景:字符串/数组/子序列

滑动窗口使用思路(寻找最长)

  • 核心:左右双指针(L,R)在起始点,R 向右逐位滑动循环

每次滑动过程中如果窗内元素满足条件,R 向右扩大窗口,并更新最优结果;如果不满足条件,L 向右缩小窗口,直到 R 到达结尾

滑动窗口使用思路(寻找最短)

  • 核心:左右双指针(L,R)在起始点,R 向右逐位滑动循环

每次滑动过程中如果窗内元素满足条件,L 向右扩大窗口,并更新最优结果;如果不满足条件,R 向右缩小窗口,直到 R 到达结尾

模板代码

// 最长模板:
初始化 left, right, result, bestResult
while (右指针没有到结尾) {
    窗口扩大加入 right 对应元素更新当前 result
    while (result 不满足要求) {
        窗口缩小移除 left 对应元素 left 右移
    }
    更新最优结果 bestResult
    right++;
}
return bestResult;
// 最短模板:
初始化 left, right, result, bestResult
while (右指针没有到结尾) {
    窗口扩大加入 right 对应元素更新当前 result
    while (result 满足要求) {
        更新最优结果 bestResult
        窗口缩小移除 left 对应元素 left 右移
    }
    right++;
}
return bestResult;

例题(leetcode)

209 长度最小的子数组

给定一个含有 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)) 时间复杂度的解法。
func minSubArrayLen(target int, nums []int) int {
    var left, right, sum, minLength int
    for right < len(nums) {
        sum += nums[right]
        for sum >= target {
            if right - left + 1 < minLength || minLength == 0 {
                minLength = right - left + 1
            }
            sum -= nums[left]
            left++
        }
        right++
    }
    return minLength
}

219 存在重复元素 Ⅱ

给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 i 和 j ,满足 nums[i] == nums[j]abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false

 

示例 1:

输入:nums = [1,2,3,1], k = 3
输出:true

示例 2:

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

示例 3:

输入:nums = [1,2,3,1,2,3], k = 2
输出:false

 

 

提示:

  • 1 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • 0 <= k <= 105
func containsNearbyDuplicate(nums []int, k int) bool {
    var set map[int]bool
    set = make(map[int]bool)
    for i, num := range nums {
        if i > k {
            delete(set, nums[i - k - 1])
        }

        if set[num] {
            return true
        }
        set[num] = true
    }
    return false
}

643 子数组最大平均数 Ⅰ

给你一个由 n 个元素组成的整数数组 nums 和一个整数 k

请你找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数。

任何误差小于 10-5 的答案都将被视为正确答案。

 

示例 1:

输入:nums = [1,12,-5,-6,50,3], k = 4
输出:12.75
解释:最大平均数 (12-5-6+50)/4 = 51/4 = 12.75

示例 2:

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

 

提示:

  • n == nums.length
  • 1 <= k <= n <= 105
  • -104 <= nums[i] <= 104
func findMaxAverage(nums []int, k int) float64 {
    left := 0
    right := 0
    sum := 0
    var result float64

    for right < len(nums) {
        sum += nums[right]
        // 窗口大小超过 k 移除 left 元素并左移 left
        for right - left + 1 > k {
            sum -= nums[left]
            left++
        }
        // 更新最优结果
        if right - left + 1 == k {
            if result == 0 {
                result = float64(sum) / float64(right - left + 1)
            }
            if float64(sum) / float64(right - left + 1) > result {
                result = float64(sum) / float64(right - left + 1)
            }
        }
        right++
    }
    return result
}

1876 长度为三且个字符不同的子字符串

如果一个字符串不含有任何重复字符,我们称这个字符串为  字符串。

给你一个字符串 s ,请你返回 s 中长度为 3 的 好子字符串 的数量。

注意,如果相同的好子字符串出现多次,每一次都应该被记入答案之中。

子字符串 是一个字符串中连续的字符序列。

 

示例 1:

输入:s = "xyzzaz"
输出:1
解释:总共有 4 个长度为 3 的子字符串:"xyz","yzz","zza" 和 "zaz" 。
唯一的长度为 3 的好子字符串是 "xyz" 。

示例 2:

输入:s = "aababcabc"
输出:4
解释:总共有 7 个长度为 3 的子字符串:"aab","aba","bab","abc","bca","cab" 和 "abc" 。
好子字符串包括 "abc","bca","cab" 和 "abc" 。

 

提示:

  • 1 <= s.length <= 100
  • s 只包含小写英文字母。
func countGoodSubstrings(s string) int {
    var (
        left int = 0
        right int = 2
        result int = 0
    )

    if len(s) < 3 {
        return result
    }

    for right < len(s) {
        if isNice(s[left:right + 1]) {
            result++
        }
        left++
        right++
    }
    return result
}

func isNice(s string) bool {
    set := map[rune]bool{}
    for _, value := range s[:] {
        if set[value] {
            return false
        }
        set[value] = true
    }
    return true
}

1984 学生分数的最小差值

给你一个 下标从 0 开始 的整数数组 nums ,其中 nums[i] 表示第 i 名学生的分数。另给你一个整数 k

从数组中选出任意 k 名学生的分数,使这 k 个分数间 最高分最低分差值 达到 最小化

返回可能的 最小差值

 

示例 1:

输入:nums = [90], k = 1
输出:0
解释:选出 1 名学生的分数,仅有 1 种方法:
- [90] 最高分和最低分之间的差值是 90 - 90 = 0
可能的最小差值是 0

示例 2:

输入:nums = [9,4,1,7], k = 2
输出:2
解释:选出 2 名学生的分数,有 6 种方法:
- [9,4,1,7] 最高分和最低分之间的差值是 9 - 4 = 5
- [9,4,1,7] 最高分和最低分之间的差值是 9 - 1 = 8
- [9,4,1,7] 最高分和最低分之间的差值是 9 - 7 = 2
- [9,4,1,7] 最高分和最低分之间的差值是 4 - 1 = 3
- [9,4,1,7] 最高分和最低分之间的差值是 7 - 4 = 3
- [9,4,1,7] 最高分和最低分之间的差值是 7 - 1 = 6
可能的最小差值是 2

 

提示:

  • 1 <= k <= nums.length <= 1000
  • 0 <= nums[i] <= 105
func minimumDifference(nums []int, k int) int {
    min := 0
    temp := 0
    sort.Ints(nums)
    for i := k - 1; i < len(nums); i++ {
        temp = nums[i] - nums[i - k + 1]
        if min == 0 ||  temp < min {
            min = temp
        }
    }
    return min
}

2269 找到一个数字的 K 美丽值

一个整数 num 的 美丽值定义为 num 中符合以下条件的 子字符串 数目:

  • 子字符串长度为 k 。
  • 子字符串能整除 num

给你整数 num 和 k ,请你返回 num 的 k 美丽值。

注意:

  • 允许有 前缀 0 。
  • 0 不能整除任何值。

一个 子字符串 是一个字符串里的连续一段字符序列。

 

示例 1:

输入:num = 240, k = 2
输出:2
解释:以下是 num 里长度为 k 的子字符串:
- "240" 中的 "24" :24 能整除 240 。
- "240" 中的 "40" :40 能整除 240 。
所以,k 美丽值为 2 。

示例 2:

输入:num = 430043, k = 2
输出:2
解释:以下是 num 里长度为 k 的子字符串:
- "430043" 中的 "43" :43 能整除 430043 。
- "430043" 中的 "30" :30 不能整除 430043 。
- "430043" 中的 "00" :0 不能整除 430043 。
- "430043" 中的 "04" :4 不能整除 430043 。
- "430043" 中的 "43" :43 能整除 430043 。
所以,k 美丽值为 2 。

 

提示:

  • 1 <= num <= 109
  • 1 <= k <= num.length (将 num 视为字符串)
func divisorSubstrings(num int, k int) int {
    result := 0
    s := strconv.Itoa(num)
    for i := k - 1; i < len(s); i++ {
        i2, _ := strconv.Atoi(s[i-k+1 : i+1])
        if i2 != 0 && num % i2 == 0 {
            result++
        }
    }
    return result
}

22379 得到 k 个黑块的最少涂色次数

给你一个长度为 n 下标从 0 开始的字符串 blocks ,blocks[i] 要么是 'W' 要么是 'B' ,表示第 i 块的颜色。字符 'W' 和 'B' 分别表示白色和黑色。

给你一个整数 k ,表示想要 连续 黑色块的数目。

每一次操作中,你可以选择一个白色块将它 涂成 黑色块。

请你返回至少出现 一次 连续 k 个黑色块的 最少 操作次数。

 

示例 1:

输入:blocks = "WBBWWBBWBW", k = 7
输出:3
解释:
一种得到 7 个连续黑色块的方法是把第 0 ,3 和 4 个块涂成黑色。
得到 blocks = "BBBBBBBWBW" 。
可以证明无法用少于 3 次操作得到 7 个连续的黑块。
所以我们返回 3 。

示例 2:

输入:blocks = "WBWBBBW", k = 2
输出:0
解释:
不需要任何操作,因为已经有 2 个连续的黑块。
所以我们返回 0 。

 

提示:

  • n == blocks.length
  • 1 <= n <= 100
  • blocks[i] 要么是 'W' ,要么是 'B'
  • 1 <= k <= n
func minimumRecolors(blocks string, k int) int {
    var left, right, max, pl, pr, result int
    for right < len(blocks) {
        // 记录窗口中黑块数量
        if blocks[right] == 'B' {
            max++
        }
        if right - left + 1 == k {
            // 如果满足条件,记录窗口坐标
            if result == 0 || result < max {
                result = max
                pl = left
                pr = right
            }

            // 判断 left 移动前是否为 'B',因为下一次移动会减去窗口左边的黑块
            if blocks[left] == 'B' {
                max--
            }
            left++
        }

        // 窗口滑动
        right++
    }
    return pr - pl + 1 - result
}

3 无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

 

示例 1:

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

示例 2:

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

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

 

提示:

  • 0 <= s.length <= 5 * 104
  • s 由英文字母、数字、符号和空格组成
func lengthOfLongestSubstring(s string) int {
    if len(s) == 1 {
        return 1
    }

    var left, right, result int
    for right < len(s) {
        if left < right {
            // 判断是否满足条件
            for i := right - 1; i >= left; i-- {
                if s[right] == s[i] {
                    left = i + 1
                    break
                }
            }
            // 更新最新结果
            if right-left+1 > result {
                result = right - left + 1
            }
        }
        // 窗口扩大
        right++
    }
    return result
}