一刷leetcode
10.16日
1、二叉树的层序遍历(done)
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
results = []
if not root:
return results
from collections import deque
que = deque([root])
while que:
result = []
for _ in range(len(que)):
cur = que.popleft()
result.append(cur.val)
if cur.left:
que.append(cur.left)
if cur.right:
que.append(cur.right)
results.append(result)
return results
2、从前序与中序遍历序列构造二叉树(done)
105从前序与中序遍历序列构造二叉树
⚠️ preorder_left下标不是从0开始,而是1。
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
# 第一步: 特殊情况讨论: 树为空. 或者说是递归终止条件
if not preorder:
return None
# 第二步: 前序遍历的第一个就是当前的中间节点.
root_val = preorder[0]
root = TreeNode(root_val)
# 第三步: 找切割点.
separator_idx = inorder.index(root_val)
# 第四步: 切割inorder数组. 得到inorder数组的左,右半边.
inorder_left = inorder[:separator_idx]
inorder_right = inorder[separator_idx+1:]
# 第五步: 切割preorder数组. 得到preorder数组的左,右半边.
# ⭐️ 重点1: 中序数组大小一定跟前序数组大小是相同的.
preorder_left = preorder[1:1 + len(inorder_left)]
preorder_right = preorder[1 + len(inorder_left):]
#递归
root.left = self.buildTree(preorder_left,inorder_left)
root.right = self.buildTree(preorder_right,inorder_right)
return root
3、二叉树展开为链表(……need again
)
114二叉树展开为链表
前序遍历ans.append()的是root,不是root.val
class Solution:
def flatten(self, root: TreeNode) -> None:
ans = []
#前序遍历
def preOrder(root):
if root:
ans.append(root) #是root,不是root.val
preOrder(root.left)
preOrder(root.right)
preOrder(root)
for i in range(1,len(ans)):
pre,cur = ans[i-1],ans[i]
pre.left = None
pre.right = cur
return ans
4、两数之和-哈希表(done)
def twoSum(nums, target):
s = dict()
for i, j in enumerate(nums): #i下表,j值
if target - j in s: #不要使用s.values()
return [s[traget-j], i]
else:
s[j] = i #hash表存储的是值,下表,方便区下标
return []
5、有序两数之和-双指针法(done)
def twoSum(numbers, target):
# 双指针法
i, j = 0, len(numbers) - 1
while i < j: #有序,左边 < 右边
if numbers[i] + numbers[j] < target:
i += 1
elif numbers[i] + numbers[j] > target:
j -= 1
else:
return [i, j]
return []
6、三数之和-双指针法(done,排序+双指针)
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
nums.sort()
results = []
for i in range(n):
if nums[i] > 0: #第一个数大于0,和肯定大于0
break
if i > 0 and nums[i] == nums[i-1]: #第一个数去重
continue
left = i+1
right = n-1
while left < right:
total = nums[i] + nums[left] + nums[right]
if total > 0:
right -= 1
elif total < 0:
left += 1
else:
results.append([nums[i], 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 results
7、盛最多水容器-双指针法(done)
11 盛最多水的容器
如果我们移动数字较大的那个指针,那么前者「两个指针指向的数字中较小值」不会增加,后者「指针之间的距离」会减小,那么这个乘积会减小。因此,我们移动数字较大的那个指针是不合理的。因此,我们移动 数字较小的那个指针。
def maxArea(height):
l, r = 0, len(height) - 1
ans = 0
while l < r:
s = (r - l) * min(height[l], height[r])
ans = max(ans, s)
# 移动短的那根
if height[l] <= height[r]:
l += 1
elif height[l] > height[r]:
r -= 1
return ans
8、对称二叉树-递归(done)
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
def im(l,r):
if l == None and r == None: #左右子树都为空
return True
if l == None or r == None: #一个为空,一个非空
return False
#它们的两个根结点具有相同的值 && 每个树的右子树都与另一个树的左子树镜像对称
return l.val == r.val and im(l.right,r.left) and im(l.left,r.right)
return im(root.left,root.right)
9、二叉树最大深度-递归(done)
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root :
return 0
#depth(root.left)辅助函数可省略
return 1 + max(self.maxDepth(root.left),self.maxDepth(root.right))
10、买卖股票的最佳时机(done)
def maxProfit(prices):
#r变量记录一个历史最低价格,第 i 天卖出股票能得到的利润就是 prices[i] - r
#我们只需要遍历价格数组一遍,记录历史最低点,然后在每一天考虑这么一个问题:如果我是在历史最低点买进的,那么我今天卖出能赚多少钱?当考虑完所有天数之时,我们就得到了最好的答案
minprice = prices[0]
maxprofit = 0
for price in prices:
maxprofit = max(price - minprice, maxprofit)
minprice = min(price, minprice)
return maxprofit
10.17日
1、电话号码的数组组合(……need again
)
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
if not digits:
return list()
phoneMap = {
"2": "abc",
"3": "def",
"4": "ghi",
"5": "jkl",
"6": "mno",
"7": "pqrs",
"8": "tuv",
"9": "wxyz",
}
def backtrack(index: int):
if index == len(digits):
combinations.append("".join(combination))
else:
digit = digits[index]
for letter in phoneMap[digit]:
combination.append(letter)
backtrack(index + 1)
combination.pop()
combination = list()
combinations = list()
backtrack(0)
return combinations
2、删除链表的倒数第 N 个结点(done)
19. 删除链表的倒数第 N 个结点
1、初始时 first 和 second 均指向头节点。我们首先使用 first 对链表进行遍历,遍历的次数为 n。此时,first 和 second 之间间隔了 n−1 个节点,即 first 比 second 超前了 n 个节点。
2、在这之后,我们同时使用 first 和 second 对链表进行遍历。当 first 遍历到链表的末尾(即 first 为空指针)时,second 恰好指向倒数第 n 个节点。
3、如果我们能够得到的是倒数第 n 个节点的前驱节点而不是倒数第 n 个节点的话,删除操作会更加方便。因此我们可以考虑在初始时将 second 指向哑节点,其余的操作步骤不变。这样一来,当 first 遍历到链表的末尾时,second 的下一个节点就是我们需要删除的节点。
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
dummy = ListNode(0,head)
l = dummy #慢指针指向哑节点
f = head
for i in range(n):
f = f.next
while f :
f = f.next
l = l.next
l.next = l.next.next #删除
return dummy.next
3、有效括号(done)
- 如果 c 是左括号,则入栈 push;
- 否则通过哈希表判断括号对应关系,若 stack 栈顶出栈括号 stack.pop() 与当前遍历括号 c 不对应,则提前返回 false。
class Solution:
def isValid(self, s: str) -> bool:
if len(s) % 2 == 1:
return False
d = {
')': '(',
'}': '{',
']': '['
}
stack = []
for i in s:
if i in d.keys() and stack != [] and d[i] == stack[-1]:
stack.pop()
else:
stack.append(i)
return not stack
4、合并两个有序链表(done)
21. 合并两个有序链表
首先,我们设定一个==哑节点 prehead ==,这可以在最后让我们比较容易地返回合并后的链表。我们维护一个 prev 指针,我们需要做的是调整它的 next 指针。然后,我们重复以下过程,直到 l1 或者 l2 指向了 null :如果 l1 当前节点的值小于等于 l2 ,我们就把 l1 当前的节点接在 prev 节点的后面同时将 l1 指针往后移一位。否则,我们对 l2 做同样的操作。不管我们将哪一个元素接在了后面,我们都需要把 prev 向后移一位。
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
#哑节点
dummy = ListNode(0)
pre = dummy
while l1 and l2:
if l1.val <= l2.val:
pre.next = l1
l1 = l1.next
else:
pre.next = l2
l2 = l2.next
pre = pre.next
pre.next = l1 if l1 else l2
return dummy.next #注意是dummy.next而非pre.next
5、搜索旋转排序数组(done)
- 在常规二分查找的时候查看以当前 mid 为分割位置分割出来的两个部分 [0, mid - 1] 和 [mid + 1, r]哪个部分是有序的
- 如果 [0, mid - 1] 是有序数组,且 target 的大小属于[nums[0],nums[mid]),则我们应该将搜索范围缩小至[0, mid - 1],否则在 [mid + 1, r] 中寻找。
- 如果 [mid + 1, r] 是有序数组,且 target的大小属于[nums[mid+1],nums[r]],则我们应该将搜索范围缩小至 [mid + 1, r],否则在 [l, mid - 1] 中寻找。
class Solution:
def search(self, nums: List[int], target: int) -> int:
#二分法
if not nums:
return -1
l, r = 0,len(nums) -1
while l <= r :
mid = (l + r)//2
if nums[mid] == target:
return mid
elif nums[l] <= nums[mid]: #说明左边有序,进行二分查找,区间[0,mid-1]
if nums[l] <= target < nums[mid]: #target在[0,mid-1]中
r = mid - 1
else: #target不在[0,mid-1]中,则查找右区间
l = mid + 1
else: #说明右边有序,进行二分查找,区间[mid+1,r]
if nums[mid] < target <= nums[r]: #target在[mid+1,r]中
l = mid + 1
else: #否则查找左区间
r = mid - 1
return -1
6、无重复字符的最长子串(……need again
)
3. 无重复字符的最长子串
while s[r] in cur: 要用while而不是if
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
cur, res = [], 0
for r in range(len(s)):
while s[r] in cur:
cur.pop(0) # 左边出
cur.append(s[r]) # 右侧无论如何都会进入新的
res = max(len(cur),res)
return res
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
ans = set()
n = len(s)
start = 0
max_len = 0
for j in range(n):
while s[j] in ans:
ans.remove(s[start])
start += 1
ans.add(s[j])
max_len = max(max_len,j-start+1)
return max_len
10.19日
1、最小路径和(done)
- 状态定义:设dp为大小 m×n 矩阵,其中 dp[i][j]的值代表直到走到 (i,j)的最小路径和。
- 转移方程:当前单元格 (i,j)只能从左方单元格 (i-1,j)或上方单元格 (i,j−1)
走到,因此只需要考虑矩阵左边界和上边界。走到当前单元格 (i,j)的最小路径和 = “从左方单元格 (i-1,j)与 从上方单元格(i,j-1)走来的 两个最小路径和中较小的 ” + 当前单元格值 grid[i][j] 。具体分为以下4 种情况:
当左边和上边都不是矩阵边界时: 即当i != 0,j != 0时,dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
当只有左边是矩阵边界时: 只能从上面来,即当i = 0, j != 0时, dp[i][j] = dp[i][j - 1] + grid[i][j];
当只有上边是矩阵边界时: 只能从左面来,即当i != 0, j = 0时, dp[i][j] = dp[i - 1][j] + grid[i][j];
当左边和上边都是矩阵边界时: 即当i=0,j=0时,其实就是起点, dp[i][j] = grid[i][j]; - 初始状态:dp初始化即可,不需要修改初始 0 值。
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
if not grid:
return 0
dp = [[0] * len(grid[0]) for _ in range(len(grid))]
for i in range(len(grid)):
for j in range(len(grid[0])):
if i==0 and j== 0:
dp[i][j] = grid[0][0]
elif i==0:
dp[0][j] = dp[0][j-1] + grid[0][j]
elif j== 0:
dp[i][0] = dp[i-1][0] + grid[i][0]
else:
dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j]
return dp[-1][-1]
2、二叉树中序遍历(done)
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
res = []
def dfs(root):
if not root:
return
# 按照 左-根-右的方式遍历
dfs(root.left)
res.append(root.val)
dfs(root.right)
dfs(root)
return res
3、验证二叉搜索树-(done)
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
res = []
def dfs(root):
if not root:
return
dfs(root.left)
res.append(root.val)
dfs(root.right)
return res
d = dfs(root)
return d == sorted(d) and len(d) == len(set(d))
4、最长连续序列(……need again
)
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
longest_streak = 0
num_set = set(nums) #去重
for num in num_set:
if num - 1 not in num_set: #判断num-1是否在nums中
current_num = num
current_streak = 1
while current_num + 1 in num_set: #判断num+1是否在nums中
current_num += 1
current_streak += 1
longest_streak = max(longest_streak, current_streak)
return longest_streak
5、不同的二叉搜索树-动态规划(……不会)
10.30日
1、子集-(……need again)
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
result = [[]]
for i in nums:
n = len(result)
for j in range(n):
result.append(result[j] + [i])
return result
2、单词搜索-回溯(……不会
)
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
#当前元素在矩阵 board 中的行列索引 i 和 j ,当前目标字符在 word 中的索引 k
def dfs(i, j, k):
if not 0 <= i < len(board) or not 0 <= j < len(board[0]) or board[i][j] != word[k]: return False
if k == len(word) - 1:
return True
# 将 board[i][j] 修改为 空字符 '' ,代表此元素已访问过,防止之后搜索时重复访问
board[i][j] = ''
res = dfs(i + 1, j, k + 1) or dfs(i - 1, j, k + 1) or dfs(i, j + 1, k + 1) or dfs(i, j - 1, k + 1)
#将 board[i][j] 元素还原至初始值,即 word[k]
board[i][j] = word[k]
return res
for i in range(len(board)):
for j in range(len(board[0])):
if dfs(i, j, 0): return True
return False
3、颜色分类(……need again)
- 如果找到了0,则将其与nums[p0]进行交换,并将p0向后移动一个位置;
- 如果找到了2,那么将其与nums[p2]进行交换,并将 p2向前移动一个位置。(不能用if)
def sortColors(nums):
p0,p2 = 0,len(nums)-1
i = 0
# 当我们将nums[i] 与nums[p2] 进行交换之后,新的nums[i] 可能仍然是2也可能是 0。然而此时我们已经结束了交换,开始遍历下一个元素nums[i+1],不会再考虑 nums[i]了,
while i <= p2:
while i <= p2 and nums[i] == 2: #不能使用if
nums[i],nums[p2] = nums[p2], nums[i]
p2 -= 1
if nums[i] == 0:
nums[i],nums[p0] = nums[p0],nums[i]
p0 += 1
i += 1
return nums
4、编辑距离(done)
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
m = len(word1)
n = len(word2)
dp = [[float('inf') for _ in range(n + 1)] for _ in range(m + 1)]
# 初始化
for i in range(m + 1):
dp[i][0] = i
for i in range(n + 1):
dp[0][i] = i
# 状态转移
# i , j 代表 word1, word2 对应位置的 index
for i in range(1, m + 1):
for j in range(1, n + 1):
# 如果word1[:i][-1]==word2[:j][-1]
if word1[i - 1] == word2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
# 否则从三种状态中选一个最小的然后 +1
else:
dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1
return dp[-1][-1]
5、不同路径(done)
def uniquePaths(m, n):
dp = [[0]*n for _ in range(m)]
#初始化
for i in range(n):
dp[0][i] = 1
for i in range(m):
dp[i][0] = 1
for i in range(1,m):
for j in range(1,n):
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[-1][-1]
6、合并区间-排序(done)
56 合并区间
将列表中的区间按照左端点升序排序。然后我们将第一个区间加入 merged 数组中,并按顺序依次考虑之后的每个区间:
- 如果当前区间的左端点在数组 merged 中最后一个区间的右端点之后,那么它们不会重合,我们可以直接将这个区间加入数组 merged的末尾;
- 否则,它们重合,我们需要用当前区间的右端点更新数组 merged 中最后一个区间的右端点,将其置为二者的较大值。
def merge(intervals):
intervals.sort(key=lambda x: x[0]) # #x:x[]字母可以随意修改,排序方式按照中括号[]里面的维度进行排序,[0]按照第一维排序,[1]按照第二维排序,reverse=true表示降序,reverse=false表示逆序。
merged = []
for interval in intervals:
# 如果列表为空,或者当前区间与上一区间不重合,直接添加
if not merged or merged[-1][-1] < interval[0]:
merged.append(interval)
else:
# 否则的话,我们就可以与上一区间进行合并
merged[-1][-1] = max(merged[-1][-1], interval[-1])
return merged
7、跳跃游戏-贪心(……不会)
class Solution:
def canJump(self, nums: List[int]) -> bool:
n, rightmost = len(nums), 0
for i in range(n):
if i <= rightmost:
rightmost = max(rightmost, i + nums[i])
if rightmost >= n - 1:
return True
return False
8、最大子序和-贪心&动态规划(done)
53 最大子序和
动态规划:若前一个元素大于0,则将其加到当前元素上dp[i] = max(dp[i-1] + nums[i],nums[i])
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
if not nums:
return 0
dp = [0] * len(nums)
dp[0] = nums[0]
maxsum = nums[0]
for i in range(1,len(nums)):
dp[i] = max(dp[i-1] + nums[i],nums[i])
maxsum = max(maxsum,dp[i])
return maxsum
9、最长回文子串-动态规划(done)
- 状态:dp[i][j]表示子串s[i:j]是否为回文子串
- 状态转移方程:dp[i][j]= (s[i] ==s[j] and dp[i+1][j-1])
- 边界条件:j-1 - (i+1)+1<2 得到j-i + 1 < 4,s[i,j]长度为2或3时不用检查字串是否回文
- 初始化:dp[i][i]=true
class Solution:
def longestPalindrome(self, s: str) -> str:
if len(s) < 2:
return s
dp = [[False]*len(s) for _ in range(len(s))]
for i in range(len(s)):
dp[i][i] = True
maxlen = 1
begin = 0
for j in range(len(s)):
for i in range(j):
if s[i] == s[j]:
if j - i <= 2:
dp[i][j] = True
if dp[i+1][j-1]:
dp[i][j] = True
# 只要 dp[i][L] == true 成立,就表示子串 s[i:L] 是回文,此时记录回文长度和起始位置
if dp[i][j] and j-i+1 > maxlen:
maxlen = j-i+1
begin = i
return s[begin:begin+maxlen]
10、旋转图像(done)
48 旋转图像
规律:matrix[i][j] => [j][n-1-i]
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
n = len(matrix)
matrix_new = [[0]*n for _ in range(n)]
for i in range(n):
for j in range(n):
matrix_new[j][n-1-i]= matrix[i][j]
matrix[:] = matrix_new #不能 matrix_new = matrix 或 matrix_new = matrix[:] 因为是引用拷贝
return matrix
10.31日
1、全排列(……need again)
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
res = []
def backtrack(first = 0):
# 所有数都填完了
if first == n:
res.append(nums[:]) #nums的拷贝
for i in range(first, n):
# 动态维护数组
nums[first], nums[i] = nums[i], nums[first]
# 继续递归填下一个数
backtrack(first + 1)
# 撤销操作
nums[first], nums[i] = nums[i], nums[first]
backtrack()
return res
2、字母异位词分组-(……need again)
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
#使用defaultdict,可以给不存在的key给一个默认的初始值
mp = collections.defaultdict(list)
for st in strs:
counts = [0] * 26
for ch in st:
counts[ord(ch) - ord("a")] += 1
# 需要将 list 转换成 tuple 才能进行哈希
mp[tuple(counts)].append(st)
return list(mp.values())
3、组合总和-(……不会)
4、在排序数组中查找元素的第一个和最后一个位置-(……不会)
5、单词拆分-(……need again)
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
dp = [False] * (len(s) + 1)
dp[0] = True
#遍历背包
for j in range(1,len(s)+1):
#遍历单词
for word in wordDict:
if j >= len(word):
dp[j] = dp[j] or (dp[j- len(word)] and s[j- len(word):j] == word)
return dp[len(s)]
6、下一个排列-(……不会)
7、两数相加-(done)
# Definition for singly-linked list.
# 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]:
s = 0 #进位
dummy = ListNode(0)
pre = dummy
while s or l1 or l2:
s += (l1.val if l1 else 0) + (l2.val if l2 else 0)
pre.next = ListNode(s%10)
pre = pre.next
if l1:
l1 = l1.next
if l2:
l2 = l2.next
s //= 10
return dummy.next
8、最小的K个数-(……need again)
class Solution:
def GetLeastNumbers_Solution(self , input: List[int], k: int) -> List[int]:
# write code here
if k > len(input):
return input
ans = self.mergeSort(input)
if k <= len(input):
return ans[:k]
return []
def mergeSort(self,arr):
if len(arr) < 2:
return arr
mid = len(arr)//2
left = arr[0:mid]
right = arr[mid:]
return self.merge(self.mergeSort(left),self.mergeSort(right))
def merge(self,left,right):
res = []
while left and right:
if left[0] <= right[0]:
res.append(left.pop(0))
else:
res.append(right.pop(0))
while left : res.append(left.pop(0))
while right : res.append(right.pop(0))
return res