leetcode-灵神跟练-滑动窗口与双指针-不定长滑动窗口-越长越合法+求最短+最小

算法
leetcode
滑动窗口
不定长滑动窗口
Published

November 16, 2025

定长滑动窗口-越长越合法/求最短/最小

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 <= 10^9

1 <= nums.length <= 10^5

1 <= nums[i] <= 10^4

思路说明

采用双指针变长滑动窗口的思路

设定左指针,控制窗口大小,遍历数组的index作为右指针

设置变量统计窗口内的数字和

当数字和达到要求的时候 - 采用while循环 - 更新字数组的最小值 - 将左边的值排除窗口,更新数字和,同时将左指针向右移动 - 直到数字和不满足要求结束循环

题解


// 209.长度最小的子数组
package main

import (
    "fmt"
    "math"
)

func minSubArrayLen(target int, nums []int) int {
    ans, left := math.MaxInt, 0
    cnt := 0

    for i, num := range nums {
        cnt += num
        for cnt >= target {
            ans = min(ans, i-left+1)
            cnt -= nums[left]
            left += 1
        }
    }

    if ans > len(nums) {
        return 0
    } else {
        return ans
    }
}

func main() {
    testCases := []struct {
        nums   []int
        target int
    }{
        {[]int{2, 3, 1, 2, 4, 3}, 7},
        {[]int{1, 4, 4}, 4},
        {[]int{1, 1, 1, 1, 1, 1, 1, 1}, 11},
    }

    for _, testCase := range testCases {
        fmt.Println(minSubArrayLen(testCase.target, testCase.nums))
    }
}

2904. 最短且字典序最小的美丽子字符串

题面

给你一个二进制字符串 s 和一个正整数 k 。

如果 s 的某个子字符串中 1 的个数恰好等于 k ,则称这个子字符串是一个 美丽子字符串 。

令 len 等于 最短 美丽子字符串的长度。

返回长度等于 len 且字典序 最小 的美丽子字符串。如果 s 中不含美丽子字符串,则返回一个 空 字符串。

对于相同长度的两个字符串 a 和 b ,如果在 a 和 b 出现不同的第一个位置上,a 中该位置上的字符严格大于 b 中的对应字符,则认为字符串 a 字典序 大于 字符串 b 。

例如,“abcd” 的字典序大于 “abcc” ,因为两个字符串出现不同的第一个位置对应第四个字符,而 d 大于 c 。

示例 1:

输入:s = “100011001”, k = 3

输出:“11001”

解释:示例中共有 7 个美丽子字符串:

  1. 子字符串 “100011001” 。
  2. 子字符串 “100011001” 。
  3. 子字符串 “100011001” 。
  4. 子字符串 “100011001” 。
  5. 子字符串 “100011001” 。
  6. 子字符串 “100011001” 。
  7. 子字符串 “100011001” 。

最短美丽子字符串的长度是 5 。

长度为 5 且字典序最小的美丽子字符串是子字符串 “11001” 。

示例 2:

输入:s = “1011”, k = 2

输出:“11”

解释:示例中共有 3 个美丽子字符串: 1. 子字符串 “1011” 。 2. 子字符串 “1011” 。 3. 子字符串 “1011” 。

最短美丽子字符串的长度是 2 。

长度为 2 且字典序最小的美丽子字符串是子字符串 “11” 。

示例 3:

输入:s = “000”, k = 1

输出:“”

解释:示例中不存在美丽子字符串。

提示:

1 <= s.length <= 100

1 <= k <= s.length

思路说明

我们要做的是找到一个子字符串,子字符串中1的数量为看,如果有多个答案,返回字典序最小的那个

首先根据题目,当字符串中1的数量小于k的时候,会返回空字符串,直接返回

然后初始化结果ans为输入字符串s,因为最差的结果就是s,所以设置为s

之后设置cnt表示窗口内1的数量,left表示窗口的左边界,用循环的index表示右边界

每次移动右边界之后 - 判断窗口内1的数量是不是达到要求 - 如果达到了,开始移动左边界,直到把最左边的1排出 - 这个过程中,左边可能有很多0,排出之后会减小子字符串的长度,所以不断更新子字符串以及左边界,这是还要注意子字符串长度相同选最小字典序的情况 - 这个过程通过while循环实现

题解


// 2904.最短且字典序最小的美丽子字符串
package main

import (
    "fmt"
    "strings"
)

func shortestBeautifulSubstring(s string, k int) string {
    if strings.Count(s, "1") < k {
        return ""
    }
    ans := s
    cnt, left := 0, 0

    for i, ch := range s {
        if ch == '1' {
            cnt += 1
        }

        for cnt >= k {
            tmp := s[left : i+1]
            if s[left] == '1' {
                cnt -= 1
            }
            left += 1
            if len(tmp) == len(ans) {
                if tmp < ans {
                    ans = tmp
                }
            }
            if len(tmp) < len(ans) {
                ans = tmp
            }
        }
    }

    return ans
}

func main() {
    testCases := []struct {
        s string
        k int
    }{
        {"100011001", 3},
        {"1011", 2},
        {"000", 1},
    }

    for _, testCase := range testCases {
        fmt.Println(shortestBeautifulSubstring(testCase.s, testCase.k))
    }
}

1234. 替换子串得到平衡字符串

题面

有一个只含有 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符,且长度为 n 的字符串。

假如在该字符串中,这四个字符都恰好出现 n/4 次,那么它就是一个「平衡字符串」。

给你一个这样的字符串 s,请通过「替换一个子串」的方式,使原字符串 s 变成一个「平衡字符串」。

你可以用和「待替换子串」长度相同的 任何 其他字符串来完成替换。

请返回待替换子串的最小可能长度。

如果原字符串自身就是一个平衡字符串,则返回 0。

示例 1:

输入:s = “QWER”

输出:0

解释:s 已经是平衡的了。

示例 2:

输入:s = “QQWE”

输出:1

解释:我们需要把一个 ‘Q’ 替换成 ‘R’,这样得到的 “RQWE” (或 “QRWE”) 是平衡的。

示例 3:

输入:s = “QQQW”

输出:2

解释:我们可以把前面的 “QQ” 替换成 “ER”。

示例 4:

输入:s = “QQQQ”

输出:3

解释:我们可以替换后 3 个 ‘Q’,使 s = “QWER”。

提示:

1 <= s.length <= 10^5

s.length 是 4 的倍数

s 中只含有 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符

思路说明

构建一个最小窗口,让窗口外的每种字符数量尽量接近n,这个最小窗口就是需要替换的字符串

通过哈希表统计每种字符的数量

题解


// 1234.替换子串得到平衡字符串
package main

import "fmt"

func balancedString(s string) int {
    // 构建哈希表,统计各自的数量
    // 构建一个最小窗口,让窗口外的每种字符数量尽量接近n,这个最小窗口就是需要替换的字符串
    mp := make(map[byte]int, 4)
    n := len(s) / 4

    for i := 0; i < len(s); i++ {
        mp[s[i]] += 1
    }

    // 已经平衡,返回0
    if mp['Q'] == n && mp['W'] == n && mp['E'] == n && mp['R'] == n {
        return 0
    }

    ans, left := len(s), 0

    for i := 0; i < len(s); i++ {
        mp[s[i]] -= 1 // 放进窗口
        for mp['Q'] <= n && mp['W'] <= n && mp['E'] <= n && mp['R'] <= n {
            // 满足条件,记录并收缩
            ans = min(ans, i-left+1)
            mp[s[left]] += 1
            left += 1
        }
    }

    return ans
}

func main() {
    testCases := []string{
        "QWER",
        "QQWE",
        "QQQW",
        "QQQQ",
    }

    for _, testCase := range testCases {
        fmt.Println(balancedString(testCase))
    }
}

2875. 无限数组的最短子数组

题面

给你一个下标从 0 开始的数组 nums 和一个整数 target 。

下标从 0 开始的数组 infinite_nums 是通过无限地将 nums 的元素追加到自己之后生成的。

请你从 infinite_nums 中找出满足 元素和 等于 target 的 最短 子数组,并返回该子数组的长度。如果不存在满足条件的子数组,返回 -1 。

示例 1:

输入:nums = [1,2,3], target = 5

输出:2

解释:在这个例子中 infinite_nums = [1,2,3,1,2,3,1,2,…] 。

区间 [1,2] 内的子数组的元素和等于 target = 5 ,且长度 length = 2 。

可以证明,当元素和等于目标值 target = 5 时,2 是子数组的最短长度。

示例 2:

输入:nums = [1,1,1,2,3], target = 4

输出:2

解释:在这个例子中 infinite_nums = [1,1,1,2,3,1,1,1,2,3,1,1,…].

区间 [4,5] 内的子数组的元素和等于 target = 4 ,且长度 length = 2 。

可以证明,当元素和等于目标值 target = 4 时,2 是子数组的最短长度。

示例 3:

输入:nums = [2,4,6,8], target = 3

输出:-1

解释:在这个例子中 infinite_nums = [2,4,6,8,2,4,6,8,…] 。

可以证明,不存在元素和等于目标值 target = 3 的子数组。

提示:

1 <= nums.length <= 10^5

1 <= nums[i] <= 10^5

1 <= target <= 10^9

思路说明

使用的nuns的数量是不不定的,可能是1-n个,但是可以观察到,如果使用n个,中间n-2个是完整的,左边nums和右边nums,分别取右边和左边连续的数据

设 nums的数据和事total

target = total*m + res

res = nums*2[i:j]

我们要做的就是找到res的长度

res = target & total

其他的按照固定步骤做即可

题解


// 2875.无限数组的最短子数组
package main

import (
    "fmt"
    "math"
)

func minSizeSubarray(nums []int, target int) int {
    total := 0
    for _, x := range nums {
        total += x
    }

    ans := math.MaxInt
    left, sum, n := 0, 0, len(nums)
    for right := 0; right < n*2; right++ {
        sum += nums[right%n]
        for sum > target%total {
            sum -= nums[left%n]
            left++
        }
        if sum == target%total {
            ans = min(ans, right-left+1)
        }
    }
    if ans == math.MaxInt {
        return -1
    }
    return ans + target/total*n
}

func main() {
    testCases := []struct {
        nums   []int
        target int
    }{
        {[]int{1, 2, 3}, 5},
        {[]int{1, 1, 1, 2, 3}, 4},
        {[]int{2, 4, 6, 8}, 3},
    }

    for _, testCase := range testCases {
        fmt.Println(minSizeSubarray(testCase.nums, testCase.target))
    }
}

76. 最小覆盖子串

题面

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。

注意:

对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。

如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = “ADOBECODEBANC”, t = “ABC”

输出:“BANC”

解释:最小覆盖子串 “BANC” 包含来自字符串 t 的 ‘A’、‘B’ 和 ‘C’。

示例 2:

输入:s = “a”, t = “a”

输出:“a”

解释:整个字符串 s 是最小覆盖子串。

示例 3:

输入: s = “a”, t = “aa”

输出: “”

解释: t 中两个字符 ‘a’ 均应包含在 s 的子串中,

因此没有符合条件的子字符串,返回空字符串。

提示:

m == s.length

n == t.length

1 <= m, n <= 10^5

s 和 t 由英文字母组成

思路说明

先说一下题目的要求,找到s的子串,子串包含t中所有字符(种类和数量)

首先,我们要知道t中有哪些字符

然后,在s中找到子串,子串中包含t中所有种类字符,这个子串尽可能小

所以使用哈希表统计t中所有字符种类和数量

然后在s中找子串,如果子串中字符在哈希表中,就减去,如果哈希表全部变成0,表示子串中已经包含t中全部字符,然后缩小窗口,直到不满足要求

题解


// 76.最小覆盖子串
package main

import (
    "fmt"
    "math"
)

func minWindow(s string, t string) string {
    // 先统计一下t中各个字符的数量,统计一下字符的种类
    mp := make(map[byte]int)

    for i := 0; i < len(t); i++ {
        mp[t[i]] += 1
    }
    left := 0
    cnt := len(mp) // 字符的种类
    ans := ""
    tmp := math.MaxInt

    for i := 0; i < len(s); i++ {
        if _, ok := mp[s[i]]; ok { // 字符有效
            mp[s[i]] -= 1
            if mp[s[i]] == 0 { // 符合要求
                cnt -= 1
            }
        }

        for cnt == 0 { // 子串中已经满足要求,缩小子串范围
            if tmp > i-left+1 {
                tmp = i - left + 1
                ans = s[left : i+1]
            }
            if _, ok := mp[s[left]]; ok { // 字符有效
                mp[s[left]] += 1
                if mp[s[left]] == 1 { // 符合要求
                    cnt += 1
                }
            }
            left += 1
        }
    }

    return ans
}

func main() {
    testCases := []struct {
        s string
        t string
    }{
        {"ADOBECODEBANC", "ABC"},
        {"a", "a"},
        {"a", "aa"},
    }

    for _, testCase := range testCases {
        fmt.Println(minWindow(testCase.s, testCase.t))
    }
}

632. 最小区间

题面

你有 k 个 非递减排列 的整数列表。找到一个 最小 区间,使得 k 个列表中的每个列表至少有一个数包含在其中。

我们定义如果 b-a < d-c 或者在 b-a == d-c 时 a < c,则区间 [a,b] 比 [c,d] 小。

示例 1:

输入:nums = [[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]

输出:[20,24]

解释:

列表 1:[4, 10, 15, 24, 26],24 在区间 [20,24] 中。

列表 2:[0, 9, 12, 20],20 在区间 [20,24] 中。

列表 3:[5, 18, 22, 30],22 在区间 [20,24] 中。

示例 2:

输入:nums = [[1,2,3],[1,2,3],[1,2,3]]

输出:[1,1]

提示:

nums.length == k

1 <= k <= 3500

1 <= nums[i].length <= 50

-105 <= nums[i][j] <= 10^5

nums[i] 按非递减顺序排列

思路说明

构建pair,包含数字和位置,然后排序,构建一个滑动窗口,窗口包含所有index并且窗口最小

题解


// 632.最小区间
package main

import (
    "fmt"
    "slices"
)

func smallestRange(nums [][]int) []int {
    type pair struct{ x, i int } // 数字和他们所在的位置
    pairs := []pair{}

    for i, arr := range nums {
        for _, num := range arr {
            pairs = append(pairs, pair{num, i})
        }
    }

    slices.SortFunc(pairs, func(a, b pair) int { return a.x - b.x })

    ansL, ansR := pairs[0].x, pairs[len(pairs)-1].x
    mp := make(map[int]int)
    left := 0

    for _, p := range pairs {
        r, i := p.x, p.i
        mp[i] += 1

        for len(mp) == len(nums) { // 每个列表都至少包含一个数
            l, i := pairs[left].x, pairs[left].i
            if r-l < ansR-ansL {
                ansL, ansR = l, r
            }
            mp[i] -= 1
            if mp[i] == 0 {
                delete(mp, i)
            }
            left += 1
        }
    }
    return []int{ansL, ansR}
}

func main() {
    testCases := [][][]int{
        [][]int{
            []int{4, 10, 15, 24, 26},
            []int{0, 9, 12, 20},
            []int{5, 18, 22, 30},
        },
        [][]int{
            []int{1, 2, 3},
            []int{1, 2, 3},
            []int{1, 2, 3},
        },
    }

    for _, testCase := range testCases {
        fmt.Println(smallestRange(testCase))
    }
}