目录
167. 两数之和 II - 输入有序数组
思路:
- 一般双指针的主要思路是先找到暴力解法,然后考虑有没有单调性之类的性质可以利用,再进行优化
- 使用i,j两个指针,初始位置在0和数组末尾,若相加的结果大于target则说明当前的结果需要减小,则j往前移;若相加的结果小于target则说明当前的结果需要增大,则i往后移。
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
n = len(numbers)
i, j = 0, n-1
while i<j:
if numbers[i] + numbers[j] < target:
i += 1
elif numbers[i] + numbers[j] > target:
j -= 1
else:
return [i+1, j+1]
88. 合并两个有序数组
思路:
- 这道题就是基本的归并思想
- 由于题目要求的是把结果存入nums1中,则要注意不能从前开始比较合并,要从后面开始
- 这里需要注意的是如果p1,p2赋初值为m-1,n-1,可能就取不到下标为0的那个数
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
# nums1的最大数指针 nums2的最大数指针 结果集的最大数指针
p1, p2, p3 = m, n, m+n
while p1 and p2:
if nums1[p1-1] >= nums2[p2-1]:
nums1[p3-1] = nums1[p1-1]
p1 -= 1
else:
nums1[p3-1] = nums2[p2-1]
p2 -= 1
p3 -= 1
while p2:
nums1[p3-1] = nums2[p2-1]
p2 -= 1
p3 -= 1
26. 删除排序数组中的重复项
思路:
- 由于不能申请额外空间,考虑直接在数组上进行操作。
- 设置两个指针,一个指向当前需要存储数的位置k,一个对数组进行扫描i
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
if not nums:
return 0
k = 1
for i in range(1, len(nums)):
if nums[i] != nums[i-1]:
nums[k] = nums[i]
k += 1
return k
76. 最小覆盖子串
思路:
- 首先一样的步骤,考虑暴力解法,会需要O(n^2)的时间复杂度
- 然后考虑单调性,我们可以使用两个指针,i,j指示答案字符串的首尾。当i逐渐往后移,j必定只能往后增加,不可能往前,因为那样会包含了最佳答案在里面,不可能得到最短的。
- 这样的话,维护一个hash表,记录t字符串每个字符出现的次数,最后考虑的就是i依次往后遍历,j从起点开始,i往后一格,将hash对应位置-1,j也往后,若当前所指的位置hash表中对应结果小于0了,说明这个字符不在答案中,j往后移
- 再维护一个答案t所需要几个不同的字符,当都得到了,比较一下当前s[j:i+1]的长度,用res记录最短的即是答案
class Solution:
def minWindow(self, s: str, t: str) -> str:
hash_table = {}
for ch in t:
hash_table[ch] = hash_table.get(ch, 0) + 1
cnt = len(hash_table.items())
res = ""
c, j = 0, 0
for i in range(len(s)):
num = hash_table.get(s[i], 0)
if num == 1:
c += 1
elif num==0:
hash_table[s[i]] = hash_table.get(s[i], 0)
hash_table[s[i]] -= 1
# j从前往后
while hash_table[s[j]] < 0 and j<i:
hash_table[s[j]] += 1
j += 1
if c==cnt:
if not res or len(res)>i-j+1:
res = s[j:i+1]
return res
32. 最长有效括号
思路:
- 首先需要记住括号序列的一个重要性质:假如将(当做数字1,)当做数字-1;则合法的括号序列前缀和一定是>=0的,并且整个序列的最终结果肯定是0
- start枚举当前这一段的开头,cnt记录前缀和。
- 若是当前的cnt<0了,说明这一段是不合法的,start=i+1,cnt=0
- 若是当前的cnt>0,继续做
- 若是当前的cnt==0,说明当前的这一段是合法的,比较和res的长度,若大于则更新res
- 注意,为了防止出现((())这种情况,也就是最后的和不会等于0的情况,所以还需要反过来再做一遍
- (和)的ascii码二进制相差一位,可以直接异或处理
class Solution:
def longestValidParentheses(self, s: str) -> int:
reverse_s = list(s[::-1])
for index, ch in enumerate(reverse_s):
reverse_s[index] = chr(ord(ch)^1)
return max(self.work(s), self.work("".join(reverse_s)))
def work(self, s):
"""
返回当前子串的最长长度
"""
start, cnt = 0, 0
n = len(s)
res = 0
for i in range(n):
if s[i] == "(":
cnt += 1
else:
cnt -= 1
if cnt < 0:
start = i+1
cnt = 0
elif cnt == 0:
res = max(res, i-start+1)
return res
155. 最小栈
思路:
- 这个题有点类似于前缀和,始终要维护一个前缀和的最小数。
- 用两个栈,一个是模拟栈操作的,一个是始终保存当前栈中最小的数字
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.stack = []
self.stack_min = []
def push(self, x: int) -> None:
self.stack.append(x)
if not self.stack_min:
self.stack_min.append(x)
else:
self.stack_min.append(min(self.stack_min[-1], x))
def pop(self) -> None:
self.stack.pop()
self.stack_min.pop()
def top(self) -> int:
return self.stack[-1]
def getMin(self) -> int:
return self.stack_min[-1]
# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(x)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.getMin()
- tips:单调栈(查找每个数左侧第一个比它小的数);单调队列=滑动窗口内的最值
84. 柱状图中最大的矩形
思路:
- 如何枚举所有的情况?枚举所有柱形的上边界,作为整个矩形的上边界
- 然后找出左右边界,找出左边离它最近,比它小且最高的矩形;找出右边离它最近,比它小且最高的矩形。
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
n = len(heights)
left, right = [0]*n, [0]*n
stk = []
for i in range(n):
while stk and heights[stk[-1]]>=heights[i]:
stk.pop()
if not stk:
left[i] = -1
else:
left[i] = stk[-1]
stk.append(i)
while stk:
stk.pop()
for i in range(n-1, -1, -1):
while stk and heights[stk[-1]]>=heights[i]:
stk.pop()
if not stk:
right[i] = n
else:
right[i] = stk[-1]
stk.append(i)
res = 0
for i in range(n):
res = max(res, heights[i]*(right[i]-left[i]-1))
return res
42. 接雨水
思路:
- 这道题用栈保存当前最小的高度,当出现一个比栈顶高的高度,则弹出进行这一小部分的计算。
- 整体接雨水的方式就是一小块一小块加起来
class Solution:
def trap(self, height: List[int]) -> int:
stk = []
res = 0
for i in range(len(height)):
last = 0
while stk and height[stk[-1]] <= height[i]:
t = stk.pop()
res += (i-t-1) * (height[t]-last)
last = height[t]
if stk:
res += (i-stk[-1]-1)*(height[i]-last)
stk.append(i)
return res
239. 滑动窗口最大值
思路:
- 滑动窗口的最大值主要思路就是保持一个队列,队列中始终保持一个递减的队列。
- 队头始终是当前窗口最大值,队尾始终是当前窗口最小值。
- 当队头超过了窗口范围弹出即可。
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
q, res = [], []
for i in range(len(nums)):
if q and i-k>=q[0]:
q.pop(0)
while q and nums[i]>=nums[q[-1]]:
q.pop()
q.append(i)
if i-k+1>=0:
res.append(nums[q[0]])
return res
918. 环形子数组的最大和
思路:
- 这类环形数组题,可以考虑的tips是将其展开为一排,展开为2n的一排,这样就变成了控制一个长度为n的滑动窗口,求窗口内的最大值
- 求前缀和,sum_[i]-sum_[j]就是j+1...i的和,用res记录最大值,时刻更新即可
class Solution:
def maxSubarraySumCircular(self, A: List[int]) -> int:
n = len(A)
for i in range(n):
A.append(A[i])
sum_ = [0]*(2*n)
for i in range(2*n):
sum_[i] = sum_[i-1] + A[i]
queue = [0]
res = sum_[0]
for i in range(1, 2*n):
if queue and i - n>queue[0]:
queue.pop(0)
if queue:
res = max(res, sum_[i] - sum_[queue[0]])
while queue and sum_[i] <= sum_[queue[-1]]:
queue.pop()
queue.append(i)
return res