双指针算法是指利用两个指针遍历数组(链表),左右指针相向前进或同向前进,在遍历过程中根据某种限制条件进行筛选,通常可以把时间复杂度降低至O(n)。
2.1 leetcode80-删除排序数组中的重复项II
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定 nums = [1,1,1,2,2,3],
函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,1,2,3,3],
函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3 。
你不需要考虑数组中超出新长度后面的元素。
设置左右指针,且双指针同向前进,左指针的作用是维护只有两个相同数字的区域,右指针的作用是遍历数组, 将满足条件的数字交换给左指针。
右指针在遍历时需要设置计数器以及记录当前的数字,并及时更新当前数字的个数。
当个数大于等于3时,跳过此数字,右指针继续前进。
当个数小于3时,将数字交换给左指针,同时左指针向前移动一格,右指针继续前进。
时间复杂度:O(n) //只遍历一遍数组
空间复杂度:O(1) //原地修改数组
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
'''
双指针
思路分析:
设置左右指针,且指针同向前进,左指针记录到3个相同值时,留在原地,
右指针继续前进,当遇到不同值时,右指针将值交换给左指针,然后左指针向
前移动一格。
左指针的作用是维护只有两个相同数字的区域,右指针的作用是遍历数组,
将满足条件的数字交换给左指针。
右指针在遍历时需要设置计数器,计算当前数字的个数。
当个数大于等于3时,跳过此数字。
当个数小于3时,将数字交换给左指针。
'''
if len(nums) == 0:
return 0
left, right = 0, 0
count = 0
cur = nums[0]
while right < len(nums):
if cur == nums[right]:
count += 1
if count <= 2 :
nums[left] = nums[right]
left += 1
else:
cur = nums[right]
count = 1
nums[left] = nums[right]
left += 1
right += 1
return left
2.2 leetcode40-接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 图片来自leetCode
解法一 按行取
按照行取水,每行有水的地方左右都有柱子,即水坑的列高都小于两边柱子的列高。
按行进行遍历,行数由最高的行确定。遍历height数组
当遇到高于等于当前行号的柱子时,表示左墙壁存在,开始取水。
当遇到小于当前行号的柱子时,表示坑存在,进行蓄水。
当又遇到大于等于当前行号的柱子时,表示右墙壁(下一个坑的左墙壁)存在,累加坑中的蓄水量,并重置蓄水量为0。
只有当左右墙壁均存在时,才会蓄水,进行蓄水量的累加计算。
开始时sum = 0
若height[i] >= col,则说明左墙壁存在,开始取水,start = True
当start为true时有两种情况.
若height[i] <= col,tmp = tmp + 1.
若height[i] > col, sum = sum + tmp
时间复杂度:O(m*n)
空间复杂度:O(1)
解法二 按列取
按照列取水,某一列可以取水,那么这一列两边必然也存在高于或等于当前列高的左右墙壁。若此列可以取水,由于木桶效应,取水量自然取决于左右两边最高墙壁中较矮的那一面墙壁。
判断某一列是否积水,简单说会出现三种情况:
当前柱子高度小于等于较矮的柱子,此时此列可以蓄水,蓄水值 = min(leftMaxHeight, rightMaxHeight) - height[i]
当前柱子高度大于较矮的柱子,此时列不可以蓄水。
时间复杂度: O(m*n)
空间复杂度:O(1)
解法三 动态规划
我们注意到在上一种解法中,求每列的左右最高墙壁时都要重新遍历数组,考虑利用dp数组记录左右两边的最高墙壁高度。在取水之前先找出每列的左右两边的最高墙壁,要用时直接查表即可。
动态转移方程如下:
dpLeftMaxHeight[i] = max( dpLeftMaxHeight[i-1], height[i])
dpRightMaxHeight[j] = max(dpRightMaxHeight[j+1], height[j])
时间复杂度:O(n)
空间复杂度:O(n)
解法四 双指针
在上一种解法中,我们使用了两个dp数组来记录每列的左右两边的最高墙壁高度。因为取水情况取决于左右两边最高墙壁的较小值,考虑在遍历过程中使用左右双指针。当左指针所在列较高时,从右边开始取水。当右指针所在列较高时,从左边开始取水。
class Solution:
def trap(self, height: List[int]) -> int:
left, right = 0 , len(height)-1
maxLeft, maxRight = 0, 0
sum = 0
while left < right:
if height[left] < height[right]:
maxLeft = max(maxLeft, height[left])
tmp = maxLeft - height[left]
sum += tmp
left = left + 1
else:
maxRight = max(maxRight, height[right])
tmp = maxRight - height[right]
sum += tmp
right = right -1
return sum
2.3 最小覆盖子串
给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。
输入: S = “ADOBECODEBANC”, T = “ABC”
输出: “BANC”
说明:
如果 S 中不存这样的子串,则返回空字符串 “”。
如果 S 中存在这样的子串,我们保证它是唯一的答案。
滑动窗口是指维护一个window,判断窗口中是否包含need字符串中的所有字母。
滑动窗口为[left, right],首先向前移动right指针,扩大滑动窗口,直到窗口中包含所有字母。此时找到一个可行解。然后向前移动left指针,缩小滑动窗口范围,同时更新最小字符串的数量。
直到window中不再包含所有need中的字母,重新移动right指针,重复之前的步骤,
直到right指针大于字符串s的长度。
关键是如何判断窗口中包含need字符串中的所有字母。使用字典记录每个字符串中每个字母的出现次数。
window = { A: 1, B: 2, C: 3}
need = { B: 1, C: 2}
class Solution:
def containAll(self, window: dict, need: dict) -> bool:
res = True
for key,val in need.items():
count = window.get(key)
if count == None or count < val:
res = False
break
return res
def minWindow(self, s: str, t: str) -> str:
left, right = 0, 0
window, need = {}, {}
#统计need中的字母个数
for c in t:
if need.get(c) == None:
need[c] = 1
else:
need[c] = need[c] + 1
minCount = len(s) + 1
res = [left, right]
while right < len(s):
if window.get(s[right]) == None:
window[s[right]] = 1
else:
window[s[right]] = window[s[right]] + 1
#扩大窗口找到可行解
while self.containAll(window, need):
count = right - left + 1
if count < minCount:
minCount = count
res = [left, right]
#向右移动左指针,缩小窗口找到最优解
window[s[left]] = window[s[left]] - 1
left = left + 1
right = right + 1
if minCount == (len(s)+1):
return ""
return s[res[0] : res[1]+1]
动左指针,缩小窗口找到最优解
window[s[left]] = window[s[left]] - 1
left = left + 1
right = right + 1
if minCount == (len(s)+1):
return ""
return s[res[0] : res[1]+1]