不定长滑动窗口(求最短/最小)
选自灵神题单,不定长滑动窗口 2.2 。
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
class Solution:def minSubArrayLen(self, target: int, nums: List[int]) -> int:left = 0ans = infsum_ = 0for right, x in enumerate(nums):sum_ += xwhile sum_ - nums[left] >= target:sum_ -= nums[left]left += 1if sum_ >= target: # 一定要加这个条件,避免刚开始时上面的while循环不会进入,而总和不满足条件却记录了结果ans = min(ans, right - left + 1)return ans if ans != inf else 0
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
输出:""
解释:示例中不存在美丽子字符串。
class Solution:def shortestBeautifulSubstring(self, s: str, k: int) -> str:if s.count("1") < k:return ""left = 0ans = ""count = inf # 结果字符串的长度for right, x in enumerate(s):if x == "1":k -= 1# 如果窗口内的1的个数超过k,或者窗口端点是0,就可以缩小窗口# 直到左端点是窗口内1的个数恰好是k的边界为止while k < 0 or s[left] == "0":if s[left] == "1":k += 1left += 1if (k == 0 # 加这个条件是为了刚开始避免k一次也没减,直接记录结果and right - left + 1 < countor right - left + 1 == countand ans> s[left : right + 1] # 如果结果字符串和滑动窗口长度一样,选择字典序小的):ans = s[left : right + 1]count = right - left + 1 # 更新结果字符串的长度return ans
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"。
class Solution:# 思路:找到元素个数大于目标值的那部分,找到包含这些部分的最小子串,将这个子串中的某个字符替换成元素个数较少的字符# 不过我们并不在意替换的细节,所以只需要找到这个最小子串的长度就好了def balancedString(self, s: str) -> int:# 统计窗口内字符元素个数与目标值的差值win = {"Q": s.count("Q") - len(s) // 4,"W": s.count("W") - len(s) // 4,"E": s.count("E") - len(s) // 4,"R": s.count("R") - len(s) // 4,}# 如果元素个数与目标值差值都为0,则该字符串符合条件,直接返回0if min(win.values()) == 0:return 0# 收集需要删除的键,保留元素个数多出来的部分keys_to_delete = [i for i in win if win[i] <= 0]# 删除这些键for key in keys_to_delete:del win[key]left = 0ans = inffor right, x in enumerate(s):if x in win:win[x] -= 1# 当窗口内字符满足条件时,就可以缩小窗口了,同时也是收割结果的时候while all([i <= 0 for i in win.values()]):ans = min(ans, right - left + 1)if s[left] in win:win[s[left]] += 1left += 1return ans
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 的子数组。
class Solution:def minSizeSubarray(self, nums: List[int], target: int) -> int:# # 将target值分为两部分,一部分是数组总和的整数倍,剩下的一部分就是不能凑够一个数组的# res = len(nums) * (target // sum(nums)) # 数组总和的整数倍# target = target % sum(nums) # 找到这个目标值的最小子数组# left, ans = 0, inf# nums = nums * 2 # 由于是无限数组,避免有尾和头拼接的情况,所以这里将两个数组拼接# for right, x in enumerate(nums):# target -= x# while target < 0:# target += nums[left]# left += 1# if target == 0:# ans = min(ans, right - left + 1)# return ans + res if ans != inf else -1# 也可以不开辟两个数组空间,用取模的方式res = len(nums) * (target // sum(nums)) # 数组总和的整数倍target = target % sum(nums) # 找到这个目标值的最小子数组left, ans = 0, inffor right in range(len(nums) * 2):target -= nums[right % len(nums)]while target < 0:target += nums[left % len(nums)]left += 1if target == 0:ans = min(ans, right - left + 1)return ans + res if ans != inf else -1
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 的子串中,
因此没有符合条件的子字符串,返回空字符串。
class Solution:def minWindow(self, s: str, t: str) -> str:from collections import Counter# 计算t字符的个数win = Counter(t)left, ans, count = 0, "", inffor right, x in enumerate(s):if x in win:win[x] -= 1# 如果窗口内涵盖了t字符,就可以缩小窗口,同时也能收割结果while all([win[i] <= 0 for i in win]):if count > right - left + 1: # 收割结果ans = s[left : right + 1]count = right - left + 1if s[left] in win: # 缩小窗口win[s[left]] += 1left += 1return ans
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]
class Solution:# 将几个数组合并在一起,并且构建一个二维列表,每个列表的第一个值是原来的元素,第二个值表示在第几个列表上的索引def smallestRange(self, nums: List[List[int]]) -> List[int]:from collections import defaultdictwin = defaultdict(int) # 存储窗口内元素是在原来的第几个列表上的出现次数n = len(nums)nums = [[j, i] for i in range(len(nums)) for j in nums[i]]nums.sort()left = 0ans_l, ans_r = nums[0][0], nums[-1][0]for right, x in enumerate(nums):win[x[1]] += 1# 如果窗口内元素在每个列表中都出现了,则缩小窗口,同时收割结果while len(win) == n:if nums[right][0] - nums[left][0] < ans_r - ans_l:ans_l, ans_r = nums[left][0], nums[right][0] # 收割结果win[nums[left][1]] -= 1 # 缩小窗口if win[nums[left][1]] == 0:del win[nums[left][1]]left += 1return [ans_l, ans_r]