原题地址.
A[i:]和B[j:]的最长重复长度为A[i+1:]和B[j+1:]最长长度加1(如果A[i]==B[j]),所以可以用动态规划思想解决。
class Solution:
def findLength(self, A: List[int], B: List[int]) -> int:
n = len(A)
m = len(B)
dp = [[0] * (m+1) for _ in range(n+1)]
res = 0
#最小的字串开始,往更长的字串去计算dp[i][j]表示A[i:]与B[j:]的最大长度
for i in range(n-1,-1,-1):
for j in range(m-1,-1,-1):
dp[i][j] = dp[i+1][j+1] + 1 if A[i] == B[j] else 0
res = max(res,dp[i][j])
return res
原题地址.
首先利用矩阵的性质,可以写出一个便捷的函数判断矩阵中有多少元素小于某一个值。
def check(mid):
i,j = n-1,0
#小于mid的元素个数
num = 0
while i >= 0 and j < n:
if matrix[i][j] <= mid:
#一升序,所以上面全部小于mid
num += i+1
j += 1
else:
i -= 1
return num >= k
进而用二分搜索就可以找找到满足条件的值
left, right = matrix[0][0], matrix[-1][-1]
while left < right:
mid = (left + right) // 2
if check(mid):
right = mid
else:
left = mid + 1
return left
原题地址.
转换主要考虑选取根节点的问题,方式不止一种,但是最容易方便的就是二分法,每次选取中间的点当根,这样左右子树就会平衡
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
def helper(l,r):
if l > r:
return
mid = l + (r-l)//2
root = TreeNode(nums[mid])
#递归构建左右子树
root.left = helper(l,mid-1)
root.right = helper(mid+1,r)
return root
return helper(0,len(nums)-1)
原题地址.
遇到括号匹配问题,首先想到利用栈的一边扫描,一边出栈,一边判断长度。
class Solution:
def longestValidParentheses(self, s: str) -> int:
out = 0
#方便计数初始值
stack = [-1]
for i in range(len(s)):
if s[i] == '(':
stack.append(i)
else:
stack.pop()
if stack:
#比较当前长度
out = max(out,i - stack[-1])
else:
#出现了多余的右括号,所以起始计数位置要重置
stack.append(i)
return out
原题地址.
匹配问题可以采用动态规划思想。
用dp[i][j] 标记s[:i]与p[:j]是否匹配,那么可写出转移方程。
如果p[j-1]==s[i-1] or '?'
,就和前缀字串相同了,如果p[j-1] == ‘*’
则分不进行匹配dp[i][j] = dp[i][j-1]
和匹配当前dp[i][j] = dp[i-1][j]
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 i in range(1, n + 1):
#连续为‘*’号才True
if p[i - 1] == '*':
dp[0][i] = True
else:
break
for i in range(1, m + 1):
for j in range(1, n + 1):
#通匹符号可以匹配任意多个或者不匹配
if p[j-1] == '*':
#不用匹配就是不用‘*’出马,j往前看一个就行;匹配的话‘*’肯定那把s[i]顶掉,所以i往前看就行。
dp[i][j] = dp[i][j-1] or dp[i-1][j]
#如果接下来一个字符可以对上,就和前缀字串一致
elif p[j-1] =='?' or p[j-1] == s[i-1]:
dp[i][j] = dp[i-1][j-1]
return dp[-1][-1]
原题地址.
还是dp思想,但是判断条件更多,边界条件也更麻烦。
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
m = len(obstacleGrid)
n = len(obstacleGrid[0])
#一开始就堵了
if obstacleGrid[0][0] == 1:
return 0
dp = [0] * n
#第一行一直往右走,一旦堵了就全堵了
for i in range(n):
if obstacleGrid[0][i] == 0:
dp[i] = 1
else:
break
for i in range(1,m):
#第一列只能往下走,要看上一行第一个走不走得通
dp[0] = dp[0] if obstacleGrid[i][0] == 0 else 0
for j in range(1,n):
#本身堵了就是0,没有就看上和左的路径了
dp[j] = dp[j-1] + dp[j] if obstacleGrid[i][j] == 0 else 0
return dp[-1]
原题地址.
一边递归一边更新总和值即可。
class Solution:
def hasPathSum(self, root: TreeNode, sum: int) -> bool:
if not root:
return False
#满足条件
if not root.left and not root.right:
return sum == root.val
return self.hasPathSum(root.left, sum - root.val) or self.hasPathSum(root.right, sum - root.val)
原题地址.
利用数量总和一定解答
class Solution:
def divingBoard(self, shorter: int, longer: int, k: int) -> List[int]:
if k == 0:
return []
if shorter == longer:
#只有一种情况,其实不要也能通过,但是可以剪枝加速
return [k * shorter]
else:
#数量和是一定的
return [(k - i) * shorter + i * longer for i in range(k + 1)]
原题地址.
暴力也可以通过,但是为了优化,需要用到前缀树和dp。
class TreeNode:
def __init__(self):
#定义前缀树,is_word是单词结尾标志
self.child = {}
self.is_word = False
class Solution:
def make_tree(self, dictionary):
#递归构建前缀树
for word in dictionary:
node = self.root
for s in word:
if not s in node.child:
node.child[s] = TreeNode()
node = node.child[s]
node.is_word = True
def respace(self, dictionary: List[str], sentence: str) -> int:
self.root = TreeNode()
self.make_tree(dictionary)
n = len(sentence)
#dp数组,dp[i]是sentence[i:]的未识别字符数
dp = [0] * (n + 1)
for i in range(n-1, -1, -1):
dp[i] = n - i
node = self.root
#判断sentence[i:]里的字串是否在字典
for j in range(i, n):
c = sentence[j]
#转移方程
if c not in node.child:
#认命,sentence[i:j]也是未识别字符
dp[i] = min(dp[i], dp[j+1]+j-i+1)
break
if node.child[c].is_word:
#是一个单词,可以不考虑sentence[i:j]
dp[i] = min(dp[i], dp[j+1])
else:
#认命,sentence[i:j]也是未识别字符
dp[i] = min(dp[i], dp[j+1]+j-i+1)
node = node.child[c]
return dp[0]
原题地址.
分持有和未持有两种情况使用动态规划思想。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n= len(prices)
if not n:
return 0
#该天不持有和持有股票的收益
dp = [[0,0]for i in range(n)]
#第一天购入
dp[0][1] = -prices[0]
for i in range(1,n):
#当天不持有,卖出去了或者一直没有
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]