目录
树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)。B是A的子结构, 即 A中有出现和B相同的结构和节点值。
class Solution:
def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
if not A or not B: return False
def recur(A, B):
if not B: return True
if not A or A.val != B.val: return False
return recur(A.left, B.left) and recur(A.right, B.right)
return recur(A, B) or self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B)
题解:递归
recur函数:
- 终止条件
- 当节点B为空,说明B已经完成匹配,返回True
- 当节点A为空,说明已经越过A的叶节点仍未完成匹配,返回False
- 当节点A和B的值不同,说明不匹配,返回False
- 返回值
- 判断A和B的左子节点是否相同
- 判断A和B的右子节点是否相同
股票的最大利润
假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
class Solution:
def maxProfit(self, prices: List[int]) -> int:
cost, profit = float('inf'), 0
for price in prices:
cost = min(cost, price)
profit = max(profit, price - cost)
return profit
题解:动态规划
- 状态定义:设动态规划列表,表示以结尾的子数组的最大利润。
- 状态转移方程:前日的最大利润=(前日的最大利润,第日价格-前日最低价格)
把数字翻译成字符串
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
class Solution:
def translateNum(self, num: int) -> int:
str_num = str(num)
n = len(str_num)
dp = [1] * (n + 1)
for i in range(2, n + 1):
if str_num[i - 2] == '1' or (str_num[i - 2] == '2' and str_num[i - 1] < '6'):
dp[i] = dp[i - 2] + dp[i - 1]
else:
dp[i] = dp[i - 1]
return dp[n]
题解:动态规划
对于一个数字,可以有两种选择,一是只翻译自己,二是和前面的数组合翻译,前提是组合数在10-25之间。如果只翻译自己,状态转移方程为:,如果组合翻译,则状态转移方程为:。对于初始化,无数字情况下,原因是当第1,2位可以组合时,显然有,而显然,由此推出。
环形链表的入口
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
fast, slow = head, head
while fast:
if fast.next and fast.next.next:
fast = fast.next.next
slow = slow.next
else:
return None
if fast == slow:
break
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
K个一组翻转链表
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
class Solution:
def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
def reverse(head, tail):
cur = tail.next
p = head
while cur != tail:
post = p.next
p.next = cur
cur = p
p = post
return tail, head
hair = ListNode(0)
hair.next = head
prev = hair
while head:
tail = prev
for i in range(k):
tail = tail.next
if not tail:
return hair.next
nex = tail.next
head, tail = reverse(head, tail)
prev.next = head
tail.next = nex
prev = tail
head = tail.next
return hair.next
矩阵中的路径
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
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] = ''
res = dfs(i + 1, j, k + 1) or dfs(i, j + 1, k + 1) or dfs(i-1, j, k + 1) or dfs(i, j - 1, k + 1)
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
题解:回溯(深度优先搜索+剪枝)
- 递归参数:当前元素在矩阵中的行列索引i和j,当前字符在word中的索引k
- 终止条件:行列索引越界或当前矩阵元素与目标字符不一致则返回False,若k==len(word)-1,则字符串全部匹配完毕,返回True
- 递推工作:将board[i][j]标记为空,代表此元素已访问过;搜索上下左右相邻的元素;还原当前矩阵元素,即board[i][j]=word[k]。
二叉树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
def dfs(root, p, q):
if not root or root == p or root == q: return root
left = dfs(root.left, p, q)
right = dfs(root.right, p, q)
if not left: return right
if not right: return left
return root
return dfs(root, p, q)
题解:递归
- 终止条件:越过叶节点则返回null;当root等于p,q,则返回root
- 递推过程:递归左子节点,返回left;递归右子节点,返回right
- 返回值:若left和right同时为空,说明root的左右子树都不含p,q,返回null;若left和right同时不为空,说明p,q在root两侧,root为最近公共祖先;当left为空,right不为空,说明p,q都不在root的左子树中,返回right。
剪绳子
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1] 。请问 k[0]*k[1]*...*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
class Solution:
def cuttingRope(self, n: int) -> int:
dp = [0] * (n + 1)
dp[2] = 1
for i in range(3, n + 1):
for j in range(2, i):
dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]))
return dp[n]
题解:动态规划
对于dp[i],我们先把绳子剪掉第一段(长度为j),如果只剪掉长度为1,对乘积毫无增益,因此从2开始剪。剪了第一段之后,剩下的i-j长度可以剪也可以不剪,如果不剪,长度乘积为j*(i-j);如股票剪的话长度为j*dp[i-j]。因此,取两者最大值。
和为s的连续正数序列
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
class Solution:
def findContinuousSequence(self, target: int) -> List[List[int]]:
i, j = 1, 1
res = []
sum = 0
while (i <= target // 2):
if sum < target:
sum += j
j += 1
elif sum > target:
sum -= i
i += 1
else:
arr = list(range(i, j))
res.append(arr)
sum -= i
i += 1
return res
题解:滑动窗口法
滑动窗口一般表示成一个左闭右开区间,窗口的左边界和右边界永远只能向右移动。当窗口和小于target时,要扩大窗口,右边界向右移动;当窗口和大于target时,要减小窗口,左边界向右移动。
顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if not matrix or not matrix[0]: return []
rows, columns = len(matrix), len(matrix[0])
visited = [[False] * columns for _ in range(rows)]
total = rows * columns
order = [0] * total
directions = [[0, 1], [1, 0], [0, -1], [-1, 0]]
row, column = 0, 0
directionIndex = 0
for i in range(total):
order[i] = matrix[row][column]
visited[row][column] = True
nextRow, nextColum = directions[directionIndex][0] + row, directions[directionIndex][1] + column
if not(0 <= nextRow < rows and 0 <= nextColum < columns and not visited[nextRow][nextColum]):
directionIndex = (directionIndex + 1) % 4
row = row + directions[directionIndex][0]
column = column + directions[directionIndex][1]
return order
题解:模拟法
每一时刻更新下一个时刻所要访问的元素时,先试探按照当前状态更新的新元素是否被访问过或越界,然后再更新所要访问的元素。
把字符串转换成整数
写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。
class Solution:
def myAtoi(self, s: str) -> int:
res, i, sign, length = 0, 0, 1, len(s)
min_int, max_int, boundary = - 2 ** 31, 2 ** 31 - 1, 2 ** 31 // 10
if not s: return 0
while s[i] == ' ':
i += 1
if i == length: return 0
if s[i] == '-': sign = -1
if s[i] in '+-': i += 1
for c in s[i:]:
if not '0' <= c <= '9': break
if res > boundary or (res == boundary and c > '7'):
return max_int if sign == 1 else min_int
res = 10 * res + ord(c) - ord('0')
return sign * res
滑动窗口的最大值
给定一个数组 nums
和滑动窗口的大小 k
,请找出所有滑动窗口里的最大值。
import collections
class MyQueue:
def __init__(self):
self.queue = collections.deque()
def pop(self, value):
if self.queue and value == self.queue[0]:
self.queue.remove(self.queue[0])
def push(self, value):
while self.queue and self.queue[-1] < value:
self.queue.pop()
self.queue.append(value)
def front(self):
return self.queue[0]
class Solution:
def maxInWindows(self, num, size):
# write code here
if size <= 0 or size > len(num):
return []
res = []
queue = MyQueue()
for i in range(size):
queue.push(num[i])
res.append(queue.front())
for i in range(size, len(num)):
queue.pop(num[i - size])
queue.push(num[i])
res.append(queue.front())
return res
题解:单调队列
每次滑动窗口时,需要将队列最前面的删除(可能已经删除了),同时在向队列压入新元素时,要始终维持一个单调递减队列。
正则表达式匹配
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
class Solution:
def isMatch(self, s: str, p: str) -> bool:
m, n = len(s), len(p)
dp = [[False] * (n + 1) for _ in range(m + 1)]
dp[0][0] = True
for j in range(2, n + 1):
dp[0][j] = dp[0][j - 2] and p[j - 1] == '*'
for i in range(1, m + 1):
for j in range(1, n + 1):
if p[j - 1] == '*':
if dp[i][j - 2]: dp[i][j] = True
elif dp[i - 1][j] and s[i - 1] == p[j - 2]: dp[i][j] = True
elif dp[i - 1][j] and p[j - 2] == '.': dp[i][j] = True
else:
if dp[i - 1][j - 1] and s[i - 1] == p[j - 1]: dp[i][j] = True
elif dp[i - 1][j - 1] and p[j - 1] == '.': dp[i][j] = True
return dp[m][n]
题解:动态规划
最小编辑代价
给定两个字符串str1和str2,再给定三个整数ic,dc和rc,分别代表插入、删除和替换一个字符的代价,请输出将str1编辑成str2的最小代价。
class Solution:
def minEditCost(self , str1 , str2 , ic , dc , rc ):
# write code here
m, n = len(str1), len(str2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
dp[i][0] = dp[i - 1][0] + dc
for j in range(1, n + 1):
dp[0][j] = dp[0][j - 1] + ic
for i in range(1, m + 1):
for j in range(1, n + 1):
if str1[i - 1] == str2[j - 1]:
dp[i][j] = min(dp[i - 1][j] + dc, dp[i][j - 1] + ic, dp[i - 1][j -1])
else:
dp[i][j] = min(dp[i - 1][j] + dc, dp[i][j - 1] + ic, dp[i - 1][j - 1] + rc)
return dp[m][n]
题解:动态规划
当str1[i-1]和str2[j-1]不相等时,如果dp[i-1][j]已得,即str1[i-2]和str2[j-1]已经相等,可以由删除str1[i-1]得到目标;如果dp[i][j-1]已得,即str1[i-1]和str2[j-2]已经相等,可以由插入str[i]得到目标。
括号生成
给出n对括号,请编写一个函数来生成所有的由n对括号组成的合法组合。
class Solution:
def generateParenthesis(self , n ):
# write code here
res = []
def dfs(string, left, right):
if left == 0 and right == 0:
res.append(string)
if right < left:
return
if left > 0:
dfs(string + '(', left - 1, right)
if right > 0:
dfs(string + ')', left, right - 1)
left, right = n, n
dfs('', left, right)
return res
题解:回溯法
加起来和为目标值的组合
给出一组候选数 C 和一个目标数 T ,找出候选数中起来和等于 T 的所有组合。
C 中的每个数字在一个组合中只能使用一次。
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
candidates.sort()
res, path = [], []
def backstrack(begin, target):
if target == 0:
res.append(list(path))
return
for i in range(begin, len(candidates)):
if target < 0: break
if i > begin and candidates[i] == candidates[i - 1]:
continue
path.append(candidates[i])
backstrack(i + 1, target - candidates[i])
path.pop()
backstrack(0, target)
return res
题解:回溯法
关键在于两个参数的调整以及剪枝过程