双指针
283. 移动零
要求:必须在原数组上操作,且保持非零元素的顺序
思路:把0移到后面 <—> 把非0元逐个移动到前面,剩余后面的就都是0
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
idx = 0
# 扫描非0元,并把它移动到前面
for i in range(len(nums)):
if nums[i] != 0:
nums[idx] = nums[i]
idx += 1
# 剩余后面的位置就是0,直接赋值
for i in range(idx, len(nums)):
nums[i] = 0
15. 三数之和(X)
要求满足,i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0,最终三元组不重复(顺序不一样也算重复)
方法一:暴力(有几个用例超时)
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
ans = []
d = dict()
for idx,num in enumerate(nums):
d[num] = idx
lens = len(nums)
for i in range(lens):
for j in range(i+1, lens):
if i == j: continue
k = -(nums[i] + nums[j])
if k in d:
if i == d[k] or j == d[k]:
continue
tmp = sorted([nums[i],nums[j],k])
if (k in d) and (tmp not in ans):
ans.append(tmp)
return ans
方法二:官方题解
(1)首先排序list
(2)从左到右逐个遍历a
,设a
的下标为i
(3)在 [i+1, len(nums)-1]
的两端开始遍历b和c(因为i != j
,i,j != k
),
(4)判断当前是否满足等式,如果满足则后续a,b,c
的相同元素全部跳过(三元组不重复)
剪枝:固定a,去掉后续等于b的元素
---------因为a固定的情况下,如果b也是原来的值,
---------则对应的c也是原来的值,最终得到的三元组是重复的
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
ans = []
lens = len(nums)
for i in range(lens):
if i>0 and nums[i] == nums[i-1]:
continue
target = -nums[i]
l = i+1
r = lens-1
while(l < r):
s = nums[l] + nums[r]
if s == target:
ans.append([nums[i], nums[l], nums[r]])
while l<r and nums[l] == nums[l+1]:
l+=1
while l<r and nums[r] == nums[r-1]:
r-=1
l+=1
r-=1
elif s < target:
l+=1
else:
r-=1
return ans
11. 盛最多水的容器
左右指针分别从两端开始逐步向中间移动(因为两端是宽度最大的)
但是,移动左指针还是右指针需要思考:
(1)怎么样才能提高面积?——增加高度,因为宽度一开始就是最大的
于是,我们在每次移动对应的高度较小的指针,希望找到更高的高度,增加面积
class Solution:
def maxArea(self, height: List[int]) -> int:
r = len(height) - 1
l = 0
ans = 0
while l < r:
h = min(height[l], height[r])
area = (r-l) * h
ans = max(ans, area)
# 移动矮边,寻找更高边
# 才有可能在宽度减小的情况下,提高面积
if h == height[l]:
l += 1
else:
r -= 1
return ans
42. 接雨水
方法一:前缀max + 后缀max
pre_max[i]
=[0, i]
中高度最高的,suf_max[i]
=[i,len-1]
中高度最高的
==>对每个位置求能接水的体积 = 左右max中短板的高度 - 位置i
的高度)
==> v = min(pre_max[i],suf_max[i]) - height[i]
class Solution:
# 时间复杂度O(n)--3n
# 空间复杂度O(n)
def trap(self, height: List[int]) -> int:
n = len(height)
pre_max = [height[0]] * n
suf_max = [height[-1]] * n
ans = 0
for i in range(1,n):
pre_max[i] = max(pre_max[i-1], height[i])
for j in range(n-2, -1, -1):
suf_max[j] = max(suf_max[j+1], height[j])
for i in range(n):
ans += min(pre_max[i], suf_max[i]) - height[i]
return ans
方法二:双指针(优化空间,并把时间从3n优化到n)
(1)左右指针l,r
,同时维护位置l
的pre_max
和位置r
的suf_max
(2)如果 pre_max < suf_max
,说明位置l
的min(pre_max, suf_max)=pre_max
。因为位置r
的suf_max
更大了,right往中心移动只可能会加大suf_max
。此时位置l
的体积计算完了,于是left+=1
(3)同理,如果 pre_max > suf_max
,说明位置r
的min(pre_max, suf_max)=suf_max
。
class Solution:
def trap(self, height: List[int]) -> int:
n = len(height)
pre_max, suf_max = 0, 0
left, right = 0, n-1
ans = 0
while left < right:
pre_max = max(pre_max, height[left])
suf_max = max(suf_max, height[right])
if pre_max < suf_max:
ans += (pre_max -height[left])
left += 1
else:
ans += (suf_max - height[right])
right -= 1
return ans
子串
560. 和为 K 的子数组(X)
求连续序列的和为k的个数
方法一:普通前缀和——超时
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
lens = len(nums)
sum_s = [0] * (lens + 1)
ans = 0
for i in range(1, lens+1):
sum_s[i] = sum_s[i-1] + nums[i-1]
for i in range(1, lens+1):
for j in range(i, lens+1):
if sum_s[j] - sum_s[i-1] == k:
ans += 1
return ans
方法二:哈希+前缀和
-
假设k = 2,前缀和数组pre,我们知道 pre[r] = 5。
那么我们的目标就是求出满足r‘ < r, pre[r] - k = pre[r’] 的 r’ 的个数,即 pre[r’] = 3
-
如下图所示,pre(r’) = 3的前缀和存在两个,为了减少查询 r’ 的时间,我们可以使用哈希表。
hashmap(v) = k,表示前缀和为v出现的次数。- 注意!我们要计算的是当前位置 r 之前,有多少次 pre[r’] = pre[r] - k
- 为实现该目的,我们应该从左往右边计算前缀和,边统计个数。
- 这样在对每一个位置求 pre[r’] 的个数,就只包含 r’ < r的情况
-
特别的,在初始时要默认前缀和为0的个数=1
-
这样在 k = 5时,我们求 pre[r] - k = 0的个数才是正确的
from collections import defaultdict
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
pre = 0
times = {0:1}
ans = 0
for v in nums:
pre += v
ans += times.get(pre-k, 0)
times[pre] = times.get(pre, 0) + 1
return ans
239. 滑动窗口最大值
单调队列
(1)队列中只维护可能为最大值的元素
(2)当遇到4后,4左边的所有元素都不可能是最大值了,可以删除左边比4小的值
(3)即维护的队列,是从左到右递减的,于是最大值就是最左边的数
from collections import deque
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
ans = []
q = deque() # 保存nums的下标
for i, x in enumerate(nums):
# 1、入队尾(右)
while q and nums[q[-1]] <= x:
q.pop() # 维护单调性(保证入队后,左边没有<=x的元素)
q.append(i)
# 2、出队首(左)
if i - q[0] + 1 > k:
q.popleft()
# 3、记录窗口最大值
if i >= k-1:
ans.append(nums[q[0]]) # 由于单调性,队首就是最大元素
return ans
76. 最小覆盖子串
前置同类型题目:209. 长度最小的子数组(双指针+前缀和)
左右指针l,r。s记录当前窗口的和
(1)如果 s >= target,此时通过移动左指针,缩小窗口,找到满足 >= target的更小区间
(2)当区间内的和小于 target 时,移动右指针,直到满足后,再重复(1)
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
n = len(nums)
s = 0
ans = inf
left = 0
for right, x in enumerate(nums):
s += x
while s >= target:
ans = min(ans, right - left + 1)
s -= nums[left]
left += 1
return ans if ans <= n else 0
字符串的覆盖 <==> 子串中每个字符出现的次数都 >= 目标串中每个字符出现的次数
(该操作在python3.10及以上可以使用Counter来实现,直接对比两个Counter对象就是比较相同键的值的大小)
方法一:类比最小子数组的方式
from collections import Counter
class Solution:
def minWindow(self, s: str, t: str) -> str:
ans_left, ans_right = -1, len(s) # 记录最小子串的idx
left = 0
cnt_s = Counter()
cnt_t = Counter(t)
for right, c in enumerate(s):
cnt_s[c] += 1
while cnt_s >= cnt_t:
if right - left < ans_right - ans_left:
ans_left, ans_right = left, right
cnt_s[ s[left] ] -= 1
left += 1
return s[ ans_left: ans_right+1] if ans_left != -1 else ""
优化方法一
存在的问题:方法一中,右指针每次移动都会判断子串是否是 t 的覆盖,十分耗时
优化方式:修改覆盖的判断方式,使用kinds,记录窗口内的元素数是否满足覆盖
from collections import Counter
class Solution:
def minWindow(self, s: str, t: str) -> str:
ans_left, ans_right = -1, len(s) # 记录最小子串的idx
left = 0
cnt_s = Counter()
cnt_t = Counter(t)
n = len(cnt_t)
kinds = 0 # 子串中val >= cnt_t中key的val的个数
for right, c in enumerate(s):
cnt_s[c] += 1
if cnt_s[c] == cnt_t[c]:
kinds += 1
while kinds == n: # 满足覆盖
if right - left < ans_right - ans_left:
ans_left, ans_right = left, right
x = s[left]
# 当前val相等,说明移除left后,子串的val就不满足目标了,于是 kinds -= 1
if cnt_s[x] == cnt_t[x]:
kinds -= 1
cnt_s[ x ] -= 1
left += 1
return s[ ans_left: ans_right+1] if ans_left != -1 else ""