1997.访问完所有房间的第一天
分析题目,可以得到两个要点:
1. 第一次到达房间i+1
需要到达房间i
两次
2. 到达当前房间i
两次需要从nextVisit[i]
再到i
一次
设f(i)
为第一次到达房间i需要的时间,则第一次到达 i+1
房间需要的时间为:
f(i + 1) = f(i) + f(i) - f(nextVisit[i])
注意: 访问房间i
的那天算1
天,访问房间i+1
算1
天
class Solution:
def firstDayBeenInAllRooms(self, nextVisit):
mod = 10**9 + 7
dp = [0] * (len(nextVisit))
#初始化原地待一天+访问下一个房间一天
dp[0] = 1
for i in range(1, len(nextVisit)):
dp[i] = (dp[i - 1] * 2 - dp[nextVisit[i-1]] + 2) % (10 ** 9 + 7)
return (dp[-1] - 1) % (10 ** 9 + 7)
2684. 矩阵中移动的最大次数
- 动态规划
本人采用了动态规划的思路,但代码存在问题,有一半的测试用例不通过,故找到类似思路且通过了的代码粘贴在下:class Solution(object): def maxMoves(self, grid): """ :type grid: List[List[int]] :rtype: int """ # 动态规划 m, n = len(grid), len(grid[0]) dp = [[0] * n for _ in range(m)] res = 0 for j in range(n - 2, -1, -1): for i in range(m): for k in i - 1, i, i+1: if 0 <= k < m and grid[k][j + 1] > grid[i][j]: dp[i][j] = max(dp[i][j], dp[k][j + 1] + 1) for i in range(m): res = max(res, dp[i][0]) return res
310.最小高度树
关键思路:设dist[x][y]
表示节点x
到节点y
的距离,假定树中距离最长的两个节点为(x, y)
, 它们之间的距离为maxdist = dist[x][y]
,则树的最小高度minheight
一定为
⌈
m
a
x
d
i
s
t
2
⌉
\lceil\frac{maxdist}{2}\rceil
⌈2maxdist⌉
-
广度优先搜索
class Solution(object): def findMinHeightTrees(self, n, edges): """ :type n: int :type edges: List[List[int]] :rtype: List[int] """ if n == 1: return [0] # 邻接矩阵 g = [[] for _ in range(n)] for x, y in edges: g[x].append(y) g[y].append(x) # 记录父节点 parents = [0] * n # 广度优先遍历 def bfs(start): vis = [False] * n # 标记是否访问过 vis[start] = True q = deque([start]) # 创建一个双端队列 并将起始元素start放入队列中 while q: x = q.popleft() # for y in g[x]: if not vis[y]: vis[y] = True parents[y] = x q.append(y) return x x = bfs(0) # 找到与节点 0 最远的节点 x y = bfs(x) # 找到与节点 x 最远的节点 y path = [] parents[x] = -1 while y != -1: path.append(y) y = parents[y] m = len(path) return [path[m//2]] if m % 2 else[path[m // 2 - 1], path[m // 2]]
-
深度优先搜索
class Solution: def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]: if n == 1: return [0] g = [[] for _ in range(n)] for x, y in edges: g[x].append(y) g[y].append(x) parents = [0] * n maxDepth, node = 0, -1 def dfs(x: int, pa: int, depth: int): nonlocal maxDepth, node if depth > maxDepth: maxDepth, node = depth, x parents[x] = pa for y in g[x]: if y != pa: dfs(y, x, depth + 1) dfs(0, -1, 1) maxDepth = 0 dfs(node, -1, 1) path = [] while node != -1: path.append(node) node = parents[node] m = len(path) return [path[m // 2]] if m % 2 else [path[m // 2 - 1], path[m // 2]]
2952.需要添加的硬币的最小数量
-
贪心算法
关键思路:
对于正整数x
,如果区间[1, x-1]
内的所有金额都可取得,且x
在数组中,则区间[1, 2x-1]
内的所有金额也都可取得。
贪心:每次找到不可取得的最小金额x
,在数组中添加x
直到找到下一个不可取得的最小整数def minimumAddedCoins(self, coins, target): """ :type coins: List[int] :type target: int :rtype: int """ coins.sort() ans, x = 0, 1 index = 0 while x<= target: if index < len(coins) and coins[index] <= x: x += coins[index] index += 1 else: ans += 1 x <<= 1 return ans
8.字符串转换整数(atoi)
-
自动机
关键思路:我们的程序在每个时刻有一个状态s
,每次从序列中输入一个字符c
,并根据字符c
转移到下一个状态s'
INT_MAX = 2 ** 31 - 1 INT_MIN = -2 ** 31 class Automaton: def __init__(self): self.state = 'start' self.sign = 1 # 不带符号默认为正整数 self.ans = 0 self.table = { 'start':['start', 'signed', 'in_number','end'], 'signed':['end', 'end', 'in_number', 'end'], 'in_number':['end', 'end', 'in_number', 'end'], 'end':['end', 'end', 'end', 'end'], } def get_col(self, c): if c.isspace(): return 0 if c == '+' or c == '-': return 1 if c.isdigit(): return 2 return 3 def get(self, c): self.state = self.table[self.state][self.get_col(c)] if self.state == 'in_number': self.ans = self.ans * 10 + int(c) self.ans = min(self.ans, INT_MAX) if self.sign == 1 else min(self.ans, -INT_MIN) elif self.state == 'signed': self.sign = 1 if c == '+' else -1 class Solution(object): def myAtoi(self, s): """ :type s: str :rtype: int """ automaton = Automaton() for c in s: automaton.get(c) return automaton.sign * automaton.ans
331.验证二叉树得前序序列化
关键思路:利用栈
def isValidSerialization(self, preorder):
"""
:type preorder: str
:rtype: bool
"""
stack = []
for node in preorder.split(','):
stack.append(node)
while len(stack) >= 3 and stack[-1] == stack[-2] == '#' and stack[-3] !='#':
stack.pop(), stack.pop(), stack.pop()
stack.append('#')
return len(stack) == 1 and stack.pop() == '#'
10.正则表达式匹配
思路:动态规划
使用dp[i][j]
表示s
的前i个字符与p
中的前j
个字符是否能够匹配。
动态规划的边界条件为f[0][0]=true
,即两个空字符串是可以匹配的
从右往左扫描,s, p
串是否匹配取决于最右端是否匹配、剩余的子串是否匹配
边界条件:
-
s[i - 1] == p[j - 1]
那么:dp[i][j] = dp[i - 1][j - 1]
-
s[i - 1] != p[j - 1]
p[i - 1] == '*' and s[i - 1] == p[j - 2]
,关于and
的第二个条件,可以展开以下讨论:p[j - 1]
的*
可以让p[j - 2]
在p
串中消失
那么dp[i][j] = dp[i][j - 2]
- 让
p[j-2]
重复1
次 (这个时候p
的最后一个字符和s
的倒数第二个字符是匹配的)
那么dp[i][j] = dp[i -1][j -2]
- 让
p[j - 2]
重复 ≥ \geq ≥2
次
那么dp[i][j] = dp[i-1][j]
-
p
为空串,s
不为空串,肯定不匹配。
s
为空串,但p
不为空串,要想匹配,只可能是右端是星号,它干掉一个字符后,把p
变为空串。
s、p
都为空串,肯定匹配。
class Solution:
def isMatch(self, s, p):
if s is None or p is None:
return False
sLen, pLen = len(s), len(p)
dp = [[False] * (pLen + 1) for _ in range(sLen + 1)]
# base case
dp[0][0] = True
for j in range(1, pLen + 1):
if p[j - 1] == "*":
dp[0][j] = dp[0][j - 2]
# 迭代
for i in range(1, sLen + 1):
for j in range(1, pLen + 1):
if s[i - 1] == p[j - 1] or p[j - 1] == ".":
dp[i][j] = dp[i - 1][j - 1]
elif p[j - 1] == "*":
if s[i - 1] == p[j - 2] or p[j - 2] == ".":
dp[i][j] = dp[i][j - 2] or dp[i - 1][j - 2] or dp[i - 1][j]
else:
dp[i][j] = dp[i][j - 2]
return dp[sLen][pLen]