不会算法的小白不是好小白,可恶还有什么可以难倒我这个美女的,不做花瓶第一天!
一、长度最小的子数组
209. 长度最小的子数组 - 力扣(LeetCode)
1.思路
滑动窗口法:把数组的区间,假设成为两个指针,先后移动两个指针
我们先读懂题目,这个很重要,不过我现在读的不是很懂,没事美女有弱点可以理解
2.辅助理解的例子(没办法罗,思路不过脑子只能解析一下)
let transactions = [1, 2, 3, 4, 5];
目标是找到一个连续的子数组,其元素总和至少为 9
let target = 9;
let result = minSubArrayLen(target, transactions);
1.初始设置
transactions = [1, 2, 3, 4, 5]
target = 9
start = 0, end = 0
sum = 0
ans = Infinity
2.迭代过程
-
第一轮迭代:
end = 0
sum = 1
(1
)sum < target
(9
),所以end++
。
-
第二轮迭代:
end = 1
sum = 3
(1 + 2
)sum < target
,所以end++
。
-
第三轮迭代:
end = 2
sum = 6
(1 + 2 + 3
)sum < target
,所以end++
。
-
第四轮迭代:
end = 3
sum = 10
(1 + 2 + 3 + 4
)sum >= target
:Math.min(ans, end - start + 1)
->Math.min(Infinity, 4 - 0 + 1)
->ans = 4
sum -= nums[start]
,start++
->sum = 9
(2 + 3 + 4
),start = 1
-
第五轮迭代:
end = 3
sum = 9
(2 + 3 + 4
)sum >= target
:Math.min(ans, end - start + 1)
->Math.min(4, 4 - 1 + 1)
->ans = 3
sum -= nums[start]
,start++
->sum = 7
(3 + 4
),start = 2
-
接下来的迭代
end
继续增加,但不再找到总和大于等于target
的更短子数组。
3结果
- 最终,
ans
的值为3
。 - 函数返回
3
,表示最短的满足条件的子数组长度是3
(即[2, 3, 4]
)。
很好这个滑动窗口是这样理解了,但是不会活学活用,那么下面继续
二.水果成篮
904. 水果成篮 - 力扣(LeetCode)
很抽象我读不懂题目:找了一个外援理解了一下题目
人话:我们的目标是找到一个窗口,其中只包含两种类型的水果,并且这个窗口尽可能大
步骤
-
初始化: 定义两个变量来跟踪窗口的开始和结束位置。同时,使用一个数据结构(如哈希表)来跟踪窗口内不同水果的数量。
-
扩大窗口: 从左向右移动窗口的右边界,即不断添加新的水果到窗口中,同时更新哈希表。
-
满足条件的窗口: 当窗口中包含超过两种水果时,移动窗口的左边界以排除一种水果,直到窗口重新只包含两种水果。
-
记录结果: 在每次更新窗口时,如果窗口只包含两种水果,更新最大收集水果的数量。
-
重复直到结束: 继续扩大和缩小窗口,直到覆盖了整个数组。
例子
假设 fruits = [1, 2, 1, 2, 3]
,我们可以按以下步骤使用滑动窗口法:
- 开始: 窗口为空,最大数量为 0。
- 窗口扩大: 添加
1
,窗口为[1]
,最大数量为 1。 - 窗口扩大: 添加
2
,窗口为[1, 2]
,最大数量为 2。 - 窗口扩大: 添加
1
,窗口为[1, 2, 1]
,最大数量为 3。 - 窗口扩大: 添加
2
,窗口为[1, 2, 1, 2]
,最大数量为 4。 - 窗口扩大: 添加
3
,窗口为[1, 2, 1, 2, 3]
。现在窗口中有三种水果,需要缩小窗口。 - 缩小窗口: 移除窗口左边的
1
,窗口变为[2, 1, 2, 3]
。依然有三种水果,继续缩小。 - 缩小窗口: 移除窗口左边的
2
,窗口变为[1, 2, 3]
。现在窗口中有两种水果,最大数量更新为 3。
最终,最大收集的水果数量为 4(在添加第四个水果之前)。
初始设置
basket = {}
: 用来存储水果的类型和数量。start = 0
: 窗口的开始位置。maxFruits = 0
: 可以收集的最大水果数。fruits = [1, 2, 1, 2, 3]
: 待处理的水果数组。
迭代过程
-
第一次迭代 (
end = 0
):fruit = fruits[0] = 1
basket = {1: 1}
maxFruits = Math.max(0, 0 - 0 + 1) = 1
-
第二次迭代 (
end = 1
):fruit = fruits[1] = 2
basket = {1: 1, 2: 1}
maxFruits = Math.max(1, 1 - 0 + 1) = 2
-
第三次迭代 (
end = 2
):fruit = fruits[2] = 1
basket = {1: 2, 2: 1}
maxFruits = Math.max(2, 2 - 0 + 1) = 3
-
第四次迭代 (
end = 3
):fruit = fruits[3] = 2
basket = {1: 2, 2: 2}
maxFruits = Math.max(3, 3 - 0 + 1) = 4
-
第五次迭代 (
end = 4
):fruit = fruits[4] = 3
basket = {1: 2, 2: 2, 3: 1}
- 现在
basket
中有三种水果。需要移动start
来删除一种水果。 - 移动
start
,basket = {1: 1, 2: 2, 3: 1}
- 继续移动
start
,basket = {2: 2, 3: 1}
maxFruits = Math.max(4, 4 - 1 + 1) = 4
结果
函数最终返回 maxFruits = 4
。这意味着最长的符合规则的连续子数组是 [1, 2, 1, 2]
,其长度为 4。
三、最小覆盖子串
76. 最小覆盖子串 - 力扣(LeetCode)
辅助笨蛋美女理解的例子
初始设置
s = "ADOBECODEBANC"
t = "ABC"
- 初始化窗口的左右指针:
l = 0, r = 0
- 窗口中符合条件的字符数量:
formed = 0
- 记录
t
中字符出现频率的哈希表tFreq
- 记录窗口中字符出现频率的哈希表
windowCounts
- 最小子串的长度:
minLength = Infinity
- 最小子串的起始索引:
minLeft = 0
迭代过程
-
初始化
tFreq
:tFreq = {'A': 1, 'B': 1, 'C': 1}
-
第一次迭代 (
r = 0
):- 当前字符
s[r] = 'A'
windowCounts = {'A': 1}
formed = 1
(因为 'A' 是t
的一部分)r++
- 当前字符
-
第二次迭代 (
r = 1
):- 当前字符
s[r] = 'D'
windowCounts = {'A': 1, 'D': 1}
formed
保持不变r++
- 当前字符
-
第三次迭代 (
r = 2
):- 当前字符
s[r] = 'O'
windowCounts = {'A': 1, 'D': 1, 'O': 1}
formed
保持不变r++
- 当前字符
-
第四次迭代 (
r = 3
):- 当前字符
s[r] = 'B'
windowCounts = {'A': 1, 'D': 1, 'O': 1, 'B': 1}
formed = 2
(因为 'B' 是t
的一部分)r++
- 当前字符
-
第五次迭代 (
r = 4
):- 当前字符
s[r] = 'E'
windowCounts = {'A': 1, 'D': 1, 'O': 1, 'B': 1, 'E': 1}
formed
保持不变r++
- 当前字符
-
第六次迭代 (
r = 5
):- 当前字符
s[r] = 'C'
windowCounts = {'A': 1, 'D': 1, 'O': 1, 'B': 1, 'E': 1, 'C': 1}
formed = 3
('C' 是t
的一部分,现在窗口包含所有t
的字符)- 开始尝试缩小窗口 (
l
开始移动):- 移动
l
直到formed
减少 l
停在3
('B' 处),此时窗口是"ADOBEC"
,长度为6
minLength = 6, minLeft = 0
- 移动
- 当前字符
-
继续迭代:
- 继续移动
r
和l
,寻找更小的覆盖子串 - 每当
formed
达到3
时,尝试缩小窗口并更新minLength
和minLeft
- 继续移动
结果
- 经过迭代,找到最小覆盖子串
"BANC"
(长度为4
,起始索引为9
) - 函数返回
"BANC"
每次迭代都会检查当前窗口是否包含了 t
中的所有字符。
美女不懂的地方,反思理解到了
缩小窗口的具体步骤
假设我们有字符串 s = "ADOBECODEBANC"
和 t = "ABC"
,并且我们已经通过向右移动右指针 r
扩展了窗口,直到它包含了 t
中的所有字符。目前的窗口是 "ADOBEC"
(从索引 0
到 5
)。
-
缩小窗口:
- 我们现在尝试通过向右移动左指针
l
来缩小窗口。每移动一次l
,窗口中包含的字符数量就会相应减少。
- 我们现在尝试通过向右移动左指针
-
维护
formed
条件:formed
变量用来跟踪窗口中是否包含了t
中的所有字符。- 在这个例子中,当窗口包含
A
,B
,C
各至少一次时,formed
条件为满足状态。
-
移动
l
直到formed
减少:- 开始逐个移除窗口左侧的字符,并检查每次移除后窗口是否仍然满足
formed
条件。 - 在这个例子中,当左指针
l
移到3
的位置时(即字符B
),窗口为"BEC"
(去掉了ADO
)。这时,窗口不再包含t
中的所有字符(A
被移除了),所以formed
条件不再满足。
- 开始逐个移除窗口左侧的字符,并检查每次移除后窗口是否仍然满足
-
更新最小子串信息:
- 在移动
l
之前,如果当前窗口满足条件,我们检查它是否比之前记录的最小窗口更小。 - 在这个例子中,窗口
"ADOBEC"
的长度为6
,这是目前找到的满足条件的最小窗口。因此,minLength
更新为6
,minLeft
更新为窗口的起始索引0
。
- 在移动
通过以上步骤,我们找到了一个满足条件的子串 "ADOBEC"
。但算法不会停止,它会继续尝试找到可能存在的更小的满足条件的子串,直到遍历完整个字符串 s
。最终的答案在这个例子中是 "BANC"
。