LeetCode-算法:101-150(Python)
- 101. 对称二叉树
- 102. 二叉树的层序遍历
- 103. 二叉树的锯齿形层次遍历
- 104. 二叉树的最大深度
- 105. 从前序与中序遍历序列构造二叉树
- 106. 从中序与后序遍历序列构造二叉树
- 107. 二叉树的层次遍历 II
- 108. 将有序数组转换为二叉搜索树
- 109. 有序链表转换二叉搜索树
- 110. 平衡二叉树
- 111. 二叉树的最小深度
- 112. 路径总和
- 113. 路径总和 II
- 114. 二叉树展开为链表
- 115. 不同的子序列
- 116. 填充每个节点的下一个右侧节点指针
- 117. 填充每个节点的下一个右侧节点指针 II
- 118. 杨辉三角
- 119. 杨辉三角 II
- 120. 三角形最小路径和
- 121. 买卖股票的最佳时机
- 122. 买卖股票的最佳时机 II
- 123. 买卖股票的最佳时机 III
- 124. 二叉树中的最大路径和
- 125. 验证回文串
- 126. 单词接龙 II
- 127. 单词接龙
- 128. 最长连续序列
- 129. 求根到叶子节点数字之和
- 130. 被围绕的区域
- 131. 分割回文串
- 132. 分割回文串 II
- 133. 克隆图
- 134. 加油站
- 135. 分发糖果
- 136. 只出现一次的数字
- 137. 只出现一次的数字 II
- 138. 复制带随机指针的链表
- 139. 单词拆分
- 140. 单词拆分 II
- 141. 环形链表
- 142. 环形链表 II
- 143. 重排链表
- 144. 二叉树的前序遍历
- 145. 二叉树的后序遍历
- 146. LRU缓存机制
- 147. 对链表进行插入排序
- 148. 排序链表
- 149. 直线上最多的点数
- 150. 逆波兰表达式求值
101. 对称二叉树
思路
递归与迭代的区别:递归是自己延伸出去,而迭代是将得到的结果替代自己
递归(函数不断引用自身,知道引用的对象已知):
- 根结点拆分为两个,即将原二叉树拆分为左子树和右子树
- 镜像比较即比较左节点和右节点结构与值是否相等
- 递归左子树和右子树
class Solution(object):
def isSymmetric(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
def helper(p, q):
if not p and not q:
return True
if not p or not q:
return False
if p.val != q.val:
return False
return helper(p.left, q.right) and helper(p.right, q.left)
return helper(root, root)
思路
迭代(每一次迭代得到的结果作为下一次迭代的初始值):
- 插入两次根结点至队列中,及拆分为左右子树
- 取出队列的前两个结点进行比较
- 插入左边的左、右结点和右边的右、左结点至队列中,如果镜像相等则队列的前两个结点相等
class Solution(object):
def isSymmetric(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
p, q = root, root
queue = list()
queue.append(p)
queue.append(q)
while queue:
p = queue.pop(0)
q = queue.pop(0)
if not p and not q: # 左右结点为空但未遍历结束
continue
if not p or not q: # 左右结点一个有值一个为空
return False
if p.val != q.val: # 左右结点值不相等
return False
queue.append(p.left)
queue.append(q.right)
queue.append(p.right)
queue.append(q.left)
return True
102. 二叉树的层序遍历
思路
BFS(宽度优先搜索算法)的基础上修改为层次遍历
- 将根结点root保存在队列queue中
- 当队列不为空时,计算队列的长度(len(queue)),从队头中取出len(queue)个结点,即一层的结点数,并插入其子节点至队末
- 将一层的结点的值保存在临时数组tmp中
- 将tmp添加至返回数组ans中
class Solution(object):
def levelOrder(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
"""
queue = list()
ans = list()
queue.append(root)
if not root: # 列表为空,返回空列表
return ans
while queue:
tmp = list() # 保存每层的结点值
for i in range(len(queue)): # 从队列中取出上一层的结点,并插入子节点至队末
node = queue.pop(0)
tmp.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
ans.append(tmp)
return ans
103. 二叉树的锯齿形层次遍历
思路
上一题102. 二叉树的层序遍历 的思路
偶数行时,将数组reverse
class Solution(object):
def zigzagLevelOrder(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
"""
queue = list()
ans = list()
queue.append(root)
direction = False
if not root: # 列表为空,返回空列表
return ans
while queue:
tmp = list()
for i in range(len(queue)):
node = queue.pop(0)
tmp.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
if direction:
tmp.reverse()
ans.append(tmp)
else:
ans.append(tmp)
direction = not direction
return ans
104. 二叉树的最大深度
思路
递归
class Solution(object):
def maxDepth(self, root):
"""
:type root: TreeNode
:rtype: int
"""
if not root:
return 0
leftDepth = self.maxDepth(root.left)
rightDepth = self.maxDepth(root.right)
return max(leftDepth, rightDepth) + 1
105. 从前序与中序遍历序列构造二叉树
思路
迭代:
preorder:前序遍历,根左右
inorder: 中序遍历,左根右
- 根root为preorder第0位,即preorder[0],并把根结点root压进堆stack中
- inorderIndex指向中序遍历第0位
- 遍历preorder剩下的数,查看stack[-1]栈顶的数node(3, 已经构建为数的结点)是否等于inorder[0](9, 最左边的结点):
- 如果不等于,即node不是最左边的结点(node.val:3 != 9),则node的左节点为preorder1,并将其压进堆stack中。
- 如果等于,且栈不为空(栈中保存了已构造的结点),栈顶的值等于inorder的值时,将其从栈中pop出,inorderIndex往右移动一位直到inorderIndex指向的数与栈顶的数不相等时,即preorder[2]为node的右节点,并将其压进堆stack中
class Solution(object):
def buildTree(self, preorder, inorder):
"""
:type preorder: List[int]
:type inorder: List[int]
:rtype: TreeNode
"""
if not preorder:
return None
root = TreeNode(preorder[0])
stack = [root]
inorderIndex = 0
for i in range(1, len(preorder)):
node = stack[-1]
if node.val != inorder[inorderIndex]:
node.left = TreeNode(preorder[i])
stack.append(node.left)
else:
while stack and stack[-1].val == inorder[inorderIndex]:
node = stack.pop()
inorderIndex += 1
node.right = TreeNode(preorder[i])
stack.append(node.right)
return root
106. 从中序与后序遍历序列构造二叉树
思路
迭代:
inorder: 中序遍历,左根右
postorder:后序遍历,左右根
参考上一题105. 从前序与中序遍历序列构造二叉树
class Solution(object):
def buildTree(self, inorder, postorder):
"""
:type inorder: List[int]
:type postorder: List[int]
:rtype: TreeNode
"""
if not postorder:
return None
root = TreeNode(postorder[-1])
stack = [root]
inorderInder = len(inorder) -1
for i in range(len(postorder)-2, -1, -1):
node = stack[-1]
if node.val != inorder[inorderInder]:
node.right = TreeNode(postorder[i])
stack.append(node.right)
else:
while stack and stack[-1].val == inorder[inorderInder]:
node = stack.pop()
inorderInder -= 1
node.left = TreeNode(postorder[i])
stack.append(node.left)
return root
107. 二叉树的层次遍历 II
思路
参考 102. 二叉树的层序遍历
class Solution(object):
def levelOrderBottom(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
"""
queue = list()
ans = list()
queue.append(root)
if not root: # 列表为空,返回空列表
return ans
while queue:
tmp = list() # 保存每层的结点值
for i in range(len(queue)): # 从队列中取出上一层的结点,并插入子节点至队末
node = queue.pop(0)
tmp.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
ans.insert(0, tmp) # 将102题的append修改为insert
return ans
108. 将有序数组转换为二叉搜索树
思路
递归:
中间结点作为跟结点,左边数组递归构造左子树,右边数组递归构造右子树
class Solution(object):
def sortedArrayToBST(self, nums):
"""
:type nums: List[int]
:rtype: TreeNode
"""
if not nums:
return None
mid = len(nums)//2
root = TreeNode(nums[mid])
root.left = self.sortedArrayToBST(nums[:mid])
root.right = self.sortedArrayToBST(nums[mid+1:])
return root
109. 有序链表转换二叉搜索树
思路
递归:
- 当链表head为空时返回空;当链表只有一个结点时直接返回
- two走两步,one走一步,当two走到链表尾时,one在链表中间,得到根结点
- 递归左子树,递归右子树
class Solution(object):
def sortedListToBST(self, head):
"""
:type head: ListNode
:rtype: TreeNode
"""
if not head:
return None
if head.next == None:
return TreeNode(head.val)
one, two = head.next, head.next.next
pre = head
while two and two.next:
pre = one
one = one.next
two = two.next.next
pre.next = None
root = TreeNode(one.val)
root.left = self.sortedListToBST(head)
root.right = self.sortedListToBST(one.next)
return root
110. 平衡二叉树
思路
递归:
- 使用104. 二叉树的最大深度 辅助求左右子树的高度
- 左右子树高度相差小于2时,递归左子树,递归右子树
class Solution(object):
def maxDepth(self, root):
if not root:
return 0
return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1
def isBalanced(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
if not root:
return True
return abs(self.maxDepth(root.left) - self.maxDepth(root.right)) < 2 and self.isBalanced(root.left) and self.isBalanced(root.right)
111. 二叉树的最小深度
思路
递归:
- 二叉树为空,返回0
- 二叉树只有根结点,返回1
- 设置最小高度为无限大,如果有左子树,计算左子树的最小高度;如果有右子树,计算右子树的最小高度
class Solution(object):
def minDepth(self, root):
"""
:type root: TreeNode
:rtype: int
"""
if not root:
return 0
if not root.right and not root.left:
return 1
minD = float("inf")
if root.left:
minD = min(minD, self.minDepth(root.left))
if root.right:
minD = min(minD, self.minDepth(root.right))
return minD + 1
112. 路径总和
思路
深度优先搜索DFS:
sum减去当前结点的值,当遍历到叶子结点时(root.left和root.right为None),判断sum是否为0,是返回True,否返回False
class Solution(object):
def hasPathSum(self, root, sum):
"""
:type root: TreeNode
:type sum: int
:rtype: bool
"""
if not root:
return False
sum -= root.val
if not root.left and not root.right:
return sum == 0
return self.hasPathSum(root.left, sum) or self.hasPathSum(root.right, sum)
113. 路径总和 II
思路
深度优先搜索DFS:
参考 112. 路径总和 当没有子节点时,tmp保存结点的值,当总和等于sum时添加至tmp至ans中。tmp.pop()的作用是回溯
class Solution(object):
def pathSum(self, root, sum):
"""
:type root: TreeNode
:type sum: int
:rtype: List[List[int]]
"""
def helper(root, sum):
if not root:
return
tmp.append(root.val)
if not root.left and not root.right and root.val == sum:
t = copy.copy(tmp) # 数组是引用类型,不copy的话,tmp变化后,ans里的tmp也会随之变化
ans.append(t)
helper(root.left, sum-root.val)
helper(root.right, sum-root.val)
tmp.pop()
tmp=list()
ans = list()
helper(root, sum)
return ans
114. 二叉树展开为链表
思路
- 先序遍历把结点保存在列表中
- 链接列表中的结点到原二叉树中
class Solution(object):
def preorder(self, root):
return [] if root == None else [root] + self.preorder(root.left) + self.preorder(root.right)
def flatten(self, root):
"""
:type root: TreeNode
:rtype: None Do not return anything, modify root in-place instead.
"""
pre = self.preorder(root)
tmp = root
for i in range(1, len(pre)):
tmp.right = pre[i]
tmp.left = None
tmp = tmp.right
115. 不同的子序列
思路
动态规划:
栗子
dp | S | “” | r | a | b | b | b | i | t |
---|---|---|---|---|---|---|---|---|---|
T | “” | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
- | r | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
- | a | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
- | b | 0 | 0 | 0 | 1 | 2 | 3 | 3 | 3 |
- | b | 0 | 0 | 0 | 0 | 1 | 3 | 3 | 3 |
- | i | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 3 |
- | t | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 3 |
- 创建dp数组,长为S字符串的长度n加1,高为T字符串的长度m加1
- 初始化第一行和第一列:
- 第一行dp[0][i]:当T为空"“时,S只有一个子序列”"等于T
- 第一列dp[i][0]:当S为空""时,T不为空时,S的子序列中不包含T,均为0
- 递推公式:
- 当t[i-1]==s[j-1]时(i的区间是[1:m], j的区间是[1:n]):dp[i][j]=dp[i-1][j-1]+dp[i][j-1](dp[i-1][j-1]意味着s[:j-1]子序列中出现t[:i-1]的次数, dp[i][j-1]意味着s[:j-1]中出现t[:i]的次数)
- 当t[i-1]!=s[j-1]时:dp[i][j]=dp[i][j-1]
class Solution(object):
def numDistinct(self, s, t):
"""
:type s: str
:type t: str
:rtype: int
"""
m, n = len(t), len(s)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(n+1):
dp[0][i] = 1
for i in range(1, m+1):
for j in range(1, n+1):
if t[i-1] == s[j-1]:
dp[i][j] = dp[i-1][j-1] + dp[i][j-1]
else:
dp[i][j] = dp[i][j-1]
return dp[-1][-1]
116. 填充每个节点的下一个右侧节点指针
思路
参考 102. 二叉树的层序遍历
BFS(宽度优先搜索算法)的基础上修改为层次遍历
- 将根结点root保存在队列queue中
- 当队列不为空时,计算队列的长度(len(queue)),从队头中取出第一个结点pre, 再遍历该层后面的结点cur,将pre.next指向cur后,插入其子节点至队末,并更新pre为cur
- 返回root
class Solution(object):
def connect(self, root):
"""
:type root: Node
:rtype: Node
"""
if not root:
return root
queue = [root]
while queue:
len_queue = len(queue)
pre = queue.pop(0)
if pre.left:
queue.append(pre.left)
if pre.right:
queue.append(pre.right)
for i in range(1, len_queue):
cur = queue.pop(0)
pre.next = cur
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
pre = cur
return root
117. 填充每个节点的下一个右侧节点指针 II
思路
参考 116. 填充每个节点的下一个右侧节点指针
class Solution(object):
def connect(self, root):
"""
:type root: Node
:rtype: Node
"""
if not root:
return root
queue = [root]
while queue:
len_queue = len(queue)
pre = queue.pop(0)
if pre.left:
queue.append(pre.left)
if pre.right:
queue.append(pre.right)
for i in range(1, len_queue):
cur = queue.pop(0)
pre.next = cur
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
pre = cur
return root
118. 杨辉三角
思路
在杨辉三角中,每一行的第一位和最后一位是1,其他数是它左上方和右上方的数的和
class Solution(object):
def generate(self, numRows):
"""
:type numRows: int
:rtype: List[List[int]]
"""
ans = list()
for i in range(numRows):
tmp = [None]*(i+1)
for j in range(i+1):
tmp[j] = 1 if j == 0 or j == i else ans[i-1][j-1] + ans[i-1][j]
ans.append(tmp)
return ans
119. 杨辉三角 II
思路
参考 118. 杨辉三角
class Solution(object):
def getRow(self, rowIndex):
"""
:type rowIndex: int
:rtype: List[int]
"""
ans = [None] * (rowIndex+1)
for i in range(rowIndex+1):
tmp = copy.copy(ans)
for j in range(i+1):
ans[j] = 1 if j==0 or j==i else tmp[j-1] + tmp[j]
return ans
120. 三角形最小路径和
思路
动态规划
- 创建dp数组,第1位即为第一层的最短路径triangle[0][0]
- 大于1行后,第一位dp[0]最小路径和为triangle当前值加上一层dp0的值;最后一位最小路径和为triangle当前值加上一层dp最后一个值;中间位置的最短路径为triangle当前值加上一层结点下标等于当前下标或上一层结点下标 -1中的较小值更新为当前位置的最小路径和
class Solution(object):
def minimumTotal(self, triangle):
"""
:type triangle: List[List[int]]
:rtype: int
"""
if not triangle:
return 0
n = len(triangle)
dp = [None] * n
dp[0] = triangle[0][0]
for i in range(1, n):
tmp = copy.copy(dp) # tmp保存上一层的最小路径和
for j in range(i+1): # 更新dp为当前层的最小路径和
if j == 0:
dp[j] = triangle[i][j]+tmp[j]
elif j==i:
dp[j] = triangle[i][j]+tmp[j-1]
else:
dp[j] = triangle[i][j] + min(tmp[j], tmp[j-1])
return min(dp)
121. 买卖股票的最佳时机
思路
第一天开始可以买股票,第二天开始可以卖股票。可以卖股票时,减去在前几天能买股票的最低价格
class Solution(object):
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
if not prices:
return 0
n = len(prices)
mini, profit = prices[0], 0
for i in range(1, n):
profit = max(profit, prices[i]-mini)
mini = min(mini, prices[i])
return profit
122. 买卖股票的最佳时机 II
思路
当天价格比前一天的价格高时,卖出股票
栗子:prices = [7,1,5,6,3,6,4]
class Solution(object):
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
n = len(prices)
cost, profit = prices[0], 0
for i in range(1, n):
if prices[i] > cost: # 当天价格比前一天的价格高时,卖出股票
profit += prices[i] - cost
cost = prices[i] # 更新成本位当天的价格
return profit
123. 买卖股票的最佳时机 III
思路
见代码备注
class Solution(object):
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
if not prices:
return 0
buy1, sell1, buy2, sell2 = -prices[0], -float("inf"), -float("inf"), -float("inf")
for i in range(1, len(prices)):
buy1 = max(buy1, -prices[i]) # 第一次买股票,负数更大表示成本更低
sell1 = max(sell1, prices[i]+buy1) # 第一次卖股票, 当前价格高于buy1时卖出(buy1为负数),得到第一次卖股票的利润
buy2 = max(buy2, sell1-prices[i]) # 第二次买股票后剩余的钱
sell2 = max(sell2, prices[i]+buy2) # 第二次卖股票,当前价格加上第二次买股票后剩余钱,得到第二次卖股票的利润
return max(0, sell2)
124. 二叉树中的最大路径和
思路
回溯
- 递归计算左子树最大路径和
- 递归计算右子树最大路集和
- 更新结点的最大路径和,由左子树最大路径和+加当前值+右子树最大路集和。路径是左子树到结点到右子树
- 更新单边最大路径和,如果加上左右的路径和,路径会出现分岔
栗子:
class Solution(object):
def __init__(self):
self.max_val = -float("inf")
def maxPathSum(self, root):
"""
:type root: TreeNode
:rtype: int
"""
def maxSum(root):
if not root:
return 0
left_val = max(0, maxSum(root.left)) # 左子树的最大路径和
right_val = max(0, maxSum(root.right)) # 右子树的最大路径和
self.max_val = max(self.max_val, root.val + left_val + right_val) # 更新结点的最大路径和
return root.val+max(left_val, right_val) # 更新单边最大路径和
maxSum(root)
return self.max_val
125. 验证回文串
思路
- 过滤剩下数字和字母后全部变为小写
- s[::-1]可翻转字符串,与原字符串比较
注:列表翻转可用reversed(s)
class Solution(object):
def isPalindrome(self, s):
"""
:type s: str
:rtype: bool
"""
s = "".join(filter(str.isalnum, str(s))).lower()
return s[::-1]==s
126. 单词接龙 II
思路
- 构建neighbor用于查找邻接词,单词长度通常较小,有一位不相等的即为邻居
- 构建preWords记录结点的前驱结点
- 构建queue用于广度优先搜索遍历
- 构建visited记录已访问的结点
- 搜索到endWord或当前层级大于已得到的路径长度时停止遍历
- 利用列表嵌套推导式得到答案
栗子
输入:
beginWord = “hit”
endWord = “cog”
wordList = [“hot”,“dot”,“dog”,“lot”,“log”,“cog”]
广度优先搜索遍历得到:
preWords = defaultdict(<class ‘list’>, {‘hot’: [‘hit’], ‘dot’: [‘hot’], ‘lot’: [‘hot’], ‘dog’: [‘dot’], ‘log’: [‘lot’], ‘cog’: [‘dog’, ‘log’]})
visited = <class ‘dict’>: {‘hit’: 1, ‘hot’: 2, ‘dot’: 3, ‘lot’: 3, ‘dog’: 4, ‘log’: 4, ‘cog’: 5}
from collections import deque, defaultdict
class Solution(object):
def findLadders(self, beginWord, endWord, wordList):
"""
:type beginWord: str
:type endWord: str
:type wordList: List[str]
:rtype: List[List[str]]
"""
if endWord not in wordList:
return []
wordList.append(beginWord)
wordList = list(set(wordList)) # 去重
neighbor = defaultdict(list) # 构建neighbor用于查找邻接词
preWords = defaultdict(list)
queue = deque([(beginWord, 1)]) # 将单词和层级放到队列中
visited = {beginWord:1}
for word in wordList:
for i in range(len(beginWord)):
neighbor[word[0:i]+'*'+word[i+1:]].append(word)
while queue:
curWord, level = queue.popleft() # popleft()取左边的结点
for i in range(len(beginWord)):
w = curWord[0:i]+'*'+curWord[i+1:]
for word in neighbor[w]:
if word not in visited:
visited[word] = level + 1
queue.append((word, level+1))
if visited[word] == level + 1: # 深度为curWord加1时,讲curWord作为word的前驱词
preWords[word].append(curWord)
if endWord in visited and level + 1 > visited[endWord]: # 搜索到endWord或当前层级大于已得到的路径长度
break
if endWord in visited:
ans = [[endWord]]
while ans[0][0] != beginWord:
ans = [[word] + pre for pre in ans for word in preWords[pre[0]]]
return ans
else:
return []
127. 单词接龙
思路
参考 126. 单词接龙 II
class Solution(object):
def ladderLength(self, beginWord, endWord, wordList):
"""
:type beginWord: str
:type endWord: str
:type wordList: List[str]
:rtype: int
"""
if endWord not in wordList:
return 0
wordList.append(beginWord)
wordList = list(set(wordList)) # 去重
neighbor = defaultdict(list) # 构建neighbor用于查找邻接词
preWords = defaultdict(list)
queue = deque([(beginWord, 1)]) # 将单词和层级放到队列中
visited = {beginWord:1}
for word in wordList:
for i in range(len(beginWord)):
neighbor[word[0:i]+'*'+word[i+1:]].append(word)
while queue:
curWord, level = queue.popleft() # popleft()取左边的结点
for i in range(len(beginWord)):
w = curWord[0:i]+'*'+curWord[i+1:]
for word in neighbor[w]:
if word not in visited:
visited[word] = level + 1
queue.append((word, level+1))
if visited[word] == level + 1: # 深度为curWord加1时,讲curWord作为word的前驱词
preWords[word].append(curWord)
if endWord in visited and level + 1 > visited[endWord]: # 搜索到endWord或当前层级大于已得到的路径长度
break
return visited[endWord] if endWord in visited else 0
128. 最长连续序列
思路
- 当存在比自身更小的数时,什么都不做
- 当不存在比自身更小的数时,连续寻找比自己大1的数并记录长度
class Solution(object):
def longestConsecutive(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
nums = list(set(nums))
maxLen = 0
for num in nums:
if num-1 not in nums:
curNum = num
curlen = 1
while curNum+1 in nums:
curNum += 1
curlen += 1
maxLen = max(maxLen, curlen)
return maxLen
129. 求根到叶子节点数字之和
思路
- 当前结点的值为父节点的值tmp乘以10加上当前结点的值
- 走到叶子结点时返回结果
- 遍历左子树和右子树并相加
class Solution(object):
def sumNumbers(self, root):
"""
:type root: TreeNode
:rtype: int
"""
def helper(root, tmp=0):
if not root:
return 0
tmp = tmp*10+root.val # 当前结点的值为父节点的值tmp乘以10加上当前结点的值
if not root.left and not root.right: # 叶子结点
return tmp
return helper(root.left, tmp) + helper(root.right, tmp)
return helper(root)
130. 被围绕的区域
思路
- 边界有“0”时,入队queue
- 当queue不为空时,遍历(x, y)相邻的点,当为“O”时,记录在connected中,即与边界“O”相连的点
- 遍历board中间的点,没有与边界“O”相连且等于“O”的点(不在connected中的点)修改为“X”
class Solution(object):
def solve(self, board):
"""
:type board: List[List[str]]
:rtype: None Do not return anything, modify board in-place instead.
"""
if not board:
return
m, n = len(board), len(board[0])
queue = list()
for i in range(m):
if board[i][0] == "O":
queue.append((i,0))
if board[i][n-1] == "O":
queue.append((i, n-1))
for i in range(n):
if board[0][i] == "O":
queue.append((0, i))
if board[m-1][i] == "O":
queue.append((m-1, i))
connected = queue[:]
while queue:
x, y = queue.pop(0)
for xx, yy in [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]: # 遍历(x, y)相邻的点
if 0<xx<m and 0<yy<n:
if (xx, yy) not in connected and board[xx][yy]=="O":
connected.append((xx, yy))
queue.append((xx, yy))
for i in range(1, m):
for j in range(1, n):
if (i, j) not in connected and board[i][j] == "O":
board[i][j]="X"
131. 分割回文串
思路
回溯
class Solution(object):
def partition(self, s):
"""
:type s: str
:rtype: List[List[str]]
"""
ans = list()
def backtrack(s, start, length, tmp=list()):
if start == length:
ans.append(tmp[:])
for i in range(start, length): # 判断s[start:i+1]不是回文,跳过
if s[start:i+1] == s[start:i+1][::-1]:
backtrack(s, i+1, length, tmp+[s[start:i+1]]) # 判断s[i+1,length]是否回文
backtrack(s, 0, len(s))
return ans
132. 分割回文串 II
思路
动态规划:
- 当s长度n为0或1时,无需分割,返回0
- 创建长度为n的数组dp,单个字符一定是回文,因此初始化dp数组为0到n。
- 遍历字符串s,如果长度为i的字符是回文,则dp[i]=0
- 长度为i的字符非回文时,需要分割。求状态转移方程:遍历i前面的字符,索引为j,如果长度s[j+1,i+1]是回文字符的话,就在dp[j]的基础上加1则可。因此,状态转移方程为:dp[i] = min(dp[j] + 1 for j in range(i) if s[j+1:i+1]==s[j+1:i+1][::-1])
class Solution(object):
def minCut(self, s):
"""
:type s: str
:rtype: int
"""
n = len(s)
if n < 2:
print(0)
dp = [i for i in range(n)]
for i in range(1, n):
if s[0:i+1] == s[0:i+1][::-1]:
dp[i] = 0
continue
dp[i] = min(dp[j] + 1 for j in range(i) if s[j+1:i+1]==s[j+1:i+1][::-1])
return dp[-1]
133. 克隆图
思路
广度优先遍历 BFS
class Solution(object):
def cloneGraph(self, node):
"""
:type node: Node
:rtype: Node
"""
if not node:
return node
visited = dict()
visited[node] = Node(node.val)
queue = [node]
while queue:
n = queue.pop()
for neighbor in n.neighbors:
if neighbor not in visited:
queue.append(neighbor)
visited[neighbor] = Node(neighbor.val)
visited[n].neighbors.append(visited[neighbor])
return visited[node]
134. 加油站
思路
- 当gas数组的总和大于等于cost数组的总和一定能绕环路行驶一周,res_gas为gas减cost(因为如果题目有解,该答案即为唯一答案)
- 出发点start,当前点cur,当前油量为cur_gas,当cur_gas小于0时,即start到cur不够油,则更新cur_gas为0,下一个点再出发start=cur+1
- 当出发点start到达起始点0点时,如res_gas大于等于0,即start为唯一答案,返回start
- 当gas数组的总和小于cost数组的总和则无法绕环路行驶一周(显而易见)。返回-1
class Solution(object):
def canCompleteCircuit(self, gas, cost):
"""
:type gas: List[int]
:type cost: List[int]
:rtype: int
"""
start, cur_gas, res_gas = 0, 0, 0
for cur in range(len(gas)):
cur_gas = cur_gas + gas[cur]-cost[cur]
res_gas = res_gas + gas[cur]-cost[cur]
if cur_gas < 0:
cur_gas = 0
start = cur + 1
return -1 if res_gas < 0 else start
135. 分发糖果
思路
- 创建candys数组记录给孩子的糖果数
- 往右遍历,右边孩子评分高于左边时,右边孩子的糖果数比左边多1个
- 往左遍历,左边孩子评分高于右边时,左边孩子的糖果数比右边多1个,比较步骤2的结果取较大值
- 返回candys数组的总和
class Solution(object):
def candy(self, ratings):
"""
:type ratings: List[int]
:rtype: int
"""
if not ratings:
return 0
n = len(ratings)
candys = [1 for _ in range(n)]
for i in range(1, n):
if ratings[i] > ratings[i-1]:
candys[i] = candys[i-1] + 1
for i in range(n-2, -1, -1):
if ratings[i] > ratings[i+1]:
candys[i] = max(candys[i], candys[i+1]+1)
return sum(candys)
136. 只出现一次的数字
思路
reduce() 函数会对参数序列中元素进行累积。
函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果
nums里的数做异或操作:
0^num = num
num1 ^ num1 = 0
from functools import reduce
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
return reduce(lambda x, y: x^y, nums)
137. 只出现一次的数字 II
思路
位运算
位运算符 | 说明(python) |
---|---|
<< | 按位左移,左移n位相当于乘以2的n次方 |
>> | 按位右移 ,左移n位相当于除以2的n次方 |
& | 按位与,二进制位数同且为1结果位为1 |
l | 按位或 ,二进制位数或有1结果位为1 |
^ | 按位异或 ,二进制位数不同结果位为1 |
~ | 按位取反,二进制位0和1结果位互换 |
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
once, twice = 0, 0
for num in nums:
once = ~twice&(once^num)
twice = ~once&(twice^num)
return once
138. 复制带随机指针的链表
思路
- 将克隆的结点clone链接在原结点后(node1–>clone1–>node2–>clone2–>…)
- 拷贝random:node1.next.random(clone1) = node1.random.next
- 连接克隆的结点:node1–>node2–>…, clone1–>clone2–>…
class Solution(object):
def copyRandomList(self, head):
"""
:type head: Node
:rtype: Node
"""
if not head:
return head
node = head
while node:
clone = Node(node.val, None, None)
clone.next = node.next
node.next = clone
node = clone.next
node = head
while node:
node.next.random = node.random.next if node.random else None
node = node.next.next
node = head
clone = head.next
ans = head.next
while node:
node.next = clone.next
clone.next = node.next.next if clone.next else None
node = node.next
clone = clone.next
return ans
139. 单词拆分
思路
动态规划
class Solution(object):
def wordBreak(self, s, wordDict):
"""
:type s: str
:type wordDict: List[str]
:rtype: bool
"""
n = len(s)
dp = [False for _ in range(n+1)]
dp[0] = True
for i in range(n+1):
for j in range(i):
if dp[j] and (s[j:i] in wordDict): # s前i个字符中,前j个字符存在wordDict中,后j到i个字符也存在wordDict中时,dp[i]为True
dp[i] = True
break
return dp[-1]
140. 单词拆分 II
思路
- 回溯 参考131.分割回文串,提交会超时
- 优化:连接wordDict为一个字符串,遍历s字符串中的字符,有不存在wordDict的字符时返回空数组
class Solution(object):
def wordBreak(self, s, wordDict):
"""
:type s: str
:type wordDict: List[str]
:rtype: List[str]
"""
def backtrack(s, wordDict, start, n, tmp=list()):
if start == n:
ans.append(" ".join(tmp))
for i in range(start, n):
if s[start:i+1] in wordDict:
backtrack(s, wordDict, i+1, n, tmp+[s[start:i+1]])
ans = list()
words = "".join(wordDict)
for char in s:
if char not in words:
return ans
backtrack(s, wordDict, 0, len(s), [])
return ans
141. 环形链表
思路
- visited数组添加已访问的结点
- 链表中有环则会访问已访问的结点
class Solution(object):
def hasCycle(self, head):
"""
:type head: ListNode
:rtype: bool
"""
visited = list()
while head:
if head in visited:
return True
else:
visited.append(head)
head = head.next
return False
142. 环形链表 II
思路
参考 141. 环形链表
class Solution(object):
def detectCycle(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
visited = list()
ans = head
while ans:
if ans in visited:
return ans
else:
visited.append(ans)
ans = ans.next
return None
143. 重排链表
思路
- 用快慢指针找到中间结点first
- 将后半段结点倒转,如1–>2–3-->4–>5–>6变为1–>2–>3–>6–>5–>4
- 将后半段结点逐一插入前半段结点中。如1–>6–>2–>5–>3–>4
class Solution(object):
def reorderList(self, head):
"""
:type head: ListNode
:rtype: None Do not return anything, modify head in-place instead.
"""
if not head or not head.next or not head.next.next:
return head
first = head.next
second = head.next.next
while second.next:
if second.next.next:
second = second.next.next
first = first.next
else:
second = second.next
invert = first.next.next if first.next.next else None
first.next.next = None
while invert:
cur = invert.next
invert.next = first.next
first.next = invert
invert = cur
front = head
back = first.next
while back:
cur = back.next
first.next = back.next
back.next = front.next
front.next = back
front = back.next
back = cur
return head
144. 二叉树的前序遍历
思路
递归
class Solution(object):
def preorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
return [] if not root else [root.val] + self.preorderTraversal(root.left) + self.preorderTraversal(root.right)
思路
迭代
- 进栈:中,右左
- 出栈:中,左右(先进后出)
class Solution(object):
def preorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
if not root:
return []
queue, ans = [root], list()
while queue:
node = queue.pop()
ans.append(node.val)
if node.right:
queue.append(node.right)
if node.left:
queue.append(node.left)
return ans
145. 二叉树的后序遍历
思路
递归
class Solution(object):
def postorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
return [] if not root else self.postorderTraversal(root.left) + self.postorderTraversal(root.right) + [root.val]
思路
迭代
- 进栈:中,左右
- 出栈:中,右左(先进后出)
- 将出栈的数组反转
class Solution(object):
def postorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
if not root:
return []
queue, ans = [root], list()
while queue:
node = queue.pop()
ans.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return ans[::-1]
146. LRU缓存机制
思路
- 创建缓存哈希表cache(字典:键值),可快速查询,对应的value存在双链表中,在链表头部表示刚访问了,在链表尾部表示最久没访问。put数据时大于缓存capacity大小时,删除链表尾部结点
- get:查询key是否存在缓存cache中,是则将对应值的结点移动至链表头部,并返回对应的值。否则返回-1
- put:查询key是否存在缓存cache中,是则修改对应值的结点的值,并移动结点至双链表头部。否则判断链表长度是否大于capacity,如果大于,则先删除尾部结点,然后创建新的结点node = DListNode(key, value)并插入至链表头部
# Definition for double-linked list.
class DListNode(object):
def __init__(self, key=0, value=0, prev=None, next=None):
self.key = key
self.value = value
self.prev = prev
self.next = next
class LRUCache(object):
def __init__(self, capacity):
"""
:type capacity: int
"""
self.cache = dict() # 缓存哈希表:cache的key存放关键词,value存放DListNode(key, value)
self.head = DListNode() # 双链表头部
self.tail = DListNode() # 双链表尾部
self.head.next = self.tail # 链接双链表
self.tail.prev = self.head
self.capacity = capacity # 缓存容量
self.size = 0 # 缓存大小
def get(self, key):
"""
:type key: int
:rtype: int
"""
if key in self.cache:
node = self.cache[key] # cache的key存放关键词,value存放DListNode(key, value)
self.moveToHead(node)
return node.value
return -1
def put(self, key, value):
"""
:type key: int
:type value: int
:rtype: None
"""
if key not in self.cache:
if self.size >= self.capacity:
removeNode = self.removeTail()
self.cache.pop(removeNode.key)
self.size -= 1
node = DListNode(key, value)
self.cache[key] = node
self.addToHead(node)
self.size += 1
else:
node = self.cache[key]
node.value = value
self.moveToHead(node)
def addToHead(self, node): # 添加至双链表头部
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
node.prev = self.head
def removeNode(self, node): # 删除链表结点
node.prev.next = node.next
node.next.prev = node.prev
def moveToHead(self, node): # 移动结点至链表头部
self.removeNode(node) # 先删除结点
self.addToHead(node) # 移动到链表头部
def removeTail(self):
node = self.tail.prev
self.removeNode(node)
return node # 返回尾部要删除的结点,以便删除cache中对应的值
# Your LRUCache object will be instantiated and called as such:
# obj = LRUCache(capacity)
# param_1 = obj.get(key)
# obj.put(key,value)
147. 对链表进行插入排序
思路
- 没有结点或只有一个结点时,直接返回head
- 创建dummy指向head的头结点,pre指向第一个结点head,node指向第二个结点,当node结点大于pre时,不操作指向后一个结点。当node大于pre(前一结点)时,temp指向dummy头结点,重头寻找大于node的结点(得到位置为temp.next),将node插入temp结点后
class Solution(object):
def insertionSortList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
if not head or not head.next:
return head
dummy = ListNode(-1)
dummy.next = head
pre = head
node = head.next
while node:
if node.val < pre.val:
temp = dummy
while temp.next.val < node.val: # 查找插入的位置
temp = temp.next
pre.next = node.next
node.next = temp.next
temp.next = node
node = pre.next
else:
pre = pre.next
node = node.next
return dummy.next
148. 排序链表
思路
归并排序
class Solution(object):
def sortList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
if not head or not head.next:
return head
first, second = head, head.next
while second and second.next: # 奇偶数个结点时,second指向最后一个结点;数个结点时,second指向尾部None
first, second = first.next, second.next.next
mid, first.next = first.next, None
left, right = self.sortList(head), self.sortList(mid) # 分割为两个链表
temp = dummy = ListNode(-1) # 两个链表的头结点比较大小组成新的链表
while left and right:
if left.val < right.val:
temp.next, left = left, left.next
else:
temp.next, right = right, right.next
temp = temp.next
temp.next = left if left else right
return dummy.next
149. 直线上最多的点数
思路
直线两点式:
x
−
x
1
x
2
−
x
1
=
y
−
y
1
y
2
−
y
1
=
>
(
x
−
x
1
)
(
y
2
−
y
1
)
=
(
x
2
−
x
1
)
(
y
−
y
1
)
=
>
y
−
y
1
x
−
x
1
=
y
2
−
y
1
x
2
−
x
1
=
>
d
y
d
x
\frac{x-x_1}{x_2-x_1} =\frac{y-y_1}{y_2-y_1}=>(x-x_1)(y_2-y_1)=(x_2-x_1)(y-y_1)=>\frac{y-y_1}{x-x_1} =\frac{y_2-y_1}{x_2-x_1}=>\frac{dy}{dx}
x2−x1x−x1=y2−y1y−y1=>(x−x1)(y2−y1)=(x2−x1)(y−y1)=>x−x1y−y1=x2−x1y2−y1=>dxdy
直接相除可能产生多位的浮点小数,因此分子分母除最大公约数简化得到的斜率相等,即为一条直线。创建slope(defaultdict),斜率为键,值为对应的点个数
注: defaultdict(int):比如list对应[ ],str对应的是空字符串,set对应set( ),int对应0
from collections import Counter, defaultdict
class Solution(object):
def gcd(self, x, y):
while y != 0:
temp = x%y
x = y
y = temp
return x
def maxPoints(self, points):
"""
:type points: List[List[int]]
:rtype: int
"""
if len(points) <=2:
return len(points)
count_points = Counter(tuple(point) for point in points) # 每个点对应的个数
points = list(count_points) # 去重
n = len(points)
if n == 1:
return count_points[points[0]]
ans = 0
for i in range(n-1):
x1, y1 = points[i]
slope = defaultdict(int) # 斜率, 默认值为0
for j in range(i+1, n):
x2, y2 = points[j]
dy, dx = y2-y1, x2-x1
g = self.gcd(dy, dx)
if g != 0: # g不为0,防止除0
dy, dx = dy//g, dx//g
slope["{}/{}".format(dy, dx)] += count_points[points[j]]
# print("({},{}), ({},{}) dy: {} dx: {} {}".format(x1, y1, x2, y2, dy, dx, slope))
ans = max(ans, max(slope.values())+count_points[points[i]])
return ans
150. 逆波兰表达式求值
思路
后缀表达式:用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中
class Solution:
def evalRPN(self, tokens)
stack = list()
for token in tokens:
if token not in "+-*/":
stack.append(token)
else:
y = stack.pop()
x = stack.pop()
# print("{}{}{}".format(x, token, y))
stack.append(str(int(eval(x+token+y))))
return int(stack[-1])