方法论
回溯:无序组合;字符串切割;集合子集;有序排列;棋盘问题
- 组合:一个元素不能重复使用,用startindex控制 ,下次递归 (i+1)
【元素可无限选取,下次递归i】【原数组,包含重复元素,同一树层,去重,对数组排序used】 - 分割:
- 子集:找树的所有节点,在终止条件前,就手机结果
- 排列:used标记用过的元素,用过的不能用
def bk( , , startindex, used, path,res)
void backtracking(参数){
if (终止条件):
{收集结果
return }
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) : #处理集合的每一个元素
{处理节点
backtracking(路径,选择列表)#递归函数
回溯操作 #撤销处理节点的
}
}
动态规划
动态规划5步曲
- 确定dp数组 & 下标含义
- 递推公式
- dp数组初始化 【涉及乘法,dp[0] =1 】
- 遍历顺序
- 举例推导dp数组
背包问题
- 01背包(每件物品只能用0或1次):n件物品,最多装重量w的背包,第i件物品的重量为weight[i],价值value[i],每件物品只能装1次,求背包价值最大总和
- 二维dp, dp[i][j]:从下标[0,i]任取,放进容量为j的背包,价值最大为dp[i][j]
dp[i][j] = max( dp[i-1][j] , dp[i-1][j-weight[i]]+ value[i]) 拿不拿第i个物品- 一维dp, 先遍历物品i, 在倒序遍历容量j【倒序是为了保证:物品只拿一次】
dp[j] = max(dp[j], dp[j-weight[i]] + value[i])
- 完全背包(每个物品数量无限)
先遍历物品i,或者先正序遍历物品j都行
【组合数:外层for物品,内层for容量】【排列数:外层for容量,内层for物品】
打家劫舍 dp[i]:下标i内(包括i)的房子,最多偷的金额为dp[i]
- 2个相邻都被偷,会报警 dp[i] = max(dp[i-1], dp[i-2]+nums[i]) 其实就是nums[i]偷不偷
- 房屋成环 res1 = rob(nums[:-1]) res2 = rob(nums[1:]) res = max(res1, res2)
- 房屋树形 dp[0]代表不偷该节点,得到的最大钱, dp[1]代表偷该节点,得到的最大钱
股票买卖的最佳时机dp[i][0]第i天持有股票,所获最大钱 dp[i][1]第i天不持有股票,所获最大钱
- 只能买卖1次 dp[i][0] = max(d[i-1][0], -price[i]) dp[i][1] = max(dp[i-1][0]. dp[i-1][0]+price[i])
- 多次买卖 dp[i][0] = max(d[i-1][0], -price[i] + dp[i-1][1])
- 只能买卖2次
- 只能买卖k次 dp[i][j]【 j=0不操作, j= 2k-1第k次买入,j=2k第k次卖出 】
- 含有冷冻期 dp[i][j]【 j=0持有股票, j=1不持有股票也不在冷冻期,也不是刚卖,j=2冷冻期,j=3刚卖出 】
- 卖出有手续费 dp[i][1] = max(dp[i-1][0]. dp[i-1][0]+price[i]-fee)
子序列问题
- 在一个序列里面:求最长递增子序列长度【 dp[i]:子序列答案以nums[i]结尾的最长递增子序列的长度】
1.1 不连续 for i,遍历j<i, 如果nums[i] > nums[j],dp[i] = max(dp[i], dp[j]+1)
1.2 连续 if num[i[ > nums[i-1]:dp[i] = dp[i-1]+1
【求连续数组的最大和】 dp[i] = max(dp[i-1]+nums[i], nums[i])- 两个序列:求最长公共子序列的长度【 dp[i][j]:A[0,i-1]和B[0,j-1]最长连续公共子序列的长度】
2.1 不连续 if A[i-1]== B[j-1]:dp[i][j] = dp[i-1][j-1]+1 else:dp[i][j] = max(dp[i-1][j], dp[i][j-1])
2.2 连续 if A[i-1]== B[j-1]:dp[i][j] = dp[i-1][j-1]+1回文子串
1.两数之和
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
record = {} #key:数 value:下标
for index, i in enumerate(nums):
if target-i in record:
return [index, record[target-i]]
else:
record[i] = index
5.最长的回文子串
'''
1.dp[i][j]:区间[i:j]左闭右闭的子串,是否是回文子串
2.如果s[i] != s[j], dp[i][j] = False
如果s[i]== s[j]【1.下标相同,是回文子串 ;
2.下标相差为1,‘aa’,也是回文;
3.下标相差大于1,如果dp[i+1][j-1]也是回文,才是回文】
3.初始化:dp[i][j] = False
4.i从大到小, j从小到大
'''
class Solution:
def longestPalindrome(self, s: str) -> str:
dp = [[False] * len(s) for _ in range(len(s))]
maxlenth = 0
left = 0
right = 0
for i in range(len(s) - 1, -1, -1):
for j in range(i, len(s)):
if s[j] == s[i]:
if j - i <= 1 or dp[i + 1][j - 1] == True:
dp[i][j] = True
if dp[i][j] and j - i + 1 > maxlenth:
maxlenth = j - i + 1
left = i
right = j
return s[left:right + 1]
2.两数相加
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
cur = dummy_head = ListNode(next = None)
carry = 0
while l1 or l2 or carry :
value = (l1.val if l1 else 0)+(l2.val if l2 else 0)+carry
cur.next = ListNode(val= value%10)
carry = value//10
cur = cur.next
if l1 : #加入l1本来就是空,如果if l1.next会引发错误
l1 = l1.next
if l2 :
l2 = l2.next
return dummy_head.next
'''递归'''
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode], carry=0) -> Optional[ListNode]:
if l1 is None and l2 is None:
return ListNode(carry) if carry else None # 如果进位了,就额外创建一个节点
if l1 is None: # 确保l1不空,否则l1.val会报错
l2, l1 = l1, l2
carry += (l1.val ) + (l2.val if l2 else 0)
l1.val = carry % 10 # 每个节点保存一个数位
l1.next = self.addTwoNumbers(l1.next , l2.next if l2 else None, carry // 10) # 进位
return l1
1480.一维数组的动态和
class Solution:
def runningSum(self, nums: List[int]) -> List[int]:
dp = [0] * len(nums)
dp[0] = nums[0]
for i in range(1, len(nums)):
dp[i] = dp[i - 1] + nums[i]
return dp
4.寻找两个正序数组的中位数【难!】
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
m = len(nums1)
n = len(nums2)
target = (m+n)//2+1
l1 = l2 = 0
cur=pre = 0
for i in range(target):
pre = cur
if (l2 >= n) or (l1 < m and nums1[l1] <nums2[l2]) :
cur = nums1[l1]
l1 += 1
else:
cur = nums2[l2]
l2 += 1
if (m+n)%2 ==1 : return cur
else : return (cur+pre)/2
3.无重复字符的最长子串
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
if len(s) <= 1: return len(s)
left, right = 0 ,0
maxlen = 0
while right< len(s):
if s[right] not in s[left:right]:
maxlen = max(maxlen,right-left+1)
right += 1
else :
while s[right] in s[left:right]:
left += 1
return maxlen
704.二分查找
class Solution:
def search(self, nums: List[int], target: int) -> int:
left ,right = 0, len(nums)-1
while left <= right: # target在闭区间【left, right】
mid = (left+right)//2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid +1
else:
right = mid-1
return -1
20.有效的括号
class Solution:
def isValid(self, s: str) -> bool:
stack = []
mapping = {'(':")", "[":']',"{":"}"}
for i in s:
if i in mapping:
stack.append(mapping[i])
elif stack and stack[-1] == i: #栈顶元素和当前元素匹配
stack.pop()
else:
return False
return False if stack else True
09.用两个栈实现队列
class MyQueue:
def __init__(self):
self.stack_in = []
self.stack_out = []
def push(self, x: int) -> None:
self.stack_in.append(x)
def pop(self) -> int: #取出栈顶元素,并移除
if self.empty():
return None
if self.stack_out:
return self.stack_out.pop()
else:
for i in range(len(self.stack_in)):
self.stack_out.append(self.stack_in.pop())
return self.stack_out.pop()
def peek(self) -> int:
ans = self.pop()
self.stack_out.append(ans)
return ans
def empty(self) -> bool:
return not (self.stack_in or self.stack_out)
217.存在重复元素
集合去重后元素个数 == 原数组的元素个数
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
return len(nums) != len(set(nums))
70.爬楼梯
class Solution:
def climbStairs(self, n: int) -> int:
#dp[i] 爬到第i届的方法数
#dp[i] = dp[i-1] + dp[i-2]
# dp[0] = 0 dp[1] = 1
if n <= 1:
return n
dp = [0] * (n+1)
dp[1] = 1
dp[2] = 2
for i in range(3, n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
14.最长公共前缀
'''
内置函数zip+ set
'''
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
ans = ''
for i in list(zip(*strs)):
if len(set(i)) == 1:
ans += i[0]
else:
break
return ans
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
start = strs[0]
for index, char in enumerate(start):
for s in strs:
if index == len(s) or s[index] != char: #1.strs[s][index] != start[index]
return start[:index]
return start
42.接雨水
'''
找下标i的柱子,左边最高[:i+1]和右边最高[i: ]的柱子【要包括本柱子】
每个柱子的雨水 = min(左边最高,右边最高)-柱子高
'''
class Solution:
def trap(self, height: List[int]) -> int:
left_height = [0] * len(height)
right_height = [0] * len(height)
left_height[0] = height[0]
right_height[-1] = height[-1]
for i in range(1, len(height)):
left_height[i] = max(height[i], left_height[i-1])
for i in range(len(height)-2, -1,-1):
right_height[i] = max(right_height[i+1], height[i])
res = 0
for i in range(len(height)):
res += min(left_height[i], right_height[i])- height[i]
return res
11.盛最多水的容器
class Solution:
def maxArea(self, height: List[int]) -> int:
left, right, con = 0, len(height)-1, 0
while left < right:
con = max(con, min(height[left], height[right])* (right-left))
if height[left] < height[right]:
left += 1
else:
right -= 1
return con
72.编辑距离
'''
1. dp[i][j] : 将word1[0:i]转变成word2[0:j],最少操作数
2. 如果 word1[i-1]==word2[j-1]: dp[i][j] = dp[i-1][j-1]
如果 word[i-1]!=word2[j-1]: dp[i][j] = min(dp[i-1][j], dp[i][j-1],dp[i-1][j-1])+1
3.dp[0][j]=j dp[i][0] = i
'''
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
dp = [[0] * (len(word2)+1) for _ in range(len(word1)+1) ]
for i in range(len(word2)+1):
dp[0][i] = i
for i in range(len(word1)+1):
dp[i][0] = i
for i in range(1, len(word1)+1):
for j in range(1, len(word2)+1):
if word2[j-1]==word1[i-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i-1][j], dp[i][j-1],dp[i-1][j-1])+1
return dp[len(word1)][len(word2)]
200.岛屿数量
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
m, n = len(grid), len(grid[0])
visited = [[False] * n for _ in range(m)]
dirs = [(-1, 0), (0, 1), (1, 0), (0, -1)] # 四个方向,左,右,下,上
result = 0
def dfs(x, y):
for d in dirs:
nextx = x + d[0]
nexty = y + d[1]
if nextx < 0 or nextx >= m or nexty < 0 or nexty >= n: # 越界了,直接跳过
continue
if not visited[nextx][nexty] and grid[nextx][nexty] == '1': # 没有访问过的同时是陆地的
visited[nextx][nexty] = True
dfs(nextx, nexty)
for i in range(m):
for j in range(n):
if not visited[i][j] and grid[i][j] == '1': #没有参观过& 陆地
visited[i][j] = True
result += 1 # 遇到没访问过的陆地,+1
dfs(i, j) # 将与其链接的陆地都标记上 true
return result
146.LRU缓存
#哈希表+双向链表
#哈希表,查询时间复杂度O(1)
#双向链表存储顺序
#删除最久未使用的数据时,需要用到链表来确定,要删的结点【即要删,双向链表的头结点】
class Node:
def __init__(self, key=0, val=0):
self.key = key
self.val = val
self.next = None
self.pre = None
class LRUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.hashmap = {} #【key:node.key】【value:node】
self.head = Node() #头结点,仅仅指示位置
self.tail = Node() #尾结点,仅仅指示位置
self.head.next = self.tail
self.tail.pre = self.head
def move(self, node):
node.pre.next = node.next
node.next.pre = node.pre
def add_to_last(self, node):
self.tail.pre.next = node
node.next = self.tail
node.pre = self.tail.pre
self.tail.pre = node
def move_to_last(self, node): #把元素从链表移除,并移到链表末尾【表示最近访问过】
self.move(node)
self.add_to_last(node)
def get(self, key: int) -> int:
if key not in self.hashmap:
return -1
node = self.hashmap[key]
self.move_to_last(node) #访问了该节点
return node.val
def put(self, key: int, value: int) -> None:
if key in self.hashmap: # 如果关键字 key 已经存在,则变更其数据值 value
node = self.hashmap[key]
node.val = value
self.move_to_last(node) #访问了该节点
return
if len(self.hashmap) == self.capacity: #如果超过容量,还要加节点,则删除头结点之后的节点
del self.hashmap[self.head.next.key] #哈希表删掉node.key
self.move(self.head.next) #双向链表删掉node
node = Node(key, value)
self.hashmap[key] = node
self.add_to_last(node)
return
15.三数之和
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
res = []
nums.sort()
for i in range(len(nums)):
if nums[i] >0: #剪枝
return res
if i > 0 and nums[i] == nums[i-1]:
continue
left = i + 1
right = len(nums)-1
while left < right:
cursum = nums[i] + nums[left] + nums[right]
if cursum < 0:
left += 1
elif cursum > 0:
right -= 1
else :
res.append( [nums[i] , nums[left] , nums[right]])
while left < right and nums[right] == nums[right-1]:
right -= 1
while left < right and nums[left] == nums[left+1]:
left += 1
left += 1
right -= 1
return res
121.买卖股票的最佳时机【买卖1次】
class Solution:
def maxProfit(self, prices: List[int]) -> int:
#dp[i][0] 第i天不持有股票,获得最大利润
#dp[i][1] 第i天持有股票,最大利润
# dp[i][0] = max(dp[i-1][0], dp[i-1][1]+ price[i])
#dp[i][1] = max(dp[i-1][1], -price[i])
dp = [[0] * 2 for i in range(len(prices))]
dp[0][1] = -prices[0]
for i in range(1, len(prices)):
dp[i][0] = max(dp[i-1][0], dp[i-1][1]+ prices[i])
dp[i][1] = max(dp[i-1][1], -prices[i])
return dp[len(prices)-1][0]
9.回文数
class Solution:
def isPalindrome(self, x: int) -> bool:
return str(x) == str(x)[::-1]
53.最大子数组和
想到动态规划:当前状态和前一个状态,有关
'''
dp[i]:以nums[i]结尾的连续子数组的,最大和
dp[i] = max(dp[i-1]+nums[i], nums[i])
dp[0]=nums[0]
'''
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
dp = [0] * len(nums)
dp[0] = nums[0]
for i in range(1, len(nums)):
dp[i] = max(dp[i-1]+nums[i], nums[i])
return max(dp)
22.括号生成
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
res = []
self.backtracking(n, 0,0,[], res)
return res
def backtracking(self, n, left, right, path , res):
if left == n and right == n :
res.append(''.join(path))
return
if left < n:
path.append('(')
self.backtracking(n, left+1, right, path, res)
path.pop()
if left > right:
path.append(')')
self.backtracking(n, left, right+1, path, res)
path.pop()
如果是 elif right<n: 输出是[“((()))”]
如果是if right < n : 输出是[“((()))”,“(()())”,“(())()”,“(()))(”,“()(())”,“()()()”,“()())(”,“())(()”,“())()(”,“()))((”,“)((())”,“)(()()”,“)(())(”,“)()(()”,“)()()(”,“)())((”,“))((()”,“))(()(”,“))()((”,“)))(((”]=C36=20个
因为是有效的括号,所以必须要是left >right
6.N字形变换
class Solution:
def convert(self, s: str, numRows: int) -> str:
if numRows < 2:
return s
res = ["" for _ in range(numRows)]
i, flag = 0, -1
for char in s:
res[i] += char
if i == 0 or i == numRows - 1:
flag = -flag
i += flag
return "".join(res)
#i的变化轨迹[numrows=3]: 0,1,2,1,0,1,2,0...N字形
21.合并两个有序链表
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
if not list1 : return list2
if not list2: return list1
if list1.val < list2.val:
list1.next = self.mergeTwoLists(list1.next, list2)
return list1
else :
list2.next = self.mergeTwoLists(list1, list2.next)
return list2
18.四数之和
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
rec = []
nums.sort()
for i in range(len(nums)):
if nums[i] > target and nums[i] > 0:
return rec
if i>0 and nums[i]==nums[i-1]:
continue
for j in range(i+1, len(nums)):
if nums[i] + nums[j] > target and target > 0: #剪枝(可省)
break
if j > i+1 and nums[j] == nums[j-1]: # 去重
continue
left = j+1
right = len(nums)-1
while left < right:
s = nums[i] + nums[j] + nums[left] + nums[right]
if s == target:
rec.append([nums[i], nums[j], nums[left], nums[right]])
while left < right and nums[left] == nums[left+1]:
left += 1
while left < right and nums[right] == nums[right-1]:
right -= 1
left += 1
right -= 1
elif s < target:
left += 1
else:
right -= 1
return rec
136.只出现一次的数字
class Solution:
def singleNumber(self, nums: List[int]) -> int:
x = 0
for i in nums:
x ^= i
return x
17.09 第k个数
class Solution:
def getKthMagicNumber(self, k):
# 使用小顶堆处理(因为每次需要得到最小的数字)
heap = [1] # 第一个元素是1
for i in range(k):
res = heapq.heappop(heap) # 每次取出堆中的首元素(最小元素)
while heap and res == heap[0]: # 忽略重复的元素
heapq.heappop(heap)
heapq.heappush(heap, res * 3) # 依次将当前元素乘以3,5,7的值压入堆中
heapq.heappush(heap, res * 5)
heapq.heappush(heap, res * 7)
return res
# 1, 3, 5,7,9,15,21,15,25,35
10.正则表达式匹配
16.最接近的三数之和
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums.sort()
mindis = float('inf')
for i in range(len(nums)):
if i > 0 and nums[i] == nums[i-1]:
continue
left = i + 1
right = len(nums)-1
while left < right:
cursum = nums[i] + nums[left] + nums[right]
if cursum == target:
return target
if abs(cursum-target) < mindis:
res = [nums[i] , nums[left] , nums[right]]
mindis = abs(cursum-target)
if cursum < target:
left += 1
else:
right -= 1
return sum(res)
88. 合并两个有序数组【原地合并】【逆向排序】
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
p1, p2, p = m - 1, n - 1, m + n - 1
while p2 >= 0: # nums2 还有要合并的元素
# 如果 p1 < 0,那么走 else 分支,把 nums2 合并到 nums1 中
if p1 >= 0 and nums1[p1] > nums2[p2]:
nums1[p] = nums1[p1] # 填入 nums1[p1]
p1 -= 1
else:
nums1[p] = nums2[p2] # 填入 nums2[p1]
p2 -= 1
p -= 1 # 下一个要填入的位置
39.组合总和
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
res = []
self.backtracking(candidates, target, 0, [], res)
return res
def backtracking(self, candidates, target, startindex, path , res):
if target == 0:
res.append(path[:])
return
if target < 0: return
for i in range(startindex, len(candidates)):
path.append(candidates[i])
self.backtracking(candidates, target-candidates[i], i, path, res)
path.pop()
7. 整数反转
class Solution:
def reverse(self, x: int) -> int:
y, res = abs(x), 0
of = (1 << 31) - 1 if x > 0 else 1 << 31
while y != 0:
res = res * 10 + y % 10
if res > of:
return 0
y //= 10
return res if x > 0 else -res