https://leetcode-cn.com/tag/dynamic-programming/problemset/
文章目录
322. 零钱兑换(中等)
https://leetcode-cn.com/problems/coin-change/
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
#让dp状态数组的状态数为amount+1,且初始值也为amount + 1
dp = [amount + 1 for i in range(amount + 1)]
#base 状态
dp[0] = 0
for i in range(len(dp)):
for coin in coins:
#子问题无解,跳过
if i - coin < 0:
continue
dp[i] = min(dp[i],dp[i-coin] + 1)
return dp[amount] if dp[amount] != amount + 1 else - 1
300. 最长递增子序列(中等)
https://leetcode-cn.com/problems/longest-increasing-subsequence/
注意子序列和子串的区别,后者必须连续
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
#初始化dp数组
dp = [1 for i in range(len(nums))]
for i in range(len(nums)):
#寻找最长
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i],dp[j]+1)
return max(dp)
5. 最长回文子串(中等)
https://leetcode-cn.com/problems/longest-palindromic-substring/
class Solution:
def longestPalindrome(self, s: str) -> str:
n = len(s)
if n < 2:
return s
dp = [[False]*n for _ in range(n)]
for i in range(n): #单个字符肯定是回文字符串
dp[i][i] = True
start = 0
maxL = 1
for L in range(2, n + 1): #注意这儿是n+1
for i in range(n): #左边下标
j = i + L - 1
if j >= n:
break
if s[i] != s[j]:
dp[i][j] = False
else:
if j - i < 3:
dp[i][j] = True
else:
dp[i][j] = dp[i+1][j-1]
if dp[i][j] and L > maxL:
start = i
maxL = L
return s[start:start+maxL]
53. 最大子序和(简单)
#第一次写,可能借助了dp数组
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
n = len(nums)
if n == 0:
return
dp = [float('-inf') for i in range(n)]
dp[0] = nums[0]
maxSubSeqSum = dp[0]
for i in range(1,n):
if dp[i-1] < 0:
dp[i] = nums[i]
else:
dp[i] = dp[i-1] + nums[i]
if dp[i] > maxSubSeqSum:
maxSubSeqSum = dp[i]
return maxSubSeqSum
#第二次写将上面进行优化,因为,其实只用到了前一个状态pre
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
pre = 0
maxSubSeqSum = nums[0]
for i in range(len(nums)):
pre = max(nums[i],pre + nums[i])
maxSubSeqSum = max(pre,maxSubSeqSum)
return maxSubSeqSum
70. 爬楼梯(简单)
class Solution:
def climbStairs(self, n: int) -> int:
if n <= 2:
return n
dp = [0 for i in range(n+1)]
dp[0] = 0
dp[1] = 1
dp[2] = 2
for i in range(3,n + 1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
#去掉dp数组
class Solution:
def climbStairs(self, n: int) -> int:
p1 = 0
p2 = 1
for i in range(n):
p = p1 + p2
p1 = p2
p2 = p
return p
剑指 Offer 13. 机器人的运动范围(中等)
https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
#数字各个位置求和
def sumNum(number):
s = 0
while number:
d,r = divmod(number,10)
number = d
s += r
return s
#判断该位置是否valid
def flag(i, j, m, n, k):
if i < 0 or j < 0 or i >= m or j >= n or sumNum(i) + sumNum(j) > k:
return False
else:
return True
dp = [[False for _ in range(n)] for _ in range(m)]
global validGrid
validGrid = 0
def dfs(x, y, m, n, k, dp):
global validGrid
offset = [[0,-1],[0,1],[-1,0],[1,0]] #左,右,上,下
if not flag(x, y, m, n, k) or dp[x][y]:
return
else:
dp[x][y] = True
validGrid += 1
for i in range(4):
dfs(x + offset[i][0], y + offset[i][1], m, n, k, dp)
dfs(0, 0, m, n, k, dp)
return validGrid
152. 乘积最大子数组(中等)
https://leetcode-cn.com/problems/maximum-product-subarray/
class Solution:
def maxProduct(self, nums) -> int:
preMax = 1
preMin = 1
maxP = float("-inf")
for i in range(len(nums)):
t1 = max(max(preMax * nums[i], nums[i]),nums[i] * preMin)
t2 = min(min(preMin * nums[i], nums[i]),nums[i] * preMax)
preMax = t1
preMin = t2
#print(preMax,preMin)
maxP = max(preMax,maxP)
return maxP
但第一次写的时候,写的代码很丑陋(分类讨论了多种情况,把头都绕晕了,其实可以抽象出来)
class Solution:
def maxProduct(self, nums) -> int:
preMax = 1
preMin = 1
maxP = nums[0]
minP = nums[0]
t1 = 1
t2 = 1
for i in range(len(nums)):
if nums[i] > 0:
if preMax > 0:
t1 = preMax * nums[i]
t2 = min(nums[i], preMin * nums[i])
elif preMax < 0:
t1 = nums[i]
t2 = preMin * nums[i]
else:
t1 = nums[i]
t2 = preMin * nums[i]
elif nums[i] < 0:
if preMax > 0:
t1 = max(nums[i], nums[i] * preMin)
t2 = min(nums[i], nums[i] * preMax)
elif preMax < 0:
t1 = preMin * nums[i]
t2 = nums[i]
else:
t1 = max(0,nums[i]*preMin)
t2 = nums[i]
else:
t1 = 0
t2 = 0
preMax = t1
preMin = t2
if preMax > maxP:
maxP = preMax
return maxP
55. 跳跃游戏(中等)
https://leetcode-cn.com/problems/jump-game/
class Solution:
def canJump(self, nums) -> bool:
maxForward = 0
for i in range(len(nums)):
if i + nums[i] > maxForward:
maxForward = i + nums[i]
if maxForward <= i:
break
return i + 1 >= len(nums)
45. 跳跃游戏 II(中等)
https://leetcode-cn.com/problems/jump-game-ii/
class Solution:
def jump(self, nums) -> int:
dp = [float("inf") for i in range(len(nums))]
dp[0] = 0
for i in range(1,len(nums)):
minValue = dp[i]
for j in range(i):
if j + nums[j] >= i:
minValue = min(minValue, dp[j] + 1)
dp[i] = minValue
return dp[len(nums) - 1]
62. 不同路径(中等)
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
dp = [[0 for i in range(n + 1)] for j in range(m + 1)]
dp[1][1] = 1
for i in range(1,m + 1):
for j in range(1, n + 1):
if [i,j] == [1,1]:
continue
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
return dp[m][n]
时间复杂度和空间复杂度都是o(m*n)
因此可以用组合的方法,直接计算答案,因为从左上到右下,一共要走m+n-2步,因为只能向右以及向下,所以向右和向下分别走了n-1步和m-1步,因此直接从m+n-2里面挑选出n-1即可
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
return math.comb(m + n - 2, n - 1)
这样时间复杂度为o(1)
63. 不同路径 II(中等)
https://leetcode-cn.com/problems/unique-paths-ii/
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid) -> int:
m = len(obstacleGrid)
n = len(obstacleGrid[0])
dp = [[0 for i in range(n + 1)] for j in range(m + 1)]
if obstacleGrid[0][0] == 1:
return 0
dp[1][1] = 1
for i in range(1,m + 1):
for j in range(1, n + 1):
if [i,j] == [1,1]:
continue
if obstacleGrid[i - 1][j - 1] != 1:
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
return dp[m][n]
64. 最小路径和(中等)
https://leetcode-cn.com/problems/minimum-path-sum/
class Solution:
def minPathSum(self, grid) -> int:
m = len(grid)
n = len(grid[0])
dp = [[0 for i in range(n)] for j in range(m)]
dp[0][0] = grid[0][0]
for i in range(1, m):
dp[i][0] = grid[i][0] + dp[i - 1][0]
for j in range(1, n):
dp[0][j] = grid[0][j] + dp[0][j - 1]
for i in range(1, m):
for j in range(1, n):
dp[i][j] = min(dp[i - 1][j],dp[i][j - 1]) + grid[i][j]
return dp[m - 1][n - 1]
22. 括号生成(中等)
https://leetcode-cn.com/problems/generate-parentheses/
class Solution:
def generateParenthesis(self, n: int):
s = ""
pre = [s]
for i in range(n):
t = set()
for valid in pre:
for j in range(len(valid) + 1):
t.add(valid[0:j] + "()" + valid[j:])
pre = list(t)
return pre
核心思路是上一次的结果,在其字符串中各个插入"()",靠集合过滤掉重复的valid。
96. 不同的二叉搜索树(中等)
https://leetcode-cn.com/problems/unique-binary-search-trees/
class Solution:
def numTrees(self, n: int) -> int:
dp = [0 for i in range(n + 1)]
dp[0] = 1
for i in range(1,n + 1):
s = 0
for j in range(0, i):
s += dp[j] * dp[i - j - 1]
dp[i] = s
return dp[n]
关键是理解二叉搜索树,求和分别以i做为根结点,左右子树的可能性的乘积
120. 三角形最小路径和(中等)
https://leetcode-cn.com/problems/triangle/
class Solution:
def minimumTotal(self, triangle) -> int:
n = len(triangle)
dp = [[float("inf") for i in range(n + 1)] for j in range(n + 1)]
dp[0][0] = 0
for i in range(1,n + 1):
for j in range(1, i + 1):
dp[i][j] = min(dp[i - 1][j], dp[i - 1][j - 1]) + triangle[i - 1][j - 1]
minPathSum = float("inf")
for j in range(n + 1):
minPathSum = min(minPathSum,dp[n][j])
return minPathSum
上面的时间复杂度和空间复杂度都是o(n*n),但是可以根据状态转移方程可以知道的是,下一层的状态只与上一层的状态有关,而不与上面其他层的状态相关,因此可以将空间复杂度控制在o(n)。
import copy
class Solution:
def minimumTotal(self, triangle) -> int:
n = len(triangle)
dp1 = [float("inf") for i in range(n)]
dp1[0] = 0
dp2 = [float("inf") for i in range(n)]
for i in range(n):
for j in range(i + 1):
if j == 0:
dp2[j] = dp1[j] + triangle[i][j]
else:
dp2[j] = min(dp1[j],dp1[j - 1]) + triangle[i][j]
dp1 = copy.copy(dp2) #注意
minPathSum = min(dp1)
return minPathSum
上面其实实际用到的空间是2n,按理可以继续优化到n
198. 打家劫舍(中等)
https://leetcode-cn.com/problems/house-robber/
class Solution:
def rob(self, nums) -> int:
n = len(nums)
if n == 1:
return nums[0]
dp = [0 for i in range(n + 1)]
dp[1] = nums[0]
dp[2] = nums[1]
for i in range(3,n + 1):
dp[i] = max(dp[i - 2], dp[i - 3]) + nums[i - 1]
return max(dp[n],dp[n - 1])
时间复杂度和空间复杂度都是o(N),下面把空间复杂度优化到常数项
class Solution:
def rob(self, nums) -> int:
n = len(nums)
f1 = 0
f2 = 0
f3 = 0
maxSum = 0
for i in range(n + 2):
if i >= n:
money = 0
else:
money = nums[i]
maxSum = max(money + max(f2, f3), maxSum)
f3 = f2
f2 = f1
f1 = maxSum
return maxSum
221. 最大正方形(中等)
https://leetcode-cn.com/problems/maximal-square/
class Solution:
def maximalSquare(self, matrix) -> int:
m = len(matrix)
n = len(matrix[0])
dp = [[0 for i in range(n + 1)] for j in range(m + 1)]
res = 0
for i in range(1,m + 1):
for j in range(1, n + 1):
s = dp[i - 1][j - 1] #左上角正方形边长
flag = True
for t in range(1, s + 1):
if dp[i][j - t] < 1 or dp[i - t][j] < 1:
flag = False
break
if matrix[i - 1][j - 1] == "1": #当前位置是否为1
if flag:
dp[i][j] = s + 1
else:
dp[i][j] = t #注意
else:
dp[i][j] = 0
res = max(res, dp[i][j])
return res * res
上面其实都了一点弯路,可以直接删繁化简为下面的状态转移方程
class Solution:
def maximalSquare(self, matrix) -> int:
m = len(matrix)
n = len(matrix[0])
dp = [[0 for i in range(n + 1)] for j in range(m + 1)]
res = 0
for i in range(1,m + 1):
for j in range(1, n + 1):
if matrix[i - 1][j - 1] == "1": #当前位置是否为1
dp[i][j] = min([dp[i - 1][j - 1], dp[i][j -1], dp[i - 1][j]]) + 1
res = max(res, dp[i][j])
return res * res
279. 完全平方数(中等)
https://leetcode-cn.com/problems/perfect-squares/
import math
class Solution:
def numSquares(self, n: int) -> int:
dp = [float("inf") for i in range(n + 1)]
dp[0] = 0
for i in range(1, n + 1):
minV = float("inf")
for j in range(1, math.floor(math.sqrt(i)) + 1):
minV = min(minV, dp[i - j * j] + 1)
dp[i] = minV
return dp[n]
算法的时间复杂度为 o ( N ∗ n ) o(N*\sqrt{n}) o(N∗n),空间复杂度为o (N)。
121. 买卖股票的最佳时机(简单)
https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
minValue = float(inf)
maxProfit = float(-inf)
for i in range(n):
if prices[i] - minValue > maxProfit:
maxProfit = prices[i] - minValue
if prices[i] < minValue:
minValue = prices[i]
return maxProfit if maxProfit > 0 else 0
338. 比特位计数(简单)
https://leetcode-cn.com/problems/counting-bits/
class Solution:
def countBits(self, n: int) -> List[int]:
dp = [0 for i in range(n + 1)]
dp[0] = 0
if n == 0:
return dp
preN = 1
for i in range(1, n + 1):
dp[i] = 1 + dp[i - preN] if i / preN != 2 else 1
if i / preN == 2:
preN = i
return dp
#没有用位运算的dp,位运算的解法详见:https://leetcode-cn.com/problems/counting-bits/solution/bi-te-wei-ji-shu-by-leetcode-solution-0t1i/