05数组双指针、滑动窗口
1、双指针
(1)双指针简介
双指针是在遍历数组时,使用两个指针遍历,从而快速达到目的。利用了区间单调性,双指针可以将时间复杂度降到 O(n)。
分类:
对撞指针:两指针移动方向相反
快慢指针:两指针移动方向相同
分离双指针:两指针属于不同数组
(2)对撞指针:
指针left、right 分别指向序列第一个元素和最后一个元素, left指针递增,right 递减,直到两指针相撞,或满足条件。
1 求解步骤
1)left 指向序列第一个元素,right 指向最后一个元素,left = 0、right = len(nums)-1
2)元素满足要求时left右移、right左移
3)指针相撞,或者满足其他条件时,跳出循环体。
2 伪代码模板
left, right = 0, len(nums) - 1
while left < right:
if 条件:
return 符合条件的值
elif 一定条件 1:
left += 1
elif 一定条件 2:
right -= 1
return 没找到 或 找到对应值
3 适用范围
查找有序数组中满足条件的元素问题(如二分查找、数字之和)
字符串反转(如反转字符串、回文数)
(3)快慢指针
指两指针从同一段开始移动,且移动的速度不同。移动快的指针被称为快指针(fast),移动慢的指针被称为慢指针(slow)。两个指针以不同速度移动,直到快指针移动到数组尾端,或者两指针相交,或者满足其他特殊条件时停止。
1 求解步骤
1)使用指针slow、fast,slow 指向数组第一个元素,fast 指向数组第二个元素
2)当满足条件时, slow+=1,当满足另外条件时, fast+=1
3)快指针移动到数组末端,或者两指针相交,或者满足其他条件时结束
2 代码模板
slow = 0
fast = 1
while 没有遍历完:
if 满足要求的特殊条件:
slow += 1
fast += 1
return 合适的值
3 适用范围
一般用于处理数组中元素的移动、删除问题,或者判断链表是否有环、链表长度问题。
(4)分离双指针
两个指针分别属于不同的数组,两个指针分别在两个数组中移动
1 求解步骤
1)使用两个指针left_1、left_2 ,left_1指向第一个数组第一个元素、left_2指向第二个数组第 一个元素
2)满足条件一时:两指针同时右移
3)满足条件二时:left_1右移
4)满足条件三时:left_2右移
5)当其中一个数组遍历完,或者满足其他条件时结束
2 代码模板
left_1 = 0
left_2 = 0
while left_1 < len(nums1) and left_2 < len(nums2):
if 条件一:
left_1 += 1
left_2 += 1
elif 条件二:
left_1 += 1
elif 条件三:
left_2 += 1
3 使用范围
分离双指针一般用于处理有序数组合并,求交集、并集问题
2、滑动窗口
(1)滑动窗口算法介绍
在数组上划定一个定长度或不定长度的窗口,利用双指针中的快慢指针技巧,对窗口进行滑动操作、缩放操作(对于不定长度的窗口,可以从左侧缩小窗口长度,也可以从右侧增大窗口长度),以及维护最优解操作。
(2)滑动窗口适用范围
滑动窗口算法一般用来解决一些查找满足一定条件的连续区间的性质的问题,可有效减少时间复杂度。
(3)固定长度滑动窗口
1 算法步骤
1)使用两个指针 left、right指向第一个元素,窗口为[left,right]
2)不断移动 right,将数组前 window―size 个元素填入窗口中
3)当窗口达到 window―size 大小时,判断窗口内的元素是否满足题目限定条件
4)向右移动 left,缩小窗口长度
5)向右移动 right,将新元素填入窗口中
6)重复上述步骤,直到right指向数组末尾
2 代码模板
left = 0
right = 0
while right < len(nums):
window.append(nums[right])
# 超过窗口大小时,缩小窗口,维护窗口中始终为 window_size 的长度
if right - left + 1 >= window_size:
# ... 维护答案
window.popleft()
left += 1
# 向右侧增大窗口
right += 1
(4)不定长度滑动窗口
1 算法步
1)使用两个指针 left、right,指向序列的第一个元素,窗口即为[left,right]
2)将right下标元素添加入窗口中
3)向右移动 right,增大窗口长度,直到窗口中的连续元素满足要求
4)停止增加窗口大小,将左侧元素移出窗口,向右移动 left,缩小窗口长度,直到窗口中的连续元素不再满足要求。
5)重复上述步骤,直到 right 到达序列末尾。
2 代码模板
left = 0
right = 0
while right < len(nums):
window.append(nums[right])
while 窗口需要缩小:
# ... 可维护答案
window.popleft()
left += 1
# 向右侧增大窗口
right += 1
3、练习题目
(1)反转字符串
题目:给定一个字符数组 s,要求将其反转。
class Solution(object):
def reverseString(self, s):
"""
:type s: List[str]
:rtype: None Do not return anything, modify s in-place instead.
"""
left = 0
right = len(s)-1
while left<=right:
s[left],s[right]=s[right],s[left]
left+=1
right-=1
(2)反转字符串中的元音字母
题目:给定一个字符串 s,将字符串中的元音字母进行反转。
class Solution(object):
def reverseVowels(self, s):
"""
:type s: str
:rtype: str
"""
def vowel(n):
return n in "aeiouAEIOU"
s=list(s)
left , right = 0,len(s)-1
while left<=right:
while left<right and not vowel(s[left]):
left+=1
while right>left and not vowel(s[right]):
right-=1
s[left],s[right]=s[right],s[left]
left+=1
right-=1
return ''.join(s)
(3)三数之和
题目:给定一个整数数组 nums,判断 nums 中是否存在三个元素 a、b、c,满足 a+b+c==0。要求找出所有满足要求的不重复的三元组。
class Solution(object):
def threeSum(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
nums.sort()
ans = []
n = len(nums)
for i in range(n - 2):
x = nums[i]
if i > 0 and x == nums[i - 1]:
continue
if x + nums[i + 1] + nums[i + 2] > 0:
break
if x + nums[-2] + nums[-1] < 0:
continue
j = i + 1
k = n - 1
while j < k:
s = x + nums[j] + nums[k]
if s > 0:
k -= 1
elif s < 0:
j += 1
else:
ans.append([x, nums[j], nums[k]])
j += 1
while j < k and nums[j] == nums[j - 1]:
j += 1
k -= 1
while k > j and nums[k] == nums[k + 1]:
k -= 1
return ans
(4)移除元素
题目:给定一个数组 nums,和一个值 val,不使用额外数组空间,将数组中所有数值等于 val 值的元素移除掉,并且返回新数组的长度。
class Solution(object):
def removeElement(self, nums, val):
"""
:type nums: List[int]
:type val: int
:rtype: int
"""
slow =0
fast =0
while fast<len(nums):
if nums[fast]!=val:
nums[slow]=nums[fast]
slow+=1
fast+=1
return slow
(5)删除有序数组中的重复项Ⅱ
题目:给定一个有序数组 nums,在原数组空间基础上删除重复出现 2 次以上的元素,并返回删除后数组的新长度。
class Solution(object):
def removeDuplicates(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
n=len(nums)
slow =2
fast =2
while fast<n:
if nums[slow-2]!=nums[fast]:
nums[slow]=nums[fast]
slow+=1
fast+=1
return slow
(6)长按键入
题目:给定代表名字的字符串 name,以及实际输入的字符串 typed,检查键盘输入的字符 typed。如果它对应的可能是你的朋友的名字(其中一些字符可能被长按),就返回 True
。否则返回 False
。
class Solution(object):
def isLongPressedName(self, name, typed):
"""
:type name: str
:type typed: str
:rtype: bool
"""
left_1 = 0
left_2 = 0
while left_2<len(typed):
if left_1<len(name) and name[left_1]==typed[left_2]:
left_1+=1
left_2+=1
elif left_2>0 and typed[left_2]==name[left_1-1]:
left_2+=1
else:
return False
return left_1==len(name)
(7)子数组最大平均数Ⅰ
题目:给定一个由 n 个元素组成的整数数组 nums 和一个整数 k,找出平均数最大且长度为 k 的连续子数组,并输出该最大平均数。
class Solution(object):
def findMaxAverage(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: float
"""
l = len(nums)
my_max = sum(nums[:k])
my_sum = sum(nums[:k])
for i in range(k,l):
my_sum+=nums[i]
my_sum-=nums[i-k]
my_max = max(my_max,my_sum)
my_max = float(my_max)
return my_max/k
(8)最长连续递增序列
题目:给定一个未经排序的数组 nums,找到最长且连续递增的子序列,并返回该序列的长度
class Solution(object):
def findLengthOfLCIS(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
left = right = 0
num = 0
for i in range(1, len(nums)):
if nums[i] > nums[i - 1]:
right = i
else:
if right - left + 1 > num:
num = right - left + 1
left = right = i
return max(num, right - left + 1)
(9)最大连续一的个数Ⅲ
题目:
给定一个由 0、1 组成的数组 nums,再给定一个整数 k。最多可以将 k 个值从 0 变到 1
返回仅包含 1 的最长连续子数组的长度。
lass Solution(object):
def longestOnes(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
num=0
ans=0
left, right = 0, 0
while right < len(nums):
if nums[right]==0:
num+=1
while num>k:
if nums[left]==0:
num-=1
left+=1
ans = max(ans,right-left+1)
right+=1
return ans