← 返回文章列表

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 <= 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))
	}
}
关于这篇文章的日期

保留旧站原始文章标注的日期;实际开始写作的时间可能更早。

同一系列 · 滑动窗口与双指针

上一篇leetcode-灵神跟练-滑动窗口与双指针-定长滑动窗口-基础 ←