双指针
双指针分为「对撞指针」、「快慢指针」、「分离双指针」。
- 对撞指针:两个指针方向相反。适合解决查找有序数组中满足某些约束条件的一组元素问题、字符串反转问题。
- 快慢指针:两个指针方向相同。适合解决数组中的移动、删除元素问题,或者链表中的判断是否有环、长度问题。
- 分离双指针:两个指针分别属于不同的数组 / 链表。适合解决有序数组合并,求交集、并集问题。
滑动窗口
算法介绍
在计算机网络中,滑动窗口协议(Sliding Window Protocol)是传输层进行流控的一种措施,接收方通过通告发送方自己的窗口大小,从而控制发送方的发送速度,从而达到防止发送方发送速度过快而导致自己被淹没的目的。我们所要讲解的滑动窗口算法也是利用了同样的特性。
滑动窗口算法(Sliding Window):在给定数组 / 字符串上维护一个固定长度或不定长度的窗口。可以对窗口进行滑动操作、缩放操作,以及维护最优解操作。
- 滑动操作:窗口可按照一定方向进行移动。最常见的是向右侧移动。
- 缩放操作:对于不定长度的窗口,可以从左侧缩小窗口长度,也可以从右侧增大窗口长度。
滑动窗口利用了双指针中的快慢指针技巧,我们可以将滑动窗口看做是快慢指针两个指针中间的区间,也可以将滑动窗口看做是快慢指针的一种特殊形式。
适用范围
滑动窗口算法一般用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。该算法可以将一部分问题中的嵌套循环转变为一个单循环,因此它可以减少时间复杂度。
按照窗口长度的固定情况,我们可以将滑动窗口题目分为以下两种:
- 固定长度窗口:窗口大小是固定的。
- 不定长度窗口:窗口大小是不固定的。
- 求解最大的满足条件的窗口。
- 求解最小的满足条件的窗口。
固定长度滑动窗口
固定长度滑动窗口算法(Fixed Length Sliding Window):在给定数组 / 字符串上维护一个固定长度的窗口。可以对窗口进行滑动操作、缩放操作,以及维护最优解操作。
不定长度滑动窗口
不定长度滑动窗口算法(Sliding Window):在给定数组 / 字符串上维护一个不定长度的窗口。可以对窗口进行滑动操作、缩放操作,以及维护最优解操作。
练习
T0344. 反转字符串
class Solution:
def reverseString(self, s: List[str]) -> None:
left, right = 0, len(s) - 1
while left < right:
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
T0345. 反转字符串中的元音字母
class Solution:
def reverseVowels(self, s: str) -> str:
vowels = ['a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U']
left = 0
right = len(s)-1
s_list = list(s)
while left < right:
if s_list[left] not in vowels:
left += 1
continue
if s_list[right] not in vowels:
right -= 1
continue
s_list[left], s_list[right] = s_list[right], s_list[left]
left += 1
right -= 1
return "".join(s_list)
T0015. 三数之和
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
nums.sort()
ans = []
for i in range(n):
if i > 0 and nums[i] == nums[i - 1]:
continue
left = i + 1
right = n - 1
while left < right:
while left < right and left > i + 1 and nums[left] == nums[left - 1]:
left += 1
while left < right and right < n - 1 and nums[right + 1] == nums[right]:
right -= 1
if left < right and nums[i] + nums[left] + nums[right] == 0:
ans.append([nums[i], nums[left], nums[right]])
left += 1
right -= 1
elif nums[i] + nums[left] + nums[right] > 0:
right -= 1
else:
left += 1
return ans
T0027. 移除元素
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
slow = 0
fast = 0
while fast < len(nums):
if nums[fast] != val:
nums[slow], nums[fast] = nums[fast], nums[slow]
slow += 1
fast += 1
return slow
T0080. 删除有序数组中的重复项 II
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
size = len(nums)
if size <= 2:
return size
slow, fast = 2, 2
while (fast < size):
if nums[slow - 2] != nums[fast]:
nums[slow] = nums[fast]
slow += 1
fast += 1
return slow
T0925. 长按键入
class Solution:
def isLongPressedName(self, name: str, typed: str) -> bool:
left_1, left_2 = 0, 0
while left_1 < len(name) and left_2 < len(typed):
if name[left_1] == typed[left_2]:
left_1 += 1
left_2 += 1
elif left_2 > 0 and typed[left_2 - 1] == typed[left_2]:
left_2 += 1
else:
return False
while 0 < left_2 < len(typed) and typed[left_2] == typed[left_2 - 1]:
left_2 += 1
if left_1 == len(name) and left_2 == len(typed):
return True
else:
return False
T0643. 子数组最大平均数 I
class Solution:
def findMaxAverage(self, nums: List[int], k: int) -> float:
left = 0
right = 0
window_total = 0
ans = float('-inf')
while right < len(nums):
window_total += nums[right]
if right - left + 1 >= k:
ans = max(window_total / k, ans)
window_total -= nums[left]
left += 1
# 向右侧增大窗口
right += 1
return ans
T0674. 最长连续递增序列
class Solution:
def findLengthOfLCIS(self, nums: List[int]) -> int:
size = len(nums)
left, right = 0, 0
window_len = 0
max_len = 0
while right < size:
window_len += 1
if right > 0 and nums[right - 1] >= nums[right]:
left = right
window_len = 1
max_len = max(max_len, window_len)
right += 1
return max_len
T1004. 最大连续1的个数 III
class Solution:
def longestOnes(self, nums: List[int], k: int) -> int:
max_count = 0
zero_count = 0
left, right = 0, 0
while right < len(nums):
if nums[right] == 0:
zero_count += 1
right += 1
if zero_count > k:
if nums[left] == 0:
zero_count -= 1
left += 1
max_count = max(max_count, right - left)
return max_count