赞
踩
我在刷题的时候学习到了“完全背包”问题。但是当遇到“完全背包 + 排列数” 或者 “完全背包 + 组合数” 就不太确定到底是:先物品,再背包(就是横着);还是 先背包,再物品,从小到大遍历(就是竖着) 进行遍历。
然后我搜索到这样的话:
结论一:
- 一般而言,先行或者先列都可以。但是有的不行。
- 组合问题的遍历顺序是:先物品,再背包,从小到大遍历(就是横着)。
先放物品的题目:零钱兑换 II
- 排列问题的遍历顺序时:先背包,再物品,从小到大遍历(就是竖着)。
先循环背包的题目:组合总和 Ⅳ
- 如果排解和组合都可以,那就随便。如:零钱兑换
但经过我的实验发现:对于完全背包问题,二维 DP 可以无视顺序。上面这个顺序是对于代码简化后的一维 DP 。
这里以 零钱兑换 II 题目为例子,按照一般的理论,需要 “先物品,再背包” 。但经过验证发现,无论是哪种顺序的遍历都可以。下面是代码,大家可以实测。
// 代码一:先 i 后 j 遍历 class Solution { public: int change(int amount, vector<int>& coins) { int dp[3001][5001] = {0}; int n = coins.size(); for(int i = 0; i <= n; i++) dp[i][0] = 1; for(int i = 1; i <= n; i++) { for(int j = 1; j <= amount; j++ ) { if(coins[i - 1] > j) dp[i][j] = dp[i - 1][j]; else dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]]; } } return dp[n][amount]; } }; // 代码二:先 j 后 i 遍历 class Solution { public: int change(int amount, vector<int>& coins) { int dp[3001][5001] = {0}; int n = coins.size(); for(int i = 0; i <= n; i++) dp[i][0] = 1; for(int j = 1; j <= amount; j++) { // int i = 1; i <= n; i++ for(int i = 1; i <= n; i++) { if(coins[i - 1] > j) dp[i][j] = dp[i - 1][j]; else dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]]; } } return dp[n][amount]; } };
组合总和 Ⅳ是典型的先循环背包的题目。但是经过我的实测发现,在使用二维数组的情况下,遍历顺序也是可以互换的。付代码:
// 代码1; class Solution { public: int combinationSum4(vector<int>& nums, int target) { int dp[201][1001] = {0}; int n = nums.size(); for(int i = 0; i <= n; i++) dp[i][0] = 1; for(int i = 1; i <= n; i++) { // int j = 1; j <= target; j++ for(int j = 1; j <= target; j++) { for(int k = i; k > 0; k--) { if(j - nums[k - 1] >= 0 && dp[i][j] < INT_MAX - dp[i][j - nums[k - 1]]) { dp[i][j] += dp[i][j - nums[k - 1]]; } } } } return dp[n][target]; } }; // 代码二: class Solution { public: int combinationSum4(vector<int>& nums, int target) { int dp[201][1001] = {0}; int n = nums.size(); for(int i = 0; i <= n; i++) dp[i][0] = 1; for(int j = 1; j <= target; j++) { for(int i = 1; i <= n; i++) { for(int k = i; k > 0; k--) { if(j - nums[k - 1] >= 0 && dp[i][j] < INT_MAX - dp[i][j - nums[k - 1]]) { dp[i][j] += dp[i][j - nums[k - 1]]; } } } } return dp[n][target]; } };
其实,可以这么理解:“排列的难度” > “组合的难度”。所以在求排列的时候就不能像组合那样无脑查几个值就好,可能得要遍历已有表才行(这也是排列问题多一重循环的原因,排列问题多了第三重循环)。
另外,别人总结的应该也是没有错误的,那他们的结论是如何得到的呢?因为有人想在“排列”中节省一下,不使用第三重循环,就变成了以下的代码3。这个代码是不能够交换 i j 的遍历顺序的。但是代码2 是可以交换的。原因在于:上面的排列代码无论是代码1 还是 代码2,某个值 DP[i][j] 都是由其左上方矩阵中的值完全确定的,但是如果你想省去第三重循环,就得用左下方的值(如代码3),这个时候遍历的顺序肯定是无法交换的。
// 代码3 class Solution { public: int combinationSum4(vector<int>& nums, int target) { vector<vector<int>> dp(nums.size()+1, vector<int>(target+1,0)); for(int i=0; i<nums.size()+1; i++) dp[i][0] = 1; for(int j = 1; j<= target; j++){ for(int i = 1; i<=nums.size(); i++){ if(j >= nums[i-1] && dp[nums.size()][j-nums[i-1]] < INT_MAX - dp[i-1][j]) //防止溢出 dp[i][j] = dp[i-1][j] + dp[nums.size()][j-nums[i-1]] ; //更新量 = 不用当前值的方法数 + 包含当前值的方法数(囊括了重复的情况) else dp[i][j] = dp[i-1][j]; } } return dp[nums.size()][target]; } }; 链接:https://leetcode.cn/problems/combination-sum-iv/solutions/1755590/dong-tai-gui-hua-by-strange-6ouldntj-waip/
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。