1. 章节算法思想
1.1 注意
使用滑动窗口(双指针)方法在处理包含负数的情况时可能会出现问题。滑动窗口通常适用于处理连续子数组的问题,因为它们依赖于数组元素的顺序和连续性。当数组中存在负数时,滑动窗口的窗口和可能会产生复杂的情况,因为加入负数会减小当前窗口的和,使得在考虑连续子数组时变得复杂。(不适用于560.和为k的子数组)
1.2 打油诗
链表子串数组题,用双指针别犹豫。双指针家三兄弟,个个都是万人迷。
快慢指针最神奇,链表操作无压力。归并排序找中点,链表成环搞判定。
左右指针最常见,左右两端相向行。反转数组要靠它,二分搜索是弟弟。
滑动窗口最困难,子串问题全靠它。左右指针滑窗口,一前一后齐头进。
1.3 模版
重点思考:
- 需要定义的变量
- 窗口扩张条件
- 窗口收缩条件
模版抽象:
func slidingWindow(string s) {
// 定义窗口相关变量
left, right := 0, 0
...
// 定义返回值相关变量
minLen :=0
...
// 定义收缩条件相关变量
window := make(map[byte]int)
...
// 确定窗口扩张条件
for left < right && right < len(s){
// income
income := s[right]
// 收缩条件更迭
window.add(c)
// 增大窗口
right++
// 判断是否满足收缩条件
for left < right && the condition of why window needs shrink{
// 求最短、最小时,在窗口进行下一次收缩前记录最短、最小值
...
// outcome
outcome := s[left]
// 收缩条件更迭
window.remove(outcome)
// 缩小窗口
left++
}
// 求最大、最长时,在窗口进行下一次扩前记录最大、最长值
...
}
// 是否得到有效值的处理
...
}
1.4 例题
1.4.1 长度最小的子数组
// frame
func minSubArrayLen(target int, nums []int) int {
// 定义窗口相关变量
left, right := 0, 0
// 定义返回值相关变量
lenMin := len(nums)+1
// 定义收缩条件相关变量
sum := 0
// 窗口扩张条件
for right <= len(nums)-1 {
// income
income := nums[right]
// 收缩条件更新
sum += income
// 右边界移动
right++
// 判断是否达到窗口收缩条件
// 持续收缩到不能收缩为止
for sum >= target {
// 收缩窗口的时候检查最小值
lenMin = min(lenMin, right-left)
// outcome
outcome := nums[left]
// 更迭收缩条件
sum -= outcome
// 左边界移动
left++
}
}
// 判断是否得到有效收缩值
if lenMin == len(nums) + 1 {
return 0
}
// 有则正常返回
return lenMin
}
1.4.2 最小覆盖子串
// frame
func minWindow(s string, t string) string {
// 定义窗口相关变量
left, right := 0, 0
// 定义返回值相关变量
minLen, start := math.MaxInt32, 0
// 定义收缩条件相关变量及初始化
mS := make(map[byte]int)
mT := make(map[byte]int)
valid := 0
for i := 0; i < len(t); i++ {
mT[t[i]]++
}
// 右边界移动
for right < len(s) {
// element income
income := s[right]
// condition processing
if _, ok := mT[income]; ok {
mS[income]++
if mS[income] == mT[income] {
valid++
}
}
// right border move
right++
// condition judge
for valid == len(mT) {
// 收缩窗口的时候更新最小值
if right -left < minLen {
start = left
minLen = right - left
}
// outcome
outcome := s[left]
// process condition
if _, ok := mT[outcome]; ok {
if mS[outcome] == mT[outcome] {
valid--
}
mS[outcome]--
}
// left move
left++
}
}
// 有效值判断及处理
if minLen != math.MaxInt32 {
return s[start:start+minLen]
}else {
return ""
}
}
1.4.3 无重复字符的最长子串
func lengthOfLongestSubstring(s string) int {
// 定义窗口相关变量
left, right := 0, 0
// 定义返回值相关变量
maxLen := math.MinInt32
// 收缩条件相关
letterMap := make(map[byte]int)
// 右边界右移开始
for right < len(s) {
// income
income := s[right]
// 收缩条件更迭
letterMap[income]++
// 指针右移
right++
// 收缩条件满足性检查
for letterMap[income] > 1 {
// outcome
outcome := s[left]
// 收缩条件更迭
letterMap[outcome]--
// 左指针右移
left++
}
// 保存现场
maxLen = max(maxLen, right-left)
}
// 有效值判断和处理
if maxLen == math.MinInt32 {
return 0
} else {
return maxLen
}
}
1.4.4 找到字符串中所有字母异位词
func findAnagrams(s string, p string) []int {
// 定义返回值相关变量
// start := 0
res := make([]int,0)
// 定义收缩条件相关变量
mS := make(map[byte]int)
mP := make(map[byte]int)
for i := 0; i < len(p); i++ {
mP[p[i]]++
}
valid := 0
// 窗口的左右边界
left, right := 0, 0
// 移动右边界
for right < len(s) {
// income
income := s[right]
// condition processing
if _, ok := mP[income]; ok {
mS[income]++
if mS[income] == mP[income] {
valid++
}
}
// right move
right++
// 是否达到收缩条件判断,当窗口值等于模式串长度时就要开始要进行有效异位词判断和窗口收缩了
for right - left == len(p) {
// 结果保存,判断当前窗口是否符合异位词标准,符合则保存,类似子串覆盖,只不过这个要求子串与模式串的长度一致
if valid == len(mP) {
res = append(res, left)
}
// outcome
outcome := s[left]
// condition processing
if _, ok := mP[outcome]; ok {
if mS[outcome] == mP[outcome] {
valid--
}
mS[outcome]--
}
// left index move
left++
}
}
// 返回
return res
}