高频题总结
- 高频题总结
- 泛洪法
- 二分查找
- 滑动窗口求解子串问题
- 单调队列解决滑动窗口最大值
- 子序列问题
- 位运算相关
- 接雨水
- 二分图
- 寻找两个正序数组的中位数
- 三数之和
- 括号生成
- 合并K个升序链表
- 下一个排列
- 最长有效括号
- 跳跃游戏
- 合并区间
- 不同路径
- 最小路径和
- 爬楼梯
- 颜色分类
- 移动零
- 零钱兑换
- 最小栈
- 每日温度
- 二叉树的直径
- 字符串解码
- Top k 排序
- 前 K 个高频元素
- 不同二叉树的棵数
- 二叉树展开为链表
- 合并二叉树
- 寻找重复数
- 单词拆分
- 二叉树中的最大路径和
- 最大正方形
- 除自身以外数组的乘积
- 打家劫舍III
- 回文子串
- 课程表
- 完全平方数
- 最佳买卖股票时机含冷冻期
- 分割等和子集
- 把二叉搜索树转换为累加树
- 路径总和III
- 根据身高重建队列
- 找到所有数组中消失的数字
- 二叉树的序列化与反序列化
- 208. 实现 Trie (前缀树)
- 240.搜索二维矩阵II
- 找到字符串中所有字母异位词
- 目标和
- 最短无序连续子数组
- 柱状图中的最大矩形
- 戳气球
泛洪法
class Solution:
origColor = None
def floodFill(self, image: List[List[int]], sr: int, sc: int, newColor: int) -> List[List[int]]:
if sr >= len(image) or sr < 0 or sc >= len(image[0]) or sc < 0:
return
if self.origColor is None:
self.origColor = image[sr][sc]
## 检查是否已访问
if image[sr][sc] < 0:
return
## 旧的颜色值是否符合要求
if image[sr][sc]==self.origColor:
## 标记为已访问
image[sr][sc] = -1
self.floodFill(image, sr+1, sc, newColor)
self.floodFill(image, sr-1, sc, newColor)
self.floodFill(image, sr, sc+1, newColor)
self.floodFill(image, sr, sc-1, newColor)
image[sr][sc] = newColor
return image
二分查找
需要考虑细节:
1、循环条件是否应该加等号(结束循环时搜索区间是否为空)
2、mid是否应该加1 (无重复元素数组的值查找问题,可以+-1;重复元素数组的边界查找问题中,进行讨论)
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回-1
def binarySearch(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1
while left <= right : ## 循环终止条件:left > right 即搜索区间为空
mid = (right + left) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
寻找左侧边界问题
def binarySearch(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums)
while left < right : ## 循环终止条件:left == right ,漏掉nums[left]
mid = (right + left) // 2
if nums[mid] == target:
right = mid
elif nums[mid] < target:
left = mid + 1
elif nums[mid] > target:
right = mid
if left > len(nums):
return -1
if nums[left] == target:
return left
else:
return -1
跳出训练时,left 变量的值)取值区间是闭区间 [0, nums.length]。
为什么 left = mid + 1,right = mid ?和之前的算法不一样?
因为「搜索区间」是 [left, right) 左闭右开,所以当 nums[mid] 被检测之后,下一步的搜索区间应该去掉 mid 分割成两个区间,即 [left, mid) 或 [mid + 1, right)。
当nums[mid] == target时的处理方式:
if nums[mid] == target:
right = mid
可见,找到 target 时不要立即返回,而是缩小「搜索区间」的上界 right,在区间 [left, mid) 中继续搜索,即不断向左收缩,达到锁定左侧边界的目的。
总结:
第一个,最基本的二分查找算法:
因为我们初始化 right = nums.length - 1
所以决定了我们的「搜索区间」是 [left, right]
所以决定了 while (left <= right)
同时也决定了 left = mid+1 和 right = mid-1
第二个,寻找左侧边界的二分查找:
因为我们初始化 right = nums.length
所以决定了我们的「搜索区间」是 [left, right)
所以决定了 while (left < right)
同时也决定了 left = mid+1 和 right = mid
因为我们需找到 target 的最左侧索引
所以当 nums[mid] == target 时不要立即返回
而要收紧右侧边界以锁定左侧边界
因为我们只需找到一个 target 的索引即可
所以当 nums[mid] == target 时可以立即返回
第三个,寻找右侧边界的二分查找:
因为我们初始化 right = nums.length
所以决定了我们的「搜索区间」是 [left, right)
所以决定了 while (left < right)
同时也决定了 left = mid+1 和 right = mid
因为我们需找到 target 的最右侧索引
所以当 nums[mid] == target 时不要立即返回
而要收紧左侧边界以锁定右侧边界
又因为收紧左侧边界时必须 left = mid + 1
所以最后无论返回 left 还是 right,必须减一
滑动窗口求解子串问题
LeetCode 76. 最小覆盖子串
给你一个字符串 S、一个字符串 T 。请你设计一种算法,可以在 O(n) 的时间复杂度内,从字符串 S 里面找出:包含 T 所有字符的最小子串。
示例:
输入:S = "ADOBECODEBANC", T = "ABC"
输出:"BANC"
提示:
如果 S 中不存这样的子串,则返回空字符串 ""。
如果 S 中存在这样的子串,我们保证它是唯一的答案。
class Solution:
def minWindow(self, s: str, t: str) -> str:
count = {} ## 滑动窗口中各个目标字符的个数
needs = {} ## 需要满足的各个字符的个数
match = 0 ## 目前已经字符数
ans = ''
for c in t:
if c not in needs:
needs[c] = 0
count[c] = 0
needs[c] += 1
left, right = 0, 0
while right < len(s): ## 结束条件:right == len(s) - 1
c = s[right]
if c in needs:
count[c] += 1
if count[c] == needs[c]:
match += 1
right += 1 ## 移动右指针
while match == len(needs):
## 当ans为空或出现长度更短的子串时更新ans
if ans == '' or right - left < len(ans):
ans = s[left:right]
c = s[left]
if c in needs:
count[c]-=1
if needs[c] > count[c]:
match -= 1
left += 1 ## 移动左指针
return ans
给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。
说明:
字母异位词指字母相同,但排列不同的字符串。
不考虑答案输出的顺序。
示例 1:
输入:
s: "cbaebabacd" p: "abc"
输出:
[0, 6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的字母异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的字母异位词。
示例 2:
输入:
s: "abab" p: "ab"
输出:
[0, 1, 2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的字母异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的字母异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的字母异位词。
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
count = {} ## 滑动窗口中各个目标字符的个数
needs = {} ## 需要满足的各个字符的个数
match = 0 ## 目前已经字符数
ans = []
for c in p:
if c not in needs:
needs[c] = 0
count[c] = 0
needs[c] += 1
left, right = 0, 0
while right < len(s): ## 结束条件:right == len(s) - 1
c = s[right]
if c in needs:
count[c] += 1
if count[c] == needs[c]:
match += 1
right += 1 ## 移动右指针
#print(s[left:right],match,left,right)
while match == len(needs):
## 当ans为空或出现长度更短的子串时更新ans
if right - left == len(p):
ans.append(left)
c = s[left]
if c in needs:
count[c]-=1
if needs[c] > count[c]:
match -= 1
left += 1 ## 移动左指针
return ans
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
count = {}
left,right = 0 ,0
ans = 0
while right < len(s):
#print(s[left:right],left,right)
c = s[right]
if c not in count:
count[c] = 0
count[c]+=1
right+=1
while count[c] > 1:
tmp = s[left]
count[tmp]-=1
if count[tmp] == 0:
del count[tmp]
left+=1
ans = max(ans,right - left)
return ans
解题模板总结:
count = {}
left, right = 0, 0
while right < len(s):
将s[right] 更新count
right+=1
while 某种条件:
从count中移除s[left]
left+=1
单调队列解决滑动窗口最大值
子序列问题
首先注意区分子序列和子串、子数组的差异:子串和子数组是连续的序列,而子数组是不连续的序列。
题目:
LeetCode 300. 最长上升子序列 (1维dp)
LeetCode 72. 编辑距离 (2维dp)
LeetCode 1143. 最长公共子序列 (2维dp)
LeetCode 516. 最长回文子序列 (2维dp)
以LeetCode 516. 最长回文子序列为例:
定义dp[i][j]表示在子串s[i:j]中的最长回文子序列的长度。
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
dp = [[0 for _ in range(len(s))] for __ in range(len(s))]
for i in range(len(s)):
dp[i][i] = 1
for i in range(len(s)-1,-1,-1):
for j in range(i+1,len(s)):
if s[i] == s[j]:
dp[i][j] = dp[i+1][j-1] + 2
else:
dp[i][j] = max(dp[i+1][j], dp[i][j-1])
#print(i,j,s[i:j+1],dp[i][j])
return dp[0][len(s)-1]
位运算相关
## 字母转换(对应ASCII码的位运算)
('a' | ' ') = 'a'
('A' | ' ') = 'a'
('b' & '_') = 'B'
('B' & '_') = 'B'
('d' ^ ' ') = 'D'
('D' ^ ' ') = 'd'
## 交换两个数
x = x ^ y
y = x ^ y
x = x ^ y
## 消除数字 n 的二进制表示中的最后一个 1
n&(n-1)
bin(92) = '0b1011100'
bin(91) = '0b1011011'
bin(88) = '0b1011000' = 92&91
LeetCode 268. 缺失数字
给定一个包含 0, 1, 2, ..., n 中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。
示例 1:
输入: [3,0,1]
输出: 2
示例 2:
输入: [9,6,4,2,3,5,7,0,1]
输出: 8
说明:
你的算法应具有线性时间复杂度。你能否仅使用额外常数空间来实现?
## 用索引和元素进行位运算,最后得到的就是缺少的元素。
class Solution:
def missingNumber(self, nums: List[int]) -> int:
ans = 0
ans = ans ^ len(nums)
for i in range(len(nums)):
ans = ans ^ i ^ nums[i]
return ans
class Solution:
def missingNumber(self, nums: List[int]) -> int:
nums_sum = sum(nums)
nums_len = len(nums)
return int(nums_len*(nums_len+1)/2) - nums_sum
接雨水
LeetCode 42. 接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
class Solution:
def trap(self, height: List[int]) -> int:
left = []
right = []
for i in range(len(height)):
if not left:
left.append(height[i])
else:
left.append(max(left[-1], height[i]))
for i in range(len(height)-1,-1,-1):
if not right:
right.append(height[i])
else:
right.insert(0,max(right[0], height[i]))
ans = 0
for i in range(len(height)):
ans += min(left[i], right[i]) - height[i]
#print(left)
#print(right)
return ans
## 使用双指针进一步优化:时间复杂度O(n),空间复杂度O(1)
class Solution:
def trap(self, height: List[int]) -> int:
if not height: return 0
left, right = 0, len(height) - 1
## l_max = max(height[:left+1]), r_max = max(height[right:])
l_max, r_max = height[left], height[right]
ans = 0
while left <= right:
#print(left,right,l_max,r_max)
if l_max < height[left]:
l_max = height[left]
if r_max < height[right]:
r_max = height[right]
if l_max < r_max: ## left 是短板
ans += l_max - height[left]
left+=1
else: ## right 是短板
ans += r_max - height[right]
right-=1
return ans
二分图
LeetCode 886. 可能的二分法
以不喜欢的关系为边,可以分为两部分等价于给图中每个点涂两种颜色,保证相邻的点不同色
DFS,加入tag变量,保证与其连接的点的tag与其相反,若不能,则返回False
class Solution:
def possibleBipartition(self, N: int, dislikes: List[List[int]]) -> bool:
lj = [[] for _ in range(N+1)]
for dis in dislikes:
lj[dis[0]].append(dis[1])
lj[dis[1]].append(dis[0])
v = [False] * (N+1)
tags = [-1] * (N+1)
def dfs(start, tag):
tags[start] = tag
v[start] = True
tag = (tag+1)%2
for end in lj[start]: ## 遍历邻居节点
if v[end]:
if tags[end] != tag:
return False
else:
if not dfs(end, tag):
return False
return True
for i in range(1, N+1):
if not v[i]:
if not dfs(i, 0): ## 可能存在多个子图,以tag等于0开始,一次dfs搜索会遍历完子图里的所有节点,开始搜索另一个子图时以tag等于0开始,
return False
return True
改编题:
给定一组 N 人(编号为 1, 2, ..., N), 我们想把每个人分进任意大小的两组。
每个人都可能喜欢其他人,那么他们应该属于同一组。
形式上,如果 likes[i] = [a, b],表示应该将编号为 a 和 b 的人归入同一组。
求最后最多能分多少组。
寻找两个正序数组的中位数
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
"""
二分查找长度较短数组的切分点。
假设:Lmax1,cut1,Rmin1; Lmax2,cut2,Rmin2
性质:max(Lmax1, Lmax2) < min(Rmin1, Rmin2)
如果 Lmax1 > Rmin2 则在左边区域查找
如果 Lmax2 > Rmin1 则在右边区域查找
"""
if len(nums1) > len(nums2):
nums1, nums2 = nums2, nums1
l1, l2 = len(nums1), len(nums2)
Lmax1 = Rmin1 = Lmax2 = Rmin1 = 0
cut1 = cut2 = 0
"""
l1可能为奇数,此时中位数就是最中间的数,也可能偶数,此时中位数为最中间的两个数的平价数,
为了便于处理,将nums1进行扩充,例如:
[3,5,9,10] -> [3,3,5,5,9,9,10,10,#] ,len=9 此时中位数是num1[4//2]=5和num1[5//2]=9的平均数
[3,5,9] -> [3,3,5,5,9,9,#] ,len=7 此时中位数是num1[3//2]=5和num1[2//2]=5的平均数
即经过扩充处理以后,无论是奇数数组还是偶数数组,计算中位数的方法都是一致的了。
"""
start = 0
end = 2*l1
while start <= end:
cut1 = (start + end)//2
cut2 = l1 + l2 - cut1
Lmax1 = nums1[(cut1 - 1)//2] if cut1!=0 else -float('inf')
Rmin1 = nums1[cut1//2] if cut1!=2*l1 else float('inf')
Lmax2 = nums2[(cut2 - 1)//2] if cut2!=0 else -float('inf')
Rmin2 = nums2[cut2//2] if cut2!=2*l2 else float('inf')
if Lmax1 > Rmin2:
end = cut1 - 1 # LMax1>RMin2,所以cut1要往左移1位,在左半区找,更新上限end=cut1-1
elif Lmax2 > Rmin1:
start = cut1 + 1
else:
break
return (max(Lmax1, Lmax2) + min(Rmin1, Rmin2))/2
三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
要点:
先排序,固定一个元素,用双指针查找剩下的两个元素;
不能包含重复三元组;
剪枝搜索。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums = sorted(nums)
#print(nums)
ans = []
for flag in range(len(nums)-2):
flag_value = nums[flag]
if flag_value > 0:
break
if flag > 0 and nums[flag]== nums[flag - 1]: ## 检查重复
continue
left, right = flag+1, len(nums)-1
while left < right:
if nums[left] + nums[right] + flag_value > 0:
right -=1
elif nums[left] + nums[right] + flag_value < 0:
left +=1
else:
#print(flag,left,right)
ans.append([flag_value, 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
return ans
括号生成
LeetCode 22. 括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例:
输入:n = 3
输出:[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
def helper(ans,cur,left,right):
if left==right and right==0:
ans.append(cur)
if left <= right:
if left > 0:
helper(ans, cur+'(', left-1, right)
if right > 0:
helper(ans, cur+')', left, right-1)
ans = []
helper(ans,'',n,n)
return ans
合并K个升序链表
LeetCode 23
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:
输入:lists = []
输出:[]
示例 3:
输入:lists = [[]]
输出:[]
提示:
k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i] 按 升序 排列
lists[i].length 的总和不超过 10^4
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
if not lists:
return None
import heapq
queue = []
new_head = ListNode(None)
cur = new_head
for i in range(len(lists)):
head = lists[i]
if head:
"""
这里如果不加i,则会报错:
TypeError: '<' not supported between instances of 'ListNode' and 'ListNode'
因为当元组里第一个元素相等时,会对第二个元素进行比较,此时需要加入序号i进行区分。
"""
heapq.heappush(queue,(head.val,i,head))
while queue:
_, i, node = heapq.heappop(queue)
cur.next = node
cur = node
if node.next:
heapq.heappush(queue, (node.next.val,i , node.next))
cur.next = None
return new_head.next
下一个排列
leetcode 31
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
"""
示例:
输入:[3,8,4,9,5,2]
输出:[3,8,5,2,4,9]
思路:
从后往前寻找第一个转折点,设为j,可知j到末尾的数字都是降序的,
设j前1个的数字为i,j后1个数字为k,从末尾到k找到一个大于i的数字,记作m,
交换m与i,然后,将j(包含j)之后的数字倒置。
如果没有找到转折点,则将整个数组倒置。
"""
def reverse(s, l, r): ## 这里如果传入的是数组切片,更改的是切片的拷贝,并没有更改原始数组,不能满足题目原地更改的要求。
while l < r:
s[l],s[r] = s[r], s[l]
l+=1
r-=1
return
if len(nums) < 2: ## 边界输入:数组长度小于2
return
for j in range(len(nums)-1, 0, -1):
if nums[j-1] < nums[j]:
break
if j==1 and nums[0] > nums[1]:
reverse(nums,0,len(nums)-1)
return
i = j - 1
for m in range(len(nums)-1 ,j-1,-1): ## 包括j
if nums[m] > nums[i]:
nums[i], nums[m] = nums[m], nums[i]
break
reverse(nums,j,len(nums)-1)
return
最长有效括号
LeetCode 32
给定一个只包含 '(' 和 ')' 的字符串,找出最长的包含有效括号的子串的长度。
示例 1:
输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"
示例 2:
输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"
class Solution:
def longestValidParentheses(self, s: str) -> int:
"""
设dp[i]表示以下标i结尾的有效括号长度。
如果s[i]==')' 且 s[i-1]=='(':
dp[i] = dp[i-2]+2
如果s[i]==')' 且 s[i-1]==')',例如:s=')(()())' ,这里与i配对的是i−dp[i−1]−1
dp[i] = dp[i-1] + 2 + dp[i-dp[i-1]-2]
"""
ans = 0
dp = [0 for i in range(len(s))]
for i in range(1,len(s)):
if s[i] == ')' :
if s[i-1] == '(':
dp[i] = dp[i-2] + 2
elif s[i-1] == ')' and i-dp[i-1]-1 >=0 and s[i-dp[i-1]-1] =='(':
dp[i] = dp[i-1] + 2 + dp[i -dp[i-1] -2]
ans = max(ans, dp[i])
return ans
跳跃游戏
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 1:
输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
示例 2:
输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
class Solution:
def canJump(self, nums: List[int]) -> bool:
n, max_dist = len(nums), 0
for i in range(n):
if i <= max_dist: ## 检查当前位置能否到达
max_dist = max(max_dist, nums[i] + i) ## 更新最远到达位置
if max_dist >= n-1: ## 如果能达到的最远位置已经大于等于n-1,则满足要求 (注意边界条件)
return True
else:
return False
return False
合并区间
LeetCode 56
给出一个区间的集合,请合并所有重叠的区间。
示例 1:
输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入: intervals = [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals = sorted(intervals, key = lambda x:x[0])
#print(intervals)
ans = []
for item in intervals:
if len(ans) == 0:
ans.append(item)
else:
if ans[-1][1] >= item[0]: ## 由于已经排序,ans[-1][0] 一定小于item[0]
ans[-1][1] = max(item[1],ans[-1][1])
else: ## 没有重叠,作为新的区间加入ans
ans.append(item)
return ans
不同路径
LeetCode 62
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
dp = [[0 for __ in range(m+1)] for _ in range(n+1)]
for i in range(1,m+1):
for j in range(1,n+1):
if i==1 and j==1:
dp[j][i] = 1
else:
dp[j][i] = dp[j-1][i] + dp[j][i-1]
return dp[n][m]
最小路径和
LeetCode 62
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
dp = [ [0 for _ in range(n)] for __ in range(m)]
for i in range(m):
for j in range(n):
if i==0 and j==0: ## 初始顶点
dp[i][j] = grid[i][j]
elif i==0: ## 左边界情况
dp[i][j] = grid[i][j] + dp[i][j-1]
elif j==0: ## 右边界情况
dp[i][j] = grid[i][j] + dp[i-1][j]
else: ## 其它通用情况下的转移方程
dp[i][j] = min(grid[i][j] + dp[i][j-1], grid[i][j] + dp[i-1][j])
print(dp)
return dp[-1][-1]
爬楼梯
LeetCode 70
class Solution:
def climbStairs(self, n: int) -> int:
if n == 0: return 1
if n == 1: return 1
if n == 2: return 2
dp_0, dp_1 = 1, 1
for i in range(2,n+1):
ans = dp_0 + dp_1
dp_0, dp_1 = dp_1, ans
return ans
颜色分类
LeetCode 75
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意:
不能使用代码库中的排序函数来解决这道题。
示例:
输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
进阶:
一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。
你能想出一个仅使用常数空间的一趟扫描算法吗?
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
l,m,r = 0, 0, len(nums)-1
while m <= r:
if nums[m] == 0:
nums[l], nums[m] = nums[m], nums[l]
l+=1
m+=1
elif nums[m] == 1:
m+=1
else:
nums[m], nums[r] = nums[r], nums[m]
r-=1
移动零
LeetCode 283
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
p = 0
for i in range(len(nums)):
if nums[i]!=0:
nums[p], nums[i] = nums[i], nums[p]
p+=1
零钱兑换
LeetCode 322
见动态规划总结
最小栈
LeetCode 155
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.stack = []
self.min_stack = []
def push(self, x: int) -> None:
self.stack.append(x)
if len(self.min_stack)==0:
self.min_stack.append(x)
elif x < self.min_stack[-1]:
self.min_stack.append(x)
else:
self.min_stack.append(self.min_stack[-1])
def pop(self) -> None:
self.min_stack.pop()
return self.stack.pop()
def top(self) -> int:
return self.stack[-1]
def getMin(self) -> int:
return self.min_stack[-1]
每日温度
LeetCode 739
请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。
class Solution:
def dailyTemperatures(self, T: List[int]) -> List[int]:
"""
思路:从前到后,单调栈。
"""
stack = []
ans = [0 for i in range(len(T))]
for i in range(len(T)-1,-1,-1):
while len(stack) > 0 and T[stack[-1]] <= T[i]: ## 注意等于情况
stack.pop()
stack.append(i)
if len(stack)==1:
ans[i] = 0
else:
ans[i] = stack[-2] - stack[-1]
stack_value = [T[j] for j in stack]
print('i=%s,T[%s]=%s'%(i,i,T[i]),'stack=',stack,'stack_value=',stack_value)
print('ans=',ans)
return ans
"""
input:[89,62,70,58,47,47,46,76,100,70]
output:[8,1,5,4,3,2,1,1,0,0]
Stdout:
i=9,T[9]=70 stack= [9] stack_value= [70]
ans= [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
i=8,T[8]=100 stack= [8] stack_value= [100]
ans= [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
i=7,T[7]=76 stack= [8, 7] stack_value= [100, 76]
ans= [0, 0, 0, 0, 0, 0, 0, 1, 0, 0]
i=6,T[6]=46 stack= [8, 7, 6] stack_value= [100, 76, 46]
ans= [0, 0, 0, 0, 0, 0, 1, 1, 0, 0]
i=5,T[5]=47 stack= [8, 7, 5] stack_value= [100, 76, 47]
ans= [0, 0, 0, 0, 0, 2, 1, 1, 0, 0]
i=4,T[4]=47 stack= [8, 7, 4] stack_value= [100, 76, 47]
ans= [0, 0, 0, 0, 3, 2, 1, 1, 0, 0]
i=3,T[3]=58 stack= [8, 7, 3] stack_value= [100, 76, 58]
ans= [0, 0, 0, 4, 3, 2, 1, 1, 0, 0]
i=2,T[2]=70 stack= [8, 7, 2] stack_value= [100, 76, 70]
ans= [0, 0, 5, 4, 3, 2, 1, 1, 0, 0]
i=1,T[1]=62 stack= [8, 7, 2, 1] stack_value= [100, 76, 70, 62]
ans= [0, 1, 5, 4, 3, 2, 1, 1, 0, 0]
i=0,T[0]=89 stack= [8, 0] stack_value= [100, 89]
ans= [8, 1, 5, 4, 3, 2, 1, 1, 0, 0]
"""
二叉树的直径
LeetCode 543
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
示例 :
给定二叉树
1
/
2 3
/
4 5
返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
"""
思路:
二叉树的直径是任意两个节点路径长度的最大值,
也即是子树左子树高度与右子树高度和的最大值,递归搜索,
用类属性变量记录搜索到的最大值。
"""
class Solution:
diameter = 0
def diameterOfBinaryTree(self, root: TreeNode) -> int:
self.heightofTree(root)
return self.diameter
def heightofTree(self,root):
if root is None:
return 0
left = self.heightofTree(root.left)
right = self.heightofTree(root.right)
diameter = left + right
if diameter > self.diameter:
self.diameter = diameter
return max(left, right) + 1
字符串解码
LeetCode 394
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
示例 1:
输入:s = "3[a]2[bc]"
输出:"aaabcbc"
示例 2:
输入:s = "3[a2[c]]"
输出:"accaccacc"
示例 3:
输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"
示例 4:
输入:s = "abc3[cd]xyz"
输出:"abccdcdcdxyz"
class Solution:
def decodeString(self, s: str) -> str:
"""
注意:存在多重嵌套编码,
每个字符有四种可能:数字,字母,左括号,右括号
"""
ans, n, stack = "", 0, []
for c in s:
if '0' <= c <= '9':
n = 10*n + int(c) ## k为正整数,可能大于10
elif c =='[':
stack.append((ans, n))
ans, n = "", 0
elif c == ']':
last_ans, cur_n = stack.pop()
ans = last_ans + cur_n*ans
else:
ans = ans + c
#print([ans], n, stack)
return ans
Top k 排序
LeetCode 215
见
前 K 个高频元素
LeetCode 347
## 快速选择算法
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
def partition(freq_list, left, right):
l, r = left, right
flag = freq_list[l][1]
while l < r: ##
#print(freq_list,l,r)
mid = (l + r)//2
if freq_list[l][1] > flag:
l+=1
elif freq_list[r][1] < flag:
r-=1
else:
if freq_list[l][1] == freq_list[r][1]:
l+=1
else:
freq_list[l], freq_list[r] = freq_list[r], freq_list[l]
return l
if len(nums)<2:return nums
cnt_dict = {}
for num in nums:
if num not in cnt_dict:
cnt_dict[num]=0
cnt_dict[num] += 1
freq_list = []
for key in cnt_dict:
value = cnt_dict[key]
freq_list.append((key,value))
if len(freq_list) > k:
left, right = 0, len(freq_list)-1
while True:
#print(freq_list[:2*k])
index = partition(freq_list, left, right)
if index < k - 1:
left = index + 1
if index > k - 1:
right = index - 1
if index == k - 1:
break
return [i[0] for i in freq_list[:k]]
## 桶排序
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
cnt_dict = {}
for num in nums:
if num not in cnt_dict:
cnt_dict[num] = 0
cnt_dict[num] += 1
#print(cnt_dict)
bucket = {}
for key in cnt_dict:
value = cnt_dict[key]
if value not in bucket:
bucket[value] = []
bucket[value].append(key)
#print(bucket)
ans = []
for i in range(len(nums),-1,-1):
if i in bucket:
ans.extend(bucket[i])
if len(ans)==k:
break
return ans
不同二叉树的棵数
LeetCode 96
给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?
示例:
输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:
1 3 3 2 1
/ / /
3 2 1 1 3 2
/ /
2 1 2 3
class Solution:
def numTrees(self, n: int) -> int:
dp = [1 for _ in range(n+1)]
if n < 2:
return dp[n]
dp[2] = 2
for i in range(3, n+1): ## 不同节点个数
cur = 0
for j in range(1,i+1):
cur += dp[j-1] * dp[i-j] ## 以j作为根节点,计算左子树的个数和右子树的个数
dp[i] = cur
#print(dp)
return dp[n]
二叉树展开为链表
LeetCode 114
给定一个二叉树,原地将它展开为一个单链表。
例如,给定二叉树
1
/
2 5
/
3 4 6
将其展开为:
1
2
3
4
5
6
class Solution:
def flatten(self, root: TreeNode) -> None:
"""
Do not return anything, modify root in-place instead.
"""
stack = [(root, 0)]
pre = TreeNode(None)
while stack:
node, flag = stack.pop()
if node is None:
continue
if flag == 0:
stack.append((node.right,0))
stack.append((node.left,0))
stack.append((node,1))
else:
#print(node.val)
pre.right = node
pre = node
pre.left = None
pre.right = None
合并二叉树
LeetCode 617
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
class Solution:
def mergeTrees(self, t1: TreeNode, t2: TreeNode) -> TreeNode:
if t1 is None and t2 is None:
return None
elif t1 is None:
return t2
elif t2 is None:
return t1
else:
t1.left = self.mergeTrees(t1.left, t2.left)
t1.right = self.mergeTrees(t1.right, t2.right)
t1.val = t1.val+t2.val
return t1
寻找重复数
LeetCode 287
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例 1:
输入: [1,3,4,2,2]
输出: 2
示例 2:
输入: [3,1,3,4,2]
输出: 3
说明:
不能更改原数组(假设数组是只读的)。
只能使用额外的 O(1) 的空间。
时间复杂度小于 O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。
"""
不能更改原数组(假设数组是只读的)。(不能排序)
只能使用额外的 O(1) 的空间。 (不能用哈希表)
时间复杂度小于 O(n2) 。 (不能2重循环)
数组中只有一个重复的数字,但它可能不止重复出现一次。
方法:
1.二分查找 O(nlogn)(用二分查找缩小目标值的搜索区间,是对O(n2)2重循环的优化):例如nums=[2,4,1,6,3,3,5,7,8],n=8,
2.快慢指针
"""
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
left, right = 1, len(nums)
while left < right: ## 结束条件为:left==right
# 搜索区间为[left, right)
mid = left + (right - left)//2 ## 边界条件:left = 10, right = 11, mid = 10 + 0 = 10
#print(left, mid, right)
cnt = 0
for num in nums:
if num <= mid:
cnt +=1
if cnt > mid: ## [left,mid]数字区间有重复,用mid更新right指针
right = mid
else: ## (mid,right)数字区间有重复,用mid+1更新left指针
left = mid + 1
return left
单词拆分
LeetCode 139
给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。
示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。
注意你可以重复使用字典中的单词。
示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false
class Solution:
"""
直接用遍历word,提交超时
ans = False
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
flag = False
for word in wordDict:
#print(word, s[:len(word)],s)
if len(s) < len(word):
continue
elif s == word:
self.ans = True
return self.ans
elif s[:len(word)] == word:
self.wordBreak(s[len(word):], wordDict)
return self.ans
"""
"""
初始化 dp=[False,⋯ ,False],长度为 n+1。n为字符串长度。dp[i]表示s的前i位是否可以用wordDict中的单词表示。
初始化 dp[0]=True,空字符可以被表示。
遍历字符串的所有子串,遍历开始索引 i,遍历区间 [0,n):
遍历结束索引 j,遍历区间 [i+1,n+1):
若 dp[i]=True且 s[i,⋯ ,j)在wordlist中:dp[j]=True。解释:dp[i]=True 说明s的前i位可以用 wordDict表示,
则 s[i,⋯ ,j)出现在wordDict中,说明s的前j位可以表示。
"""
ans = False
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
dp = [False for __ in range(len(s)+1)]
dp[0] = True
for i in range(len(s)):
for j in range(i+1, len(s)+1):
if dp[i] and s[i:j] in wordDict:
dp[j] = True
return dp[-1]
二叉树中的最大路径和
最大正方形
LeetCode 221
在一个由 '0' 和 '1' 组成的二维矩阵内,找到只包含 '1' 的最大正方形,并返回其面积。
示例:
输入:
matrix =
[["1","0","1","0","0"],
["1","0","1","1","1"],
["1","1","1","1","1"],
["1","0","0","1","0"]]
输出:4
class Solution:
def maximalSquare(self, matrix: List[List[str]]) -> int:
if len(matrix)==0 or len(matrix[0])==0:
return 0
rows, columns = len(matrix), len(matrix[0])
ans = 0
dp = [[0 for _ in range(columns)] for __ in range(rows)]
for i in range(rows):
for j in range(columns):
if matrix[i][j] == '1':
if i == 0 or j == 0:
dp[i][j] = 1
else:
dp[i][j] = min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1
ans = max(ans, dp[i][j])
return ans*ans
除自身以外数组的乘积
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,
其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
示例:
输入: [1,2,3,4]
输出: [24,12,8,6]
提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
进阶:
你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)
"""
思路:利用前缀乘积和后缀乘积计算答案
"""
class Solution:
def productExceptSelf1(self, nums: List[int]) -> List[int]:
ans = [0] * len(nums)
for i in range(len(nums)):
if i==0:
ans[i] = 1
else:
ans[i] = ans[i-1]*nums[i-1]
R = 1
for j in range(len(nums)-1, -1, -1):
ans[j] = ans[j] * R
R = nums[j]*R
return ans
def productExceptSelf2(self, nums: List[int]) -> List[int]:
L, R = [0]*len(nums), [0]*len(nums)
ans = []
for i in range(len(nums)):
if i == 0:
L[i] = 1
else:
L[i] = nums[i-1] * L[i-1]
for j in range(len(nums)-1, -1, -1):
if j == len(nums)-1:
R[j] = 1
else:
R[j] = nums[j+1] * R[j+1]
for i in range(len(nums)):
ans.append(L[i]*R[i])
return ans
打家劫舍III
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。
除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。
如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 1:
输入: [3,2,3,null,3,null,1]
3
/
2 3
3 1
输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
示例 2:
输入: [3,4,5,1,3,null,1]
3
/
4 5
/
1 3 1
输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
class Solution:
ans = -float('inf')
def rob(self, root: TreeNode) -> int:
def helper(root):
if root is None:
return 0, 0
left, pre_left = helper(root.left)
right, pre_right = helper(root.right)
self.ans = max(self.ans, root.val + pre_left + pre_right, left + right)
return max(root.val + pre_left + pre_right, left + right), left + right
if root is None:
return 0
else:
helper(root)
return self.ans
回文子串
LeetCode 647
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
示例 1:
输入:"abc"
输出:3
解释:三个回文子串: "a", "b", "c"
示例 2:
输入:"aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
提示:
输入的字符串长度不会超过 1000 。
class Solution:
def countSubstrings(self, s: str) -> int:
dp = [[0 for _ in range(len(s))] for __ in range(len(s))]
ans = 0
## 由状态转移方程可知:先计算dp[i+1][j-1],才能计算dp[i][j] ,所以先对j进行循环
for j in range(len(s)):
for i in range(j+1):
if i == j:
dp[i][j] = 1
ans+=1
elif j - i == 1 and s[i]==s[j]:
dp[i][j] = 1
ans+=1
elif s[i] == s[j] and dp[i+1][j-1]:
dp[i][j] = 1
ans+=1
return ans
课程表
LeetCode 207
你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1 。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]
给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?
示例 1:
输入: 2, [[1,0]]
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。
示例 2:
输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。
提示:
输入的先决条件是由 边缘列表 表示的图形,而不是 邻接矩阵 。详情请参见图的表示法。
你可以假定输入的先决条件中没有重复的边。
1 <= numCourses <= 10^5
"""
将课程看成是节点,课程的先修关系是节点的有向边。转换成拓扑排序问题。
建立一个记录邻接表的字典和记录节点入度的list,不断的将入度为0的节点加入到队列中,出队时更新其指向的节点的入度。
最后检查是否所有节点都加入队列(入度为0)
"""
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
edges = {}
indeg = [0] * numCourses ## 记录节点的入度
for info in prerequisites:
if info[1] not in edges:
edges[info[1]] = []
edges[info[1]].append(info[0])
indeg[info[0]] += 1
candidate = [i for i in range(numCourses) if indeg[i] == 0]
visited = 0
while candidate:
visited +=1
u = candidate.pop(0)
if u in edges:
for v in edges[u]:
indeg[v]-=1
if indeg[v] == 0:
candidate.append(v)
return visited == numCourses
完全平方数
LeetCode 279
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
示例 1:
输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.
示例 2:
输入: n = 13
输出: 2
解释: 13 = 4 + 9.
class Solution:
def numSquares(self, n: int) -> int:
square_nums = [i**2 for i in range(0, int(math.sqrt(n))+1)]
dp = [float('inf')]*(n+1)
dp[0] = 0
for i in range(1,n+1):
for num in square_nums:
if i < num:
break
dp[i] = min(dp[i], dp[i-num] + 1) ## 状态转移方程
return dp[-1]
最佳买卖股票时机含冷冻期
LeetCode 309
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
示例:
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
class Solution:
def maxProfit(self, prices: List[int]) -> int:
## 用dp数组记录状态,
## dp[i][0] 表示 第i天没有持有股票状态的最大利润
## dp[i][1] 表示 第i天持有股票状态的最大利润
if len(prices) <= 1:
return 0
dp = [[None, None] for _ in range(len(prices))] ## dp数组的长度是n还是n+1?
dp[0][0] = 0
dp[0][1] = -prices[0]
dp[1][0] = max(dp[0][0], dp[0][1] + prices[1]) ## 前一天买入,当天卖出获利,故加上当天的价格
dp[1][1] = max(dp[0][1], -prices[1])
for i in range(2,len(prices)):
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-2][0] - prices[i])
return dp[-1][0]
分割等和子集
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
每个数组中的元素不会超过 100
数组的大小不会超过 200
示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.
class Solution:
def canPartition(self, nums: List[int]) -> bool:
"""
dp[i][j] 表示从数组的 [0,i]下标范围内选取若干个正整数(可以是 0 个),
是否存在一种选取方案使得被选取的正整数的和等于 j
"""
if len(nums) < 2:
return False
numsSum = sum(nums)
numsMax = max(nums)
if numsSum & 1:
return False
target = numsSum//2
if target < numsMax:
return False
dp = [[False]*(target+1) for _ in range(len(nums))]
for i in range(len(nums)):
dp[i][0] = True
dp[0][nums[0]] = True
for i in range(1, len(nums)): # [1,n]
num = nums[i]
for j in range(1, target+1):
if j >= num:
dp[i][j] = dp[i-1][j] | dp[i-1][j - num]
else:
dp[i][j] = dp[i-1][j]
return dp[-1][target]
把二叉搜索树转换为累加树
LeetCode 538
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
节点的左子树仅包含键 小于 节点键的节点。
节点的右子树仅包含键 大于 节点键的节点。
左右子树也必须是二叉搜索树。
class Solution:
cur_sum = 0
def convertBST(self, root: TreeNode) -> TreeNode:
if root is None:
return root
self.convertBST(root.right)
root.val +=self.cur_sum
self.cur_sum = root.val
self.convertBST(root.left)
return root
路径总和III
LeetCode 437
给定一个二叉树,它的每个结点都存放着一个整数值。
找出路径和等于给定数值的路径总数。
路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。
示例:
root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8
10
/
5 -3
/
3 2 11
/
3 -2 1
返回 3。和等于 8 的路径有:
1. 5 -> 3
2. 5 -> 2 -> 1
3. -3 -> 11
class Solution:
def pathSum(self, root: TreeNode, sum: int) -> int:
"""
思路:与(LeetCode 560.和为K的子数组)类似,记录前缀和
可以将LeetCode 560改成递归的形式
"""
def helper(root, cursum):
if root is None:
return 0
cursum+=root.val
# cursum为到当前节点路径的前缀和,例如target=8,cursum=10, 如果lookup[10-8]=3,
# 说明有3个节点的前缀和为2,此时这3个节点到当前节点的路径和刚好为target=8
if cursum - self.target_sum in self.lookup:
cnt = self.lookup[cursum - self.target_sum] #
else:
cnt = 0
if cursum not in self.lookup:
self.lookup[cursum] = 0
self.lookup[cursum]+=1
left_cnt = helper(root.left, cursum)
right_cnt = helper(root.right, cursum)
self.lookup[cursum]-=1
return cnt + left_cnt + right_cnt
self.lookup = {}
self.lookup[0] = 1
self.target_sum = sum
return helper(root, 0)
根据身高重建队列
LeetCode 406
假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。
每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。
请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。
示例 1:
输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。
示例 2:
输入:people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]]
输出:[[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]
提示:
1 <= people.length <= 2000
0 <= hi <= 106
0 <= ki < people.length
题目数据确保队列可以被重建
people: [[7, 0], [4, 4], [7, 1], [5, 0], [6, 1], [5, 2]]
people: [[7, 0], [7, 1], [6, 1], [5, 0], [5, 2], [4, 4]] # 排序后
ans: [[7, 0]]
ans: [[7, 0], [7, 1]]
ans: [[7, 0], [6, 1], [7, 1]]
ans: [[5, 0], [7, 0], [6, 1], [7, 1]]
ans: [[5, 0], [7, 0], [5, 2], [6, 1], [7, 1]]
ans: [[5, 0], [7, 0], [5, 2], [6, 1], [4, 4], [7, 1]]
class Solution:
def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
print('people:',people)
people = sorted(people, key = lambda x:(-x[0],x[1]))
print('people:',people)
ans = []
for item in people:
if len(ans) > item[1]:
ans.insert(item[1], item)
else:
ans.append(item)
print('ans:',ans)
return ans
找到所有数组中消失的数字
给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。
找到所有在 [1, n] 范围之间没有出现在数组中的数字。
您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。
示例:
输入:
[4,3,2,7,8,2,3,1]
输出:
[5,6]
"""
[4,3,2,7,8,2,3,1]
[4, 3, 2, -7, 8, 2, 3, 1]
[4, 3, -2, -7, 8, 2, 3, 1]
[4, -3, -2, -7, 8, 2, 3, 1]
[4, -3, -2, -7, 8, 2, -3, 1]
[4, -3, -2, -7, 8, 2, -3, -1]
[4, -3, -2, -7, 8, 2, -3, -1]
[4, -3, -2, -7, 8, 2, -3, -1]
[-4, -3, -2, -7, 8, 2, -3, -1]
"""
class Solution:
def findDisappearedNumbers(self, nums: List[int]) -> List[int]:
for i in range(len(nums)):
index = abs(nums[i])
if nums[index - 1] > 0:
nums[index - 1] *=-1
print(nums)
ans = []
for i in range(len(nums)):
if nums[i] > 0:
ans.append(i+1)
return ans
二叉树的序列化与反序列化
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
示例:
你可以将以下二叉树:
1
/
2 3
/
4 5
序列化为 "[1,2,3,null,null,4,5]"
class Codec:
def serialize(self, root):
"""Encodes a tree to a single string.
:type root: TreeNode
:rtype: str
"""
if not root:return None
ans = []
q = [root]
while q:
node = q.pop(0)
if node is not None:
ans.append(str(node.val))
q.append(node.left)
q.append(node.right)
else:
ans.append('null')
ret = ','.join(ans)
#print(ret)
return ret
def deserialize(self, data):
"""Decodes your encoded data to tree.
:type data: str
:rtype: TreeNode
"""
if not data:return None
data = data.split(',')
root = TreeNode(data.pop(0))
q = [root]
while q:
node = q.pop(0)
if data:
val = data.pop(0)
if val!='null':
node.left = TreeNode(val)
q.append(node.left)
if data:
val = data.pop(0)
if val!='null':
node.right = TreeNode(val)
q.append(node.right)
return root
## 递归
class Codec:
def serialize(self, root):
"""Encodes a tree to a single string.
:type root: TreeNode
:rtype: str
"""
if root is None:
return 'null'
return str(root.val) + ',' + self.serialize(root.left) + ',' + self.serialize(root.right)
def deserialize(self, data):
"""Decodes your encoded data to tree.
:type data: str
:rtype: TreeNode
"""
def helper(data_list):
if not data_list:
return None
val = data_list.pop(0)
if val!='null':
root = TreeNode(val)
root.left = helper(data_list)
root.right = helper(data_list)
return root
if not data or data == 'null':
return None
data_list = data.split(',')
return helper(data_list)
208. 实现 Trie (前缀树)
实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。
示例:
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // 返回 true
trie.search("app"); // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");
trie.search("app"); // 返回 true
说明:
你可以假设所有的输入都是由小写字母 a-z 构成的。
保证所有输入均为非空字符串。
## 定义TrieNode 节点类型
from collections import defaultdict
class TrieNode:
def __init__(self):
self.word = False
self.children = defaultdict(TrieNode)
class Trie:
def __init__(self):
"""
Initialize your data structure here.
"""
self.root = TrieNode()
def insert(self, word: str) -> None:
"""
Inserts a word into the trie.
"""
cur = self.root
for w in word:
cur = cur.children[w] # w指向TrieNode 节点
cur.word = True
def search(self, word: str) -> bool:
"""
Returns if the word is in the trie.
"""
cur = self.root
for w in word:
if w in cur.children:
cur = cur.children[w]
else:
return False
return cur.word
def startsWith(self, prefix: str) -> bool:
"""
Returns if there is any word in the trie that starts with the given prefix.
"""
cur = self.root
for w in prefix:
if w in cur.children:
cur = cur.children[w]
else:
return False
return True
240.搜索二维矩阵II
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
每行的元素从左到右升序排列。
每列的元素从上到下升序排列。
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
rows = len(matrix)
cols = len(matrix[0])
i, j = rows-1 , 0
while 0<=i and j<=cols-1:
if matrix[i][j] == target:
return True
elif matrix[i][j] > target:
i-=1
elif matrix[i][j] < target:
j+=1
return False
找到字符串中所有字母异位词
LeetCode 438
给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。
字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。
说明:
字母异位词指字母相同,但排列不同的字符串。
不考虑答案输出的顺序。
示例 1:
输入:
s: "cbaebabacd" p: "abc"
输出:
[0, 6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的字母异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的字母异位词。
示例 2:
输入:
s: "abab" p: "ab"
输出:
[0, 1, 2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的字母异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的字母异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的字母异位词。
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
"""
题目要求查找字符串而不是子序列,字符串只包含小写字母
1.记录p中出现的字母数
2.统计s中前len(p)个字符出现的字母数
3.滑动窗口:
(1).如果lookup与count相等,记录i
(2).下一个循环之前,s[i]在count中的记录减一,s[i+len(p)]在count中的记录加一。
"""
if len(p) > len(s):
return []
ans = []
lookup = [0 for _ in range(26)]
count = [0 for _ in range(26)]
needed = 0
for i in range(len(p)):
lookup[ord(p[i]) - ord('a')] += 1
count[ord(s[i]) - ord('a')] += 1
for i in range(len(s) - len(p) + 1):
#print(count)
if lookup == count:
ans.append(i)
if i + len(p) < len(s):
count[ord(s[i])- ord('a')]-=1
count[ord(s[i+len(p)]) - ord('a')] +=1
return ans
目标和
LeetCode 494
给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。
现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
示例:
输入:nums: [1, 1, 1, 1, 1], S: 3
输出:5
解释:
-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
一共有5种方法让最终目标和为3。
提示:
数组非空,且长度不会超过 20 。
初始的数组的和不会超过 1000 。
保证返回的最终结果能被 32 位整数存下。
## 递归,超时
class Solution:
def findTargetSumWays(self, nums: List[int], S: int) -> int:
if len(nums) == 0:
if S == 0:
return 1
else:
return 0
return self.findTargetSumWays(nums[1:], S - nums[0]) +self.findTargetSumWays(nums[1:], S + nums[0])
动态规划(二维dp)
dp[i][j]的含义 : 数组中前i个元素组成和为j的方案数。
因为和最大为sum(nums),所以和的范围为 -sum(nums)到 0 到 sum(nums)。
数组大小设为[[0 for _ in range(sum(nums)*2+1)] for _ in range(len(nums))]
1.初始化:
dp[0][total-nums[0]]=+1
dp[0][total+nums[0]]=+1
2.状态转移方程:
l = j - nums[i] if j - nums[i] >= 0 else 0
r = j + nums[i] if j + nums[i] < total*2+1 else 0
dp[i][j] = dp[i-1][l] + dp[i-1][r]
思考:为什么动态规划能处理而递归DFS超时?
因为递归的时间复杂度是O(2^n),而动态规划的时间复杂度是O(n*total)
class Solution(object):
def findTargetSumWays(self, nums, S):
total = sum(nums)
if abs(total) < abs(S): return 0
dp = [[0]*(total*2+1) for _ in range(len(nums))]
# 如果 nums[0]=0, dp[0][total]=2,
# 否则 dp[0][total-nums[0]] = 1
# dp[0][total+nums[0]] = 1
dp[0][total - nums[0]] += 1
dp[0][total + nums[0]] += 1
for i in range(1,len(nums)):
for j in range(total*2 + 1):
# l和r要保证在合法的索引范围内
l = j - nums[i] if j - nums[i] >= 0 else 0
r = j + nums[i] if j + nums[i] <=total*2 else 0
dp[i][j] = dp[i-1][l] + dp[i-1][r]
return dp[-1][S + total]
最短无序连续子数组
LeetCode 581
给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
你找到的子数组应是最短的,请输出它的长度。
示例 1:
输入: [2, 6, 4, 8, 10, 9, 15]
输出: 5
解释: 你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。
说明 :
输入的数组长度范围在 [1, 10,000]。
输入的数组可能包含重复元素 ,所以升序的意思是<=。
class Solution:
def findUnsortedSubarray(self, nums: List[int]) -> int:
"""
[1,2,3,4,7,6,5,8,9,10]
[1,2,3,4,7,7,7,8,9,10]
[1,2,3,4,5,5,5,8,9,10]
在升序部分,最大的元素一定在尾部,最小的元素一定在首部,
如果一个元素小于左边元素的最大值,那么这个元素一定在中间无序部分,
如果一个元素大于右边元素的最小值,那么这个元素一定在中间无序部分
可以利用这个性质,得到中间乱序部分的左右边界
"""
left_max = nums[0]
right_min = nums[-1]
left_index, right_index = 0, -1 # 边界条件:输入数组为升序时
for i in range(len(nums)):
if nums[i] < left_max:
right_index = i # 记录右边界
else:
left_max = nums[i]
for i in range(len(nums)-1, -1,-1):
if nums[i] > right_min:
left_index = i # 记录左边界
else:
right_min = nums[i]
return right_index - left_index + 1
柱状图中的最大矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例:
输入: [2,1,5,6,2,3]
输出: 10
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
"""
暴力解法:遍历位置i, 寻找左右两边第一个最小高度
ans = 0
for i in range(len(heights)):
height = heights[i]
left,right = i,i
while left>0 and heights[left-1] >= heights[i]:
left -= 1
while right < len(heights)-1 and heights[right+1] >= heights[i]:
right += 1
#print(left,i,right)
ans = max(ans, (right + 1 - left)*height)
return ans
"""
## 使用单调栈,帮助寻找左右两边最小高度, 记录左右两个最小高度的位置
if len(heights) == 0:
return 0
if len(heights) == 1:
return heights[0]
left, right = [0]*len(heights), [0]*len(heights)
stack = []
for i in range(len(heights)):
while stack and heights[stack[-1]] >= heights[i]:
stack.pop()
left[i] = stack[-1] if stack else -1
stack.append(i)
stack = []
for i in range(len(heights)-1, -1, -1):
while stack and heights[stack[-1]] >= heights[i]:
stack.pop()
right[i] = stack[-1] if stack else len(heights) #### 边界条件
stack.append(i)
print(left)
print(right)
return max([(right[i] - left[i] - 1) * heights[i] for i in range(len(heights))])
戳气球
LeetCode 312
有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。如果你戳破气球 i ,就可以获得 nums[left] * nums[i] * nums[right] 个硬币。
这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。
求所能获得硬币的最大数量。
说明:
你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。
0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100
示例:
输入: [3,1,5,8]
输出: 167
解释: nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
coins = 315 + 358 + 138 + 181 = 167
参考题解
class Solution:
def maxCoins(self, nums: List[int]) -> int:
n = len(nums)
nums = [1] + nums + [1]
dp = [[0]*(n+2) for _ in range(n+1)]
# 最终要求 dp[0][n+1],
# 故 for i in [n,n-1,n-2...]
# for j in [i+1, i+2,...]
for i in range(n,-1,-1):
for j in range(i+1, n+2):
for k in range(i+1, j):
dp[i][j] = max(dp[i][j], dp[i][k] + nums[i] * nums[k] * nums[j] + dp[k][j])
return dp[0][-1]