赞
踩
原题地址.
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]
原题地址.
利用插入排序的思想,从原数组的尾元素开始往前遍历,插入并维护一个新的有序数组,插入位置就是该元素右侧小于它的元素个数。
class Solution:
def countSmaller(self, nums: List[int]) -> List[int]:
sortns = []
res = []
for n in reversed(nums):
idx = bisect.bisect_left(sortns, n)
res.append(idx)
sortns.insert(idx,n)
return res[::-1]
原题地址.
一个经典的dp问题,定义dp[i][j]
为从i行j列到最后右下角所需要的最少生命值。就可以写出对应的转移方程
class Solution: def calculateMinimumHP(self, dungeon: List[List[int]]) -> int: #初始化 n, m = len(dungeon), len(dungeon[0]) dp = [[float('inf')] * (m + 1) for _ in range(n + 1)] #边界条件 dp[n][m - 1] = dp[n - 1][m] = 1 for i in range(n - 1, -1, -1): for j in range(m - 1, -1, -1): #过来所需要的初始值 minn = min(dp[i + 1][j], dp[i][j + 1]) #初始值必须大于1,dungeon可能加血 dp[i][j] = max(minn - dungeon[i][j], 1) return dp[0][0]
原题地址.
终于又来简单题了。利用hash表完成常数实际访问,具体为以短数组构建hash表,遍历长数组判断是否在其中。
class Solution: def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]: if len(nums1) > len(nums2): #hash表用小数组更好 return self.intersect(nums2, nums1) m = collections.Counter(nums1) intersection = list() #看另外一个数组元素是否在hash表 for num in nums2: if num in m.keys(): intersection.append(num) m[num] -= 1 if m[num] == 0: m.pop(num) return intersection
原题地址.
本质上是一个经典的dp问题,而且每一行的路径答案只和上一行相关,所以可以进行状态压缩。
class Solution: def minimumTotal(self, triangle: List[List[int]]) -> int: n = len(triangle) #dp[?][i] 根据?是prev或者curr储存当前行或者上一行i位置的路径最小和 dp = [[0] * n for _ in range(2)] #初始值 dp[0][0] = triangle[0][0] for i in range(1,n): #判断当前使用的是那一行 curr, prev = i % 2, 1 - i % 2 #边界值 dp[curr][0] = dp[prev][0] + triangle[i][0] dp[curr][i] = dp[prev][i-1] + triangle[i][i] #状态转移 for j in range(1,i): dp[curr][j] = min(dp[prev][j-1],dp[prev][j]) + triangle[i][j] return min(dp[1 - n%2])
原题地址.
运用动态规划或者说是带记忆的递归解决。
class Solution:
def numTrees(self, n: int) -> int:
G = [0]*(n+1)
G[0], G[1] = 1, 1
for i in range(2,n + 1):
for j in range(0,i):
#左右子树节点数为j和i-1-j
G[i]+= G[j]*G[i-1-j]
return G[-1]
如果有足够的数学知识,会知道这是卡特兰数,可以直接用递推公式求解。
class Solution(object):
def numTrees(self, n):
C = 1
for i in range(0, n):
C = C * 2*(2*i+1)/(i+2)
return int(C)
原题地址
在遍历每一个边时,判断端点颜色是否符合规矩以及对未染色的节点染色。
class Solution: def isBipartite(self, graph: List[List[int]]) -> bool: n = len(graph) UNCOLORED, RED, GREEN = 0, 1, 2 #存储颜色 color = [UNCOLORED] * n valid = True def dfs(node,c): nonlocal valid color[node] = c #另外一个节点应该的颜色 cNei = (GREEN if c == RED else RED) for neighbor in graph[node]: #未染色的,染上这个颜色 if color[neighbor] == UNCOLORED: dfs(neighbor, cNei) if not valid: return #颜色发生冲突 elif color[neighbor] != cNei: valid = False return for i in range(n): #遍历所有节点,因为可能有多个子图 if color[i] == UNCOLORED: dfs(i, RED) if not valid: break return valid
原题地址
没啥好说的,二分搜索注意一下边界条件。
class Solution: def searchInsert(self, nums: List[int], target: int) -> int: l = 0 r = len(nums) - 1 #<=的写法因为对终止循环前的元素判断了对于target的大小,l就是该插入的地方 while l <= r: mid = (l+r)//2 if nums[mid] < target: l = mid + 1 elif nums[mid] > target: r = mid - 1 else: #提前返回 return mid else: return l
原题地址
利用动态规划解决,类别编辑距离处理。又可以用滚动数组优化二维dp数据。
class Solution: def isInterleave(self, s1: str, s2: str, s3: str) -> bool: m = len(s1) n = len(s2) t = len(s3) if m+n != t: return False #dp数组初始化 dp = [False]*(n+1) dp[0] = True #原来二维i每一行之和上一行有关 for i in range(m+1): for j in range(n+1): p = i + j - 1 if i > 0: dp[j] = dp[j] and s3[p] == s1[i-1] if j > 0: dp[j] = dp[j] or (dp[j-1] and s3[p] == s2[j-1]) return dp[-1]
原题地址
自底向上得动态规划。dp[i][j]
记录以ij为左右边界得最大值。
class Solution: def maxCoins(self, nums: List[int]) -> int: n = len(nums) rec = [[0] * (n + 2) for _ in range(n + 2)] val = [1] + nums + [1] for i in range(n - 1, -1, -1): #右边界 for j in range(i + 2, n + 2): #左边界 for k in range(i + 1, j): #中间元素 total = val[i] * val[k] * val[j] total += rec[i][k] + rec[k][j] rec[i][j] = max(rec[i][j], total) return rec[0][n+1]
原题地址
使用双指针从两端向中间搜索。
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
i = 0
j = len(numbers) - 1
while numbers[i] + numbers[j] != target:
if numbers[i] + numbers[j] > target:
j -= 1
else:
i += 1
return [i+1,j+1]
原题地址
递归成生成左右子树的小问题解决。
class Solution: def generateTrees(self, n: int) -> List[TreeNode]: def generateTrees(start, end): if start > end: return [None,] res = [] for i in range(start,end + 1): #递归 leftTrees = generateTrees(start, i - 1) rightTrees = generateTrees(i + 1, end) #拼接 for l in leftTrees: for r in rightTrees: currTree = TreeNode(i) currTree.left = l currTree.right = r res.append(currTree) return res return generateTrees(1, n) if n else []
原题地址
其实就是寻找旋转数组旋转点,利用二分法可以方便的找到。
class Solution:
def minArray(self, numbers: List[int]) -> int:
l,r = 0,len(numbers)-1
while l < r:
mid = (l+r)//2
if numbers[mid] > numbers[r]:
l = mid + 1
elif numbers[mid] < numbers[r]:
r = mid
else:
r -= 1
return numbers[l]
原题地址
常规的动态规划问题,利用滚动数组优化。
class Solution: def minPathSum(self, grid: List[List[int]]) -> int: m = len(grid) n = len(grid[0]) dp = [0] * n #一行行求解 for i in range(m): for j in range(n): if i == 0 and j != 0: dp[j] = dp[j-1] + grid[i][j] elif j ==0 and i !=0: dp[j] = dp[j] + grid[i][j] elif i ==0 and j == 0: dp[j] = grid[i][j] else: dp[j] = min(dp[j],dp[j-1]) + grid[i][j] return dp[-1]
原题地址
需要思考一个数的转化过程,拿到一个奇数运算后肯定变成一个偶数,拿到一个偶数之后肯定变成一个奇数;而最后拿到2这个偶数的获胜,拿到1这个奇数的就输了。所以先手偶数的就会获胜。
class Solution:
def divisorGame(self, N: int) -> bool:
return N%2==0
原题地址
这种需要多次求解子问题的问题一般都用动态规划思想,设数组dp[i][j]
为前i个元素分为j段的结果,那么状态转移方程就是在i个元素中选择第k个划分为j-1段以及后面一段求和。
class Solution: def splitArray(self, nums: List[int], m: int) -> int: n = len(nums) #dp初始化 dp = [[float('inf')] * (m + 1) for _ in range(n + 1)] dp[0][0] = 0 #sub是序列和,用来快速求出段落之和 sub = [0] for elem in nums: sub.append(sub[-1] + elem) for i in range(1,n+1): for j in range(1,min(i,m) + 1): for k in range(i): #k分为2段选择其中大的。 dp[i][j] = min(dp[i][j],max(dp[k][j-1],sub[i]-sub[k])) return dp[n][m]
原题地址
经典的dfs搜索可以解决,但是会超时,所以加入记忆化搜索。
class Solution: def longestIncreasingPath(self, matrix: List[List[int]]) -> int: direcs = [(-1, 0), (1, 0), (0, -1), (0, 1)] if not matrix: return 0 #记忆化数组 meme = {} def dfs(row: int, column: int) -> int: best = 1 for dx, dy in direcs: newRow, newColumn = row + dx, column + dy if 0 <= newRow < rows and 0 <= newColumn < columns and matrix[newRow][newColumn] > matrix[row][column]: #直接提取 if (newRow,newColumn) in meme: best = max(best, meme[(newRow,newColumn)] + 1) else: #计算,仅第一次 best = max(best, dfs(newRow,newColumn) + 1) meme[(row,column)] = best return best ans = 0 rows, columns = len(matrix), len(matrix[0]) for i in range(rows): for j in range(columns): ans = max(ans, dfs(i, j))
原题地址
因为在两个字符串中都要遍历,考虑使用双指针法遍历。
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
n, m = len(s), len(t)
#双指针分别在s,t中遍历
i = j = 0
while i < n and j < m:
#找到一个,s指针后移
if s[i] == t[j]:
i += 1
#不管有没有,t指针都要后移
j += 1
return i == n
如果需要处理很多个s判断子序列的话,可以用动态规划,先把t的信息储存。f[i][j]表示位置i之后第一个出现字母j的坐标
class Solution: def isSubsequence(self, s: str, t: str) -> bool: n, m = len(s), len(t) #dp数组,第二维代表26个字母 f = [[0] * 26 for _ in range(m)] f.append([m] * 26) #填充数组 for i in range(m - 1, -1, -1): for j in range(26): f[i][j] = i if ord(t[i]) == j + ord('a') else f[i + 1][j] add = 0 for i in range(n): #无效位置 if f[add][ord(s[i]) - ord('a')] == m: return False add = f[add][ord(s[i]) - ord('a')] + 1 return True
原题地址
利用栈遍历,出栈时比较最大深度
class Solution:
def maxDepth(self, root: TreeNode) -> int:
stack = []
if root is not None:
stack.append((1, root))
res = 0
while stack:
current_depth, root = stack.pop()
if root:
res = max(res, current_depth)
stack.append((current_depth+1,root.left))
stack.append((current_depth+1,root.right))
return res
原题地址
这题建议直接看题解.。。。
这道困难题让我的青春结束了。
原题地址
采用动态规划思想,数字i拆分成数字j和其余部分处理。
class Solution:
def integerBreak(self, n: int) -> int:
dp = [0] * (n + 1)
for i in range(2, n + 1):
for j in range(i):
#子问题,j这个数字可以选择拆分或者不拆分。
dp[i] = max(dp[i],j * (i - j),dp[j]*(i-j))
return dp[n]
原题地址
暴力遍历也可以,但是既然是有序的,就要利用好这个性质,省略一下对比操作。
class Solution:
def findMagicIndex(self, nums: List[int]) -> int:
n = len(nums)
i = 0
while i < n:
if nums[i] == i:
return i
if nums[i] > i: # 此时我们可以排除索引i到nums[i-1]这一整段
i = nums[i] # 由于数组可以保持平稳,所以nums[i]这一元素不可排除
else:
i += 1
return -1
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。