70-爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
class Solution:
def climbStairs(self, n: int) -> int:
# 动态规划
if n <= 2:
return n
dp = [0]*n
dp[0] = 1
dp[1] = 2
for i in range(2, n):
dp[i] = dp[i-1] + dp[i-2]
return dp[-1]
121-买卖股票的最佳时机
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
- 方法1:贪心逐步计算出最小买入价格,以及最大卖出利润
class Solution:
def maxProfit(self, prices: List[int]) -> int:
minPrice = float('inf') # 极大值
maxProfit = 0 # 初始最大利润为0
for price in prices:
minPrice = min(minPrice, price)
maxProfit = max(maxProfit, price-minPrice)
return maxProfit
- 方法2:动态规划
(1)只有卖和不卖两种选择
选择买:min_price = min(min_price, prices[i])
选择卖:dp[i] = prices[i] - min_price
选择不卖:dp[i] = dp[i-1]
动态转移方程:dp[i] = max(dp[i-1], prices[i]-min_price)
(2)初始条件:dp[0] = 0表示
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if len(prices) == 0:
return 0
dp = [0]*len(prices)
dp[0] = 0
min_price = prices[0]
for i in range(1, len(prices)):
min_price = min(min_price, prices[i])
dp[i] = max(dp[i-1], prices[i]-min_price)
print(dp[i])
return dp[-1]
62-不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
dp = [[0 for i in range(n)] for j in range(m)]
# 第一种写法
dp[0][0] = 1
for i in range(m):
for j in range(n):
# 左边有有效值
if i-1 >= 0 and i-1 < m:
dp[i][j] = dp[i][j] + dp[i-1][j]
# 右边有有效值
if j-1 >= 0 and j-1 < n:
dp[i][j] = dp[i][j] + dp[i][j-1]
return dp[m-1][n-1]
# 第二种写法
for i in range(m):
dp[i][0] = 1
for j in range(n):
dp[0][j] = 1
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[m-1][n-1]
# 动态规划优化
dp = [0]*n
for i in range(n):
dp[i] = 1
for i in range(1, m):
dp[0] = 1
for j in range(1, n):
dp[j] = dp[j-1] + dp[j]
return dp[n-1]
# 递归写法
# 1 递归关系式:dfs(i,j) = dfs(i-1, j) + dfs(i, j-1)
# 2 找出初始值:如果i或者j有一个为0,那么不能使用关系式
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
arr = [[0 for i in range(n)] for j in range(m)]
return self.dfs(m-1, n-1, arr)
def dfs(self, i, j, arr):
if i == 0 or j == 0:
return 1
if arr[i][j] != 0:
return arr[i][j]
arr[i][j] = self.dfs(i-1, j, arr) + self.dfs(i, j-1, arr)
return arr[i][j]
64-最小路径之和
输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
m = len(grid)
n = len(grid[0])
dp = [[0 for j in range(n)] for i in range(m)]
dp[0][0] = grid[0][0]
# 初始化最左边的列
for i in range(1, m):
dp[i][0] = dp[i-1][0] + grid[i][0]
# 初始化最上面的行
for j in range(1, n):
dp[0][j] = dp[0][j-1] + grid[0][j]
# 计算剩余的值
for i in range(1,m):
for j in range(1,n):
dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
# print(dp)
return dp[m-1][n-1]
72-编辑距离
输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
# 动态规划关系式
# 插入:dp[i][j] = dp[i][j-1] + 1
# 删除:dp[i][j] = dp[i-1][j] + 1
# 替换:dp[i][j] = dp[i-1][j-1] + 1
# 关系式:dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) + 1
m = len(word1)
n = len(word2)
dp = [[0 for j in range(n+1)] for i in range(m+1)]
# dp[0][0...n] 初始化
for i in range(1,n+1):
dp[0][i] = dp[0][i-1] + 1
for j in range(1,m+1):
dp[j][0] = dp[j-1][0] + 1
for i in range(1, m+1):
for j in range(1, n+1):
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) + 1
return dp[m][n]
# 动态规划优化
m = len(word1)
n = len(word2)
dp = [0]*(n+1)
# 初始化dp[0...n]的值
for i in range(n+1):
dp[i] = i
for i in range(1, m+1):
tmp = dp[0]
dp[0] = i
for j in range(1, n+1):
pre = tmp
tmp = dp[j]
if word1[i-1] == word2[j-1]:
dp[j] = pre
else:
dp[j] = min(min(dp[j-1], pre), dp[j]) + 1
return dp[n]
53-最大子序和
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
思路(正数实现增加):
若前一个元素大于0,则将其加到当前元素上
操作 | 数 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
变换前 | -2 | 1 | -3 | 4 | -1 | 2 | 1 | -5 | 4 | |
变换后 | -2 | 1 | -2 | 4 | 3 | 5 | 6 | 1 | 5 | --> max(nums) |
状态转移方程:f(i) = max{f(i-1) + nums[i], nums[i]}
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
n = len(nums)
for i in range(1, n):
if nums[i-1] > 0:
nums[i] += nums[i-1] # 滚动数组
return max(nums)
- 1 定义数组的含义
dp[i]:以nums[i]结尾的子序的最大和
max = max(dp[0], dp[1], ... dp[n-1]) - 2 找到数组元素之间的关系式
对于每一个房间,小偷可以选择偷或者不偷
不偷: dp[i] = dp[i-1]
偷: dp[i] = dp[i-1] + nums[i]
关系式为:dp[i] = max(dp[i-1], dp[i-1]+nums[i]) - 3 数组元素的初始值
dp[0] = nums[0]
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
if nums == None or len(nums) == 0:
return 0
length = len(nums)
dp = [0]*(length)
dp[0] = nums[0]
resMax = dp[0]
for i in range(1, length):
dp[i] = max(nums[i], dp[i-1] + nums[i])
if dp[i] > resMax:
resMax = dp[i]
return resMax
198-打家劫舍
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
- 1 定义数组的含义:
dp[i]当小偷到达i号房屋时,最高可以盗窃的金额是dp[i] - 2 数组元素之间的关系:
小偷只有两种选择,偷还是不偷
不偷:dp[i] = dp[i-1]
偷: dp[i] = dp[i-2] + nums[i] # 相邻房间不可以偷窃
关系式:dp[i] = max(dp[i-1], dp[i-2]+nums[i]) - 3 找出初始值
当只有一个房间,必须偷窃:dp[0] = nums[0]
当有两个房间时,偷窃较多金额的房间:dp[1] = max(nums[0], nums[1])
class Solution:
def rob(self, nums: List[int]) -> int:
if len(nums) == 0 or nums == None:
return 0
length = len(nums)
dp = [0]*(length)
dp[0] = nums[0]
# 数组边界判定,防止越界
if len(nums) < 2:
return nums[0]
dp[1] = max(nums[0], nums[1])
for i in range(2, length):
dp[i] = max(dp[i-1], dp[i-2] + nums[i])
return dp[length-1]
322-兑换硬币
输入:coins = [1, 2, 5], amount = 11
输出:3 (最少硬币数量)
解释:11 = 5 + 5 + 1
1 定义数组的含义
dp[i]:凑成总金额n所需的最少硬币数量,dp[amount]就是我们想要的答案2 找出数组元素之间的关系
对于每一枚硬币coins[j]我们只有两种选择,选或者不选
如果选择:dp[i] = dp[i-coins[j]] + 1
如果不选择:dp[i] = dp[i]
所以公式为:dp[i] = min(dp[i], dp[i-coins[j]] + 1])3 找到初始值
当i=0时,dp[0] = 0
为了防止取最小值时dp[i]被覆盖,因此给dp[i]赋予一个尽量大的值动态规划
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
dp = [amount+1]*(amount+1)
dp[0] = 0
for i in range(1,amount+1):
for j in range(len(coins)):
# 满足兑换的条件
if coins[j] <= i:
dp[i] = min(dp[i], dp[i-coins[j]] + 1)
if dp[amount] > amount:
return -1
else:
return dp[amount]
120-三角形最小路径和
输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出:11
将上面的数组进行转化:
triangle =
[[2],
[3,4],
[6,5,7],
[4,1,8,3]]
需要注意边界条件!
class Solution:
def minimumTotal(self, triangle: List[List[int]]) -> int:
if len(triangle) == 1:
return triangle[0][0]
n = len(triangle)
dp = [[0]*n for _ in range(n)]
dp[0][0] = triangle[0][0]
for i in range(1, n):
# 最左侧的列只能由正上方元素计算
dp[i][0] = dp[i-1][0] + triangle[i][0]
for j in range(1, i):
minNum = min(dp[i-1][j-1], dp[i-1][j])
dp[i][j] = triangle[i][j] + minNum
# 最右侧的列只能由左上方元素计算
dp[i][i] = dp[i-1][i-1] + triangle[i][i]
print(dp)
return min(dp[n-1])
10-正则表达式匹配
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
- '.' 匹配任意单个字符
- '*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
输入:s = "ab" p = "."
输出:true
解释:"." 表示可匹配零个或多个('*')任意字符('.')。
public boolean isMatch1(String s, String p){
if(s == null || p == null) return false;
int len_s = s.length();
int len_p = p.length();
//存放状态,默认初始值都是 false。
boolean[][]dp = new boolean[len_s+1][len_p+1];
//初始化
dp[0][0] = true;
for(int j = 1; j <= len_p; j++){
if(p.charAt(j-1) == '*')
dp[0][j] = dp[0][j-2]; }
for(int i = 1; i <= len_s; i++){
for(int j = 1; j <= len_p; j++){
//如果不为‘*’且匹配
if(p.charAt(j-1)=='.'||p.charAt(j-1)==s.charAt(i-1))
dp[i][j] = dp[i-1][j-1];
//如果是 *
else if(p.charAt(j-1)=='*'){
//如果p[j]前面的字符p[j-1]与s[i]字符不匹配,则匹配0个
if(j!=1&&p.charAt(j-2)!='.'&&p.charAt(j-2)!=s.charAt(i-1)){
dp[i][j] = dp[i][j-2]; }
else{
//否则有三种情况: //匹配0个,匹配1个,匹配多个
dp[i][j] = dp[i][j-2] || dp[i][j-1]||dp[i-1][j];
}
}
}
}
return dp[len_s][len_p]; }
}