【自顶向下】从一个规模较大的问题 f(20),向下逐渐分解规模,直到 f(1)、f(2)触底,再逐层返回答案,这就是【自顶向下】
【自顶向上】直接从最低下,最简单,问题规模最小的f(1)和f(2)开始往上推,直到推到想要的答案f(20),这就是动态规划的思路。
求解动态规划的核⼼问题是穷举。
但是这类存在重叠子问题 ,直接暴力搜索效率较低。故需要一个备忘录或者DP Table,避免不必要的计算。
其一定具备最优子结构 ,才能通过子问题的最值得到原问题的最值。
由于问题千变万化,穷举所有可行解不不容易,故需要列出正确的状态转移方法 。
重叠子问题,最优子结构,状态转移方程是动态规划的三要素。
写出状态转移方程一般是最难的,一个辅助框架:
明确 状态 -> 定义 dp数组/函数的含义 -> 明确 选择 -> 明确 base case。
摘自算法小抄
基本例子 斐波那契数列 1、暴力递归 1 2 3 def fib (N:int ): if N == 1 or N == 2 : return 1 ; return fib(N-1 ) + fib(N-2 )
假设$N=20$,
可以发现,这里存在大量的重复计算,既重叠子问题 。
递归算法的时间复杂度:子问题个数 * 解决一个子问题需要的时间(函数中的操作)。
2、带备忘录的递归 既然耗时原因是超时,那就造一个备忘录 ,每次算一个子问题的值后,先将其存到备忘录中,再返回;每次遇到一个子问题先查询备忘录,看看有没有记录,再去做。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def fib_with_not (N:int ): note = [0 ]*(N+1 ) def helper (n:int ): if n == 1 or n == 2 : return 1 ; if note[n] != 0 : return note[n] note[n] = helper(n-1 ) + helper(n-2 ) return note[n] res = helper(N) return res
现在画出递归树,你就知道 备忘录 到底做了什么:
实际上, 带「备忘录」 的递归算法, 把⼀棵存在巨量冗余的递归树通过「剪枝」 , 改造成了⼀幅不存在冗余的递归图, 极⼤减少了⼦问题(即递归图中节点) 的个数。
自顶向上与自顶向下
【自顶向下】从一个规模较大的问题 f(20),向下逐渐分解规模,直到 f(1)、f(2)触底,再逐层返回答案,这就是【自顶向下】
【自顶向上】直接从最低下,最简单,问题规模最小的f(1)和f(2)开始往上推,直到推到想要的答案f(20),这就是动态规划的思路。
3、dp数组的迭代解法 将备忘录独立出来,搞成dp_table,在其上完成【自底向上】。
1 2 3 4 5 6 7 8 def fib3 (N:int ): dp_table = [0 ] *(N+1 ) dp_table[1 ] = dp_table[2 ] =1 for i in range (3 ,N+1 ): dp_table[i] = dp_table[i-1 ] + dp_table[i-2 ] return dp_table[N]
这⾥, 引出「状态转移⽅程」 这个名词, 实际上就是描述问题结构的数学形式:
「状态转移⽅程」 的重要性, 它是解决问题的核⼼。 很容易发现, 其实状态转移⽅程直接代表着暴⼒解法。
凑零钱问题 给你 k 种⾯值的硬币, ⾯值分别为 c1, c2 ... ck , 每种硬币的数量⽆限, 再给⼀个总⾦额 amount , 问你最少需要⼏枚硬币凑出这个⾦额, 如果不可能凑出, 算法返回 -1 。函数签名如下:
1 2 def coinChange (coins:list , amount:int )
⽐如说 k = 3 , ⾯值分别为 1, 2, 5, 总⾦额 amount = 11 。 那么最少需要 3 枚硬币凑出, 即 11 = 5 + 5 + 1。
1、暴力递归
什么是最优子结构?
假如问题是考出最高的总成绩,那么你只需要每门课考到最好,每门课之间互不影响。
但是如果加一个条件,你的语文和数学成绩会相互制约,此消彼长。这样你考的总分就不是最高的了。因为子问题并不独立,无法同时最优,所以最优子结构被破坏。
回到这一题,如你想求amount=11的最少硬币数,你可以先求amount=10的最少硬币数(子问题),你只需要把子问题的答案加一就是原问题的答案。
先确定【状态】,既原问题和子问题之间变化的量,由于硬币数量无限,故唯一的状态就是目标金额amount。
确定【dp函数】的定义,当前目标金额是n,至少需要dp(n)个硬币凑出该金额。
然后确定【选择】并择优,既对于每个状态,可以做出什么选择改变当前状态。
体到这个问题, ⽆论当前的⽬标⾦额是多少, 选择就是从⾯额列表coins 中选择⼀个硬币, 然后⽬标⾦额就会减少。
代码框架:
1 2 3 4 5 6 7 8 def coinChange (coins:list , amount:int ): def dp (n ): for coin in coins: res = min (res,1 +dp(n-coin)) return res return dp(amount)
最后明确【base case】,显然目标金额为0时,所需硬币为0;当目标金额小于0时,无解,返回-1;
1 2 3 4 5 6 7 8 9 10 11 12 def coinChange (coins:list , amount:int ): def dp (n ): if n == 0 : return 0 if n < 0 : return -1 res = float ('inf' ) for coin in coins: subproblem = dp(n-coin) res = min (res,1 +subproblem) return res if res != float ('inf' ) else -1 return dp(amount)
以上代码的数学形式就是状态转移方程:
2、带备忘录的递归 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def coinChange2 (self, coins: list , amount: int ) -> int : memo = dict () def dp (n ): if n in memo: return memo[n] if n == 0 : return 0 if n < 0 : return -1 res = float ('inf' ) for coin in coins: subproblem = dp(n-coin) if subproblem == -1 : continue res = min (res,1 +subproblem) memo[n] = res if res != float ('inf' ) else -1 return memo[n] return dp(amount)
3、dp数组的迭代解法 定义dp数组:
dp[i]=x表示,当目标金额为i时,至少需要x枚硬币。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution : def coinChange (self, coins: List [int ], amount: int ) -> int : dp = [amount+1 ] * (amount+1 ) dp[0 ] = 0 for i in range (len (dp)): for coin in coins: if i - coin < 0 : continue dp[i] = min (dp[i],1 + dp[i-coin]) return -1 if dp[amount] == amount + 1 else dp[amount]
NC19 子数组的最大累加和问题 分析 定义dp数组的含义:
**以nums[i]为结尾的「最大子数组和」为dp[i]**。
这种定义之下,想得到整个nums数组的「最大子数组和」,不能直接返回dp[n-1],而需要遍历整个dp数组:
1 2 3 4 5 int res = Integer.MIN_VALUE;for (int i = 0 ; i < n; i++) { res = Math.max(res, dp[i]); } return res;
依然使用数学归纳法来找状态转移关系:假设我们已经算出了dp[i-1],如何推导出dp[i]呢?
可以做到,dp[i]有两种「选择」,要么与前面的相邻子数组连接,形成一个和更大的子数组 ;要么不与前面的子数组连接,自成一派,自己作为一个子数组。
如何选择?既然要求「最大子数组和」,当然选择结果更大的那个:
1 2 // 要么自成一派,要么和前面的子数组合并 dp[i] = Math.max (nums[i], nums[i] + dp[i - 1 ]);
解法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution : def maxsumofSubarray (self , arr ): n = len (arr) if n == 0 : return 0 dp = [0 ] *n dp[0 ] = arr[0 ] for i in range (1 ,n): dp[i] = max (dp[i-1 ]+arr[i],arr[i]) sum_max = float ("-inf" ) print (dp) for i in range (n): sum_max = max (sum_max,dp[i]) return sum_max
NC7 买卖股票的最好时机 暴力解法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution : def maxProfit2 (self , prices ): ''' 暴力法 ''' n = len (prices) g_max = 0 for i in range (n): for j in range (i+1 ,n): cur_max = prices[j] - prices[i] g_max = max (cur_max,g_max) return g_max
一次遍历 1 2 3 4 5 6 7 8 9 10 def maxProfit (self,prices ): g_max = 0 curMin = prices[0 ] n = len (prices) for sell in range (1 ,n): curMin = min (curMin,prices[sell]) g_max = max (g_max, prices[sell] - curMin) return g_max
NC34 求路径 分析 机器人有两个约束条件:
定义DP数组
二维dp数组,dp[i] [j] 表示从起点到我当前位置(i行j列)有多少条可行的路径,因为我们从约束条件中可以知道,机器人每次只能往右或者往下走,则我们当前点必定是从上面或者左边走过来的(除边界外)。
解法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution : def uniquePaths (self , m , n ): dp = [ [0 for _ in range (n)] for _ in range (m)] dp[0 ][0 ]= 1 for row in range (m): for col in range (n): if row > 0 and col > 0 : dp[row][col] = dp[row-1 ][col] + dp[row][col-1 ] elif row == 0 and col>0 : dp[row][col] = dp[row][col-1 ] elif row > 0 and col ==0 : dp[row][col] = dp[row-1 ][col] return dp[m-1 ][n-1 ]
NC126 换钱的最少货币数 描述 给定数组arr,arr中所有的值都为正整数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个aim,代表要找的钱数,求组成aim的最少货币数。
如果无解,请返回-1.
解法 定义dp数组:dp[i]=x表示,当目标金额为i时,至少需要x枚硬币。
回到这一题,如你想求amount=11的最少硬币数,你可以先求amount=10的最少硬币数(子问题),你只需要把子问题的答案加一就是原问题的答案。
加1指的是,用当前面额的硬币可以凑出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution : def minMoney (self , arr , aim ): dp = [aim+1 ] * (aim+1 ) dp[0 ] = 0 coins = sorted (arr) for cur_money in range (len (dp)): for coin in coins: if cur_money-coin < 0 : continue dp[cur_money] = min (dp[cur_money],1 + dp[cur_money-coin]) return -1 if dp[aim] == aim+1 else dp[aim]
NC145 0-1背包 描述 已知一个背包最多能容纳物体的体积为
现有 个物品,第 个物品的体积为$ v_i$ , 重量为 $w_i$。
求当前背包最多能装多大重量的物品?
解法 这个题目中的物品不可以分割,要么装进包里,要么不装,不能说切成两块装一半。
第一步 要明确两点,「状态」和「选择」。
状态有两个,就是「背包的容量」和「可选择的物品」 。
选择就是「装进背包」或者「不装进背包」 。
明白了状态和选择,动态规划问题基本上就解决了,只要往这个框架套就完事儿了:
1 2 3 4 for 状态1 in 状态1 的所有取值: for 状态2 in 状态2 的所有取值: for ... dp[状态1 ][状态2 ][...] = 择优(选择1 ,选择2. ..)
第二步要明确dp数组的定义 。
dp数组是什么?其实就是描述问题局面的一个数组。换句话说,我们刚才明确问题有什么「状态」,现在需要用dp数组把状态表示出来。
首先看看刚才找到的「状态」,有两个,也就是说我们需要一个二维dp数组,一维表示可选择的物品,一维表示背包的容量。
dp[i][w]的定义如下:对于前i个物品,当前背包的容量为w,这种情况下可以装的最大价值是dp[i][w]。
比如说,如果 dp[3][5] = 6,其含义为:对于给定的一系列物品中,若只对前 3 个物品进行选择,当背包容量为 5 时,最多可以装下的价值为 6。
根据这个定义,我们想求的最终答案就是dp[N][W]。
base case 就是dp[0][..] = dp[..][0] = 0,因为没有物品或者背包没有空间的时候,能装的最大价值就是 0。
细化上面的框架:
1 2 3 4 5 6 7 8 9 10 11 int dp[N+1 ][W+1 ]dp[0 ][..] = 0 dp[..][0 ] = 0 for i in [1. .N]: for w in [1. .W]: dp[i][w] = max ( 把物品 i 装进背包, 不把物品 i 装进背包 ) return dp[N][W]
第三步,根据「选择」,思考状态转移的逻辑。
简单说就是,上面伪码中「把物品i装进背包」和「不把物品i装进背包」怎么用代码体现出来呢?
这一步要结合对dp数组的定义和我们的算法逻辑来分析:
先重申一下刚才我们的dp数组的定义:
dp[i][w]表示:对于前i个物品,当前背包的容量为w时,这种情况下可以装下的最大价值是dp[i][w]。
如果你没有把这第i个物品装入背包 ,那么很显然,最大价值dp[i][w]应该等于dp[i-1][w]。你不装嘛,那就继承之前的结果。
如果你把这第i个物品装入了背包 ,那么dp[i][w]应该等于dp[i-1][w-wt[i-1]] + val[i-1]。
首先,由于i是从 1 开始的,所以对val和wt的取值是i-1。
从1开始,对应的物品是0。
而dp[i-1][w-wt[i-1]]也很好理解:你如果想装第i个物品,你怎么计算这时候的最大价值?
w是当前背包的容量,wt[i-1]是第i个物品的重量,val[i-1]是第i个物品的价值
换句话说,在装第i个物品的前提下,背包能装的最大价值是多少?
显然,你应该寻求剩余重量w-wt[i-1]限制下能装的最大价值,加上第i个物品的价值val[i-1],这就是装第i个物品的前提下,背包可以装的最大价值。
综上就是两种选择,我们都已经分析完毕,也就是写出来了状态转移方程,可以进一步细化代码:
1 2 3 4 5 6 7 for i in [1. .N]: for w in [1. .W]: dp[i][w] = max ( dp[i-1 ][w], dp[i-1 ][w - wt[i-1 ]] + val[i-1 ] ) return dp[N][W]
完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 class Solution : def knapsack (self , V , n , vw ): dp = [[0 for _ in range (V+1 )] for _ in range (n+1 )] for i in range (1 , n+1 ): for j in range (1 , V+1 ): if j < vw[i-1 ][0 ]: dp[i][j] = dp[i-1 ][j] else : dp[i][j] = max (dp[i-1 ][j], dp[i-1 ][j-vw[i-1 ][0 ]]+vw[i-1 ][1 ]) return dp[n][V]
C++版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int knapsack (int W, int N, vector<int >& wt, vector<int >& val) { vector<vector<int >> dp (N + 1 , vector<int >(W + 1 , 0 )); for (int i = 1 ; i <= N; i++) { for (int w = 1 ; w <= W; w++) { if (w - wt[i-1 ] < 0 ) { dp[i][w] = dp[i - 1 ][w]; } else { dp[i][w] = max (dp[i - 1 ][w - wt[i-1 ]] + val[i-1 ], dp[i - 1 ][w]); } } } return dp[N][W]; }
NC148 几步可以从头跳到尾 描述 动态规划
来自牛客题解区
使用 dp[i] 表示第 i 个元素到最后一个元素所需要的步数(下标从1开始),起点是 n-1 到 n 需要步数为1,依次自定向下计算:
对于第 $i$个元素,如果 $i + A[i-1] \geq n$ (数组下标从0开始),则可以一步到位;
否则我们遍历 $i$ 之后的元素,找到最小到$n$的步数,既 $dp[i] = min(dp[i+j],d[i])$,(j为往后遍历的数量),并使之加一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution : def Jump (self , n , A ): dp = [n+1 ]*(n+1 ) if 1 + A[0 ] >= n: return 1 dp[n-1 ] = 1 for i in range (n-2 ,0 ,-1 ): print ("i->" ,i) if i + A[i-1 ] >= n: dp[i] = 1 continue for j in range (1 ,A[i-1 ]+1 ): dp[i] = min (dp[i+j],dp[i]) ''' 由于是从后向前遍历,前一步到后一步需要+1 ''' dp[i] += 1 print (dp) return dp[1 ]
动态规划2 从前向后找跳板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def Jump2 (self,n,A ): dp = [n+1 ] * (n) dp[0 ] = 0 for i in range (0 ,n): cur_max = i + A[i] j = i + 1 while j<= cur_max and j <n: dp[j] = min (dp[i]+1 ,dp[j]) j += 1 return dp[n-1 ]
动态规划3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def Jump3 (self,n,A ): dp = [0 ] * (n+1 ) i,j = 2 ,1 while i<=n: while j + A[j-1 ] < i: j += 1 dp[i] = dp[j] + 1 i += 1
贪心算法
摘自牛客评论区
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def greedy (self,n,A ): ''' 贪心算法 ''' end = 0 farthest = 0 steps = 0 len_ = n-1 for i in range (len_): print (end,farthest) farthest = max (farthest,i+A[i]) if end >= n: break if end == i: end = farthest steps+=1 return steps
NC155 牛牛的数列
摘自牛客评论区
牛牛现在有一个 n 个数组成的数列 nums , 牛牛现在想取一个连续的 子序列,并且这个子序列还必须得满足: 最多只把一个数改变成一个正整数 , 就可以使得这个连续的子序列是一个严格上升 的子序列, 牛牛想知道这个连续子序列最长的长度是多少。
分析 tail[i] 表示以i结尾的最长连续递增子序列的长度,
head[i]表示以i开头的最长连续递增子序列的长度
状态转移方程,
对于tail[i],初始状态tails[0]=1,如果 nums[i]大于nums[i-1],则tail[i] = tail[i-1]+1,从左往右遍历
对于head[i],初始状态head[n-1]=1,如果nums[i]小于nums[i+1],则head[i] = head[i+1] +1,从右往左遍历
解法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class Solution : def maxSubArrayLength (self , nums ): n = len (nums) if n<=2 : return n head = [0 ] * n tail = [0 ] * n tail[0 ] = 1 for i in range (1 ,n): if nums[i]>nums[i-1 ]: tail[i] = tail[i-1 ] + 1 else : tail[i] = 1 head[n-1 ] = 1 for i in range (n-1 ,-1 ,-1 ): if nums[i] > nums[i-1 ]: head[i-1 ] = head[i] + 1 else : head[i-1 ] = 1 res = 1 for i in range (n): if i-1 >=0 and i+1 < n and nums[i+1 ]-nums[i-1 ]>1 : res = max (res,tail[i-1 ]+head[i+1 ]+1 ) else : if i == 0 : if nums[i+1 ] > 1 : res = max (res,head[i+1 ]+1 ) else : res = max (res,head[i+1 ]) elif i == n-1 : res = max (res,tail[i-1 ]+1 ) else : res = max (res,tail[i-1 ]+1 ,head[i+1 ]+1 ) return res
NC127 最长公共字串 描述 给定两个字符串str1和str2,输出两个字符串的最长公共子串
题目保证str1和str2的最长公共子串存在且唯一。
解法:暴力解法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution : def LCS (self , str1 , str2 ): len1 = len (str1) len2 = len (str2) if len1 < len2: min_len = len1 min_str = str1 max_str = str2 else : min_len = len2 min_str = str2 max_str = str1 global_max_str = "" for i in range (min_len-1 ,-1 ,-1 ): for j in range (i,min_len): cur_str = min_str[i:j+1 ] if cur_str in max_str: if len (global_max_str) < len (cur_str): global_max_str = cur_str print (str2[i:j+1 ]) return global_max_str
解法:动态规划 参考评论区:
【数据结构和算法】动态规划解最长公共子串_牛客博客 (nowcoder.net)
注意这题求的是最长公共子串,不是最长公共子序列,子序列可以是不连续的,但子串一定是连续的。
定义 dp[i][j] 表示字符串 str1中的第 i个字符和 str2中 第 j 个字符为最后一个元素所构成的最长公共字串。
如果要求 dp[i][j],也就是 str1的第 i 个字符和str2的第j个字符为最后一个元素所构成的最长公共字串,我们首先需要判断这两个字符是否相等。
其重点是,以i,j分别是str1,str2的结束字符串,当前的结尾字符记录,会在前一个字符记录上进行迭代,同时在迭代过程中记录字串长度,并记住结束位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution : def LCS (self , str1 , str2 ): maxLength = 0 maxLastIndex = 0 len1 = len (str1) len2 = len (str2) dp = [ [0 for _ in range (len2+1 )] for _ in range (len1+1 ) ] for i in range (len1): for j in range (len2): if str1[i] == str2[j]: dp[i+1 ][j+1 ] = dp[i][j] + 1 if dp[i+1 ][j+1 ] > maxLength: maxLength = dp[i+1 ][j+1 ] maxLastIndex = i else : dp[i+1 ][j+1 ] = 0 res = str1[maxLastIndex-maxLength+1 :maxLastIndex+1 ] return res
解法二:优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 maxLastIndex = 0 maxLength = 0 len1 = len (str1) len2 = len (str2) dp = [0 for _ in range (len2+1 )] for i in range (0 ,len1): for j in range (len2-1 ,-1 ,-1 ): if str1[i] == str2[j]: dp[j+1 ] = dp[j] + 1 if dp[j+1 ] > maxLength: maxLength = dp[j+1 ] maxLastIndex = i else : dp[j+1 ] = 0 res = str1[maxLastIndex-maxLength+1 :maxLastIndex+1 ] return res
NC91 最长递增子序列 描述 给定数组arr,设长度为n,输出arr的最长递增子序列。(如果有多个答案,请输出其中 按数值(注:区别于按单个字符的ASCII码值)进行比较的 字典序最小的那个)
示例:
1 2 输入:[2,1,5,3,6,4,8,9,7] 返回值:[1,3,4,8,9]
解法:动态规划 来自评论区
题解 | #最长递增子序列#_牛客博客 (nowcoder.net)
dp[i]=x 意义为 以i为结尾的序列,最大递增子长度是多少。
比较顺序如下:
i
j_border
i->j
1
1
0
2
2
0,1
3
3
0,1,2
…
…
…
6
6
0,1,2,3,4,5
6号位置上的元素会依次与0-5号位置上的元素进行比较,如果6号位置上的元素大于0号位置上的元素,则dp[6]=dp[0]+1。
1 dp[i] = dp[j] + 1, if arr[i] > arr[j], j = 0,..., i-1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution : def LIS (self , arr ): len_arr = len (arr) dp = [1 ] *(len_arr+1 ) g_max = 0 for i in range (len_arr): for j in range (i): if arr[i] > arr[j] and dp[i]<dp[j]+1 : dp[i] = dp[j] + 1 g_max = max (g_max,dp[i]) res = [0 ] * (g_max) dp_size = len_arr + 1 i = dp_size-1 j = g_max while j >0 : if dp[i] == j: j -=1 res[j] =arr[i] i -= 1 return res
优化版:二分法动态规划
TY78 最大上升子序列 描述 链接:https://www.nowcoder.com/questionTerminal/dcb97b18715141599b64dbdb8cdea3bd?answerType=1&f=discussion 来源:牛客网
一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …,aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中序列和最大为18,为子序列(1, 3, 5, 9)的和. 你的任务,就是对于给定的序列,求出最大上升子序列和。注意,最长的上升子序列的和不一定是最大的,比如序列(100, 1, 2, 3)的最大上升子序列和为100,而最长上升子序列为(1, 2, 3)。
解法:来自评论区 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 size_n = int (input ().strip()) nums = list (map (int ,input ().strip().split())) dp = [0 ]*(size_n+1 ) g_max = 0 for i in range (size_n): dp[i] = nums[i] for j in range (i): if nums[i] > nums[j]: dp[i] = max (dp[i], dp[j] + nums[i]) g_max = max (g_max,dp[i]) print (g_max)
2021网易测试开发-第6题 描述 大富翁游戏规则如下
玩家起始会获得一定资本M金币
玩家每一次可以走一个格,或者跳两个格;走一格耗费2个金币能量;跳两个格,耗费3个金币能量;金币只有满足能量消耗时,才能继续往下走
玩家每走到一个格,会得到这个格的奖励,每个格的奖励金币数为非负整数
当玩家走到这个格后,总金币数不足以支持下一步金币消耗时,则不能继续往下走,游戏结束
玩家第一步可以选择走一步进第1格或者跳2步进第2格起始,玩家可以选择在任意一格结束游戏
问玩家游戏中,最多能得到多少个金币?
分析 创建数组 dp,dp[i]代表在第i个位置可以获得的最大收益,i 从1开始直到结束。
第0个位置为初始成本。
在第i个位置的收益,dp[i] 等于跳过去的位置的收益dp[i-j-1] 加上当前位置的收益golds[i-1]再减去消耗。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 def test4 (): M = int (input ()) golds = list (map (int ,input ().split())) step_costs = [2 ,3 ] n = len (golds) dp = [0 ] * (n+1 ) dp[0 ] = M gold = 0 for i in range (1 ,n+1 ): for j,cur_cost in enumerate (step_costs): if i -j-1 <0 : continue if dp[i-j-1 ] < cur_cost: continue dp[i] = max (dp[i],dp[i-j-1 ] + golds[i-1 ] - cur_cost) gold = max (gold,dp[i]) gein = max (dp[1 :]) print (gein)
描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意: 给定 n 是一个正整数。
1 2 3 4 5 输入: 2 输出: 2 解释: 有两种方法可以爬到楼顶。 1. 1 阶 + 1 阶 2. 2 阶
1 2 3 4 5 6 输入: 3 输出: 3 解释: 有三种方法可以爬到楼顶。 1. 1 阶 + 1 阶 + 1 阶 2. 1 阶 + 2 阶 3. 2 阶 + 1 阶
解法: 思想:dp[i]=dp[i-1]+dp[i-2],和废弃那波数列有点像
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution : def climbStairs1 (self, n: int ) -> int : if n > 2 : return self.climbStairs(n-2 ) + self.climbStairs(n-1 ) else : return n def climbStairs (self,n ) -> int : dp = [0 ] * (n+1 ) dp[1 ] = 1 dp[2 ] = 2 for i in range (3 ,n+1 ): dp[i] = dp[i-1 ] + dp[i-2 ] return dp[n]