文章目录
300. 最长递增子序列
知道用动态规划,不知道怎么写,看了K神答案:
下面是根据思路,自己写的:
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
n = len(nums)
dp = [1] * n
for i in range(1, n):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
还有一种方法,先跳过,二刷过来看。
301. 删除无效的括号
太难了,回头再做,先跳了
309. 最佳买卖股票时机含冷冻期
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if not prices:
return 0
n = len(prices)
# f[i][0]: 手上持有股票的最大收益
# f[i][1]: 手上不持有股票,并且处于冷冻期中的累计最大收益
# f[i][2]: 手上不持有股票,并且不在冷冻期中的累计最大收益
f = [[-prices[0], 0, 0]] + [[0] * 3 for _ in range(n - 1)]
for i in range(1, n):
f[i][0] = max(f[i - 1][0], f[i - 1][2] - prices[i])
f[i][1] = f[i - 1][0] + prices[i]
f[i][2] = max(f[i - 1][1], f[i - 1][2])
return max(f[n - 1][1], f[n - 1][2])
下面是我的:
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if not prices or len(prices) == 0:
return 0
n = len(prices)
dp = [[-prices[0], 0, 0]] + [[0] * 3 for _ in range(n - 1)]
for i in range(1, n):
dp[i][0] = max(dp[i - 1][0], dp[i - 1][2] - prices[i])
dp[i][1] = dp[i - 1][0] + prices[i]
dp[i][2] = max(dp[i - 1][1], dp[i - 1][2])
return max(dp[n -1][1], dp[n - 1][2])
312. 戳气球
消去型不用顺着题目去想,还是回到最后一步。只剩一个气球。那么它的子问题就是左边的区间,和右边的区间。并且这两个区间是互不影响的。
动态规划4步法:
1、确定状态
最后一步
子问题
2、转移方程
3、初始条件和边界情况
4、计算顺序
下面是我照着写的代码,这个题太难了,不会。
class Solution:
def maxCoins(self, nums: List[int]) -> int:
if not nums or len(nums) == 0:
return 0
A = [1] + nums + [1]
n = len(nums)
dp = [[0] * (n + 2) for _ in range(n + 2)]
for i in range(n + 1):
dp[i][i + 1] = 0
for length in range(3, n + 3):
for i in range(n + 2):
j = i + length - 1
if j > n + 1:
break
for k in range(i + 1, j):
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + A[i] * A[k] * A[j])
return dp[0][n + 1]
322. 零钱兑换
给出不同面额的硬币以及一个总金额. 写一个方法来计算给出的总金额可以换取的最少的硬币数量. 如果已有硬币的任意组合均无法与总金额面额相等, 那么返回 -1.
直觉
所以这种题,不论我们自己怎么做设计,都会找到返例。我们该如何用动态规划的方法解决这道题呢?
动态规划4步法:
1、确定状态
是动态规划中的定海神针,类似于数学中的函数。
我们一般都需要开一个f[i] 或者 f[i][j] 的数组
确定状态需要2步:最后一步 and 子问题
最后一步
如上图所示,如果ak是最优的策略,俺么27-ak也一定是最优的策略。也就是27-ak也是最少能拼出的硬币数。
如果可以将原问题转化成一个子问题,并且规模变小了,我们就可以设置f(x)了。这里f(X)=最少用多少枚硬币拼出X。f(27)=5,最少用5枚硬币,拼出27. 所以原问题就成了求f(27)。 子问题是求f(27-ax)。
子问题
下面是递归的解法补充:
为什么要说递归的解法,在《算法通关40讲》中也提到过,DP就是递推,递推就是递归➕记忆化搜索。侯老师这里讲的其实也是这样,为什么我们要想最后一步,就是通过最后一步,知道递归的条件。
递归算法的问题,f(20)算了很多次,递归的问题就是重复计算非常多,导致算法的运行效率非常低下。动态规划就是可以将递归的冗余计算去掉,秘诀就是将计算结果保存下来,并且改变了计算顺序。
2、转移方程
刚才我们用的是()。但是在转移方程中,我们是用的[] 。表示一个数组,不是一个函数名了,是一个数组。
有了转移方程,我们就可以写动态规划了(其实只是解决了一大半问题)。但是我们经常忽略的问题就是初始条件和边界情况。
3、初始条件和边界情况
有了初始条件和边界情况,我们就要考虑计算顺序了,要先算哪个下标,再算哪个下标呢?
4、计算顺序
一般动态规划中,我们都是从小往大算。因为当我们算f[x]的时候,f[x-2], f[x-5], f[x-7]在前面已经算过了。而且不需要重复计算。
这里如果是贪心算法,可能第一步就是拿7块钱,但是可能第一步就错了。这里就体现了为什么叫“动态规划”,就是在不断的从无到有,并且是在不断的规划调整的。
计算f[27],不是靠感觉,而是因为算出了f[0] —> f[26]后,自然而然的从f[25], f[22], f[20]里选择最小的(最优的)。
小结:
最值型动态规划
如何写f[x],就是在化成子问题的时候,将“最少的硬币拼出更小的面值”这句话翻译出来,f[x]表示最少用多少枚硬币拼出面值x。
动态规划的优点:消除冗余,加速计算。
看着老师的代码写的:
import sys
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
if not coins or len(coins) == 0 or amount < 0:
return -1
n = len(coins)
dp = [sys.maxsize] * (amount + 1)
dp[0] = 0
for i in range(amount + 1):
for j in range(n):
last_index = i - coins[j]
if last_index < 0 or dp[last_index] == sys.maxsize:
continue
dp[i] = min(dp[i], dp[last_index] + 1)
if dp[amount] == sys.maxsize: return -1
return dp[amount]