回顾一下分治思想,归并排序就是采用的这种思路:
https://leetcode-cn.com/problems/maximum-subarray/solution/zui-da-zi-xu-he-by-leetcode-solution/
这段话很重要,其实浏览器的搜索方式就是依赖于树结构,快速找到关键词所在的位置和信息。
题目1:零钱兑换II
这道题的难点在于存在重复的情况,如1 2 1 1和1 1 2 1是属于同一种零钱兑换方式,因此不能简单的将dp[i]写成
dp[i]+=dp[i-coin]
注意这里的循环方式不能将状态i作为外层循环,而应该将coins作为外层循环,状态i作为内层循环,利用不同的coin来更新状态数组dp[i]
正确的循环代码如下:
for (int i = 0; i < coins.size(); i++) { // 遍历物品
for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量
dp[j] += dp[j - coins[i]];
}
}
仔细观察可以发现,加入我要计算dp[6],coins为[1,5],那么首先循环将coins[0]=1带入,在内层循环中可以得出
dp[6]=1(61),外层完成一次循环。开启第二次外循环coins[1]=5,在内循环中dp[6]更新为2(61 1+5)。然后就完全结束了,不会记录(5+1)的情况,即不会存在重复,内层循环代码从conins[i]开始,因为小于conins[i]的状态数组必定为0。此时dp[j]里算出来的就是组合数
如果将两个循环交换一下:
for (int j = 0; j <= amount; j++) { // 遍历背包容量
for (int i = 0; i < coins.size(); i++) { // 遍历物品
if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
}
}
背包容量的每一个值,都是经过 1 和 5 的计算,包含了[1, 5] 和 [5, 1]两种情况。此时dp[j]里算出来的就是排列数
详细讲解见链接:
https://leetcode-cn.com/problems/coin-change-2/solution/518-ling-qian-dui-huan-iiwan-quan-bei-ba-ynjf/
题目2:分割等和子集
这个是0-1背包问题,解题思路见链接:
https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/
代码:
bool canPartition(vector<int>& nums) {
//计算nums的和
int sum = accumulate(nums.begin(), nums.end(), 0);
//如果sum为奇数,则返回false,因为奇数必定不能整除一半
if (sum & 1) return false;
//求出数组中的最大值
int max_num = *max_element(nums.begin(), nums.end());
int target = sum / 2;
if (max_num > target) return false;
//创建状态数组
vector<vector<bool>>dp(nums.size(), vector<bool>(sum / 2 + 1, false));
//确定边界条件
//dp[i][0]都为true
//dp[0][nums[0]]为true
for (int i = 0; i < nums.size(); i++) {
dp[i][0] = true;
}
dp[0][nums[0]] = true;
//开始遍历
for (int i = 1; i < nums.size(); i++) {
for (int j = 1; j <= sum / 2; j++) {
if (j < nums[i]) {
dp[i][j] = dp[i - 1][j];
}
else {
dp[i][j] = dp[i - 1][j] || dp[i-1][j - nums[i]];
}
}
}
return dp[nums.size() - 1][sum / 2];
}
进行空间优化:
代码:
bool canPartition2(vector<int>& nums) {
//计算nums的和
int sum = accumulate(nums.begin(), nums.end(), 0);
//如果sum为奇数,则返回false,因为奇数必定不能整除一半
if (sum & 1) return false;
//求出数组中的最大值
int max_num = *max_element(nums.begin(), nums.end());
int target = sum / 2;
if (max_num > target) return false;
//创建状态数组
vector<bool>dp(target+1,false);
dp[0] = true;
dp[nums[0]] = true;
//开始遍历
for (int i = 1; i < nums.size(); i++) {
for (int j = target; j >= nums[i]; j--) {
if (nums[i] == target)
return true;
dp[j] = dp[j] || dp[j - nums[i]];
}
}
return dp[target];
}
题目3:
这道题的关键在于自己动手推算,发现前几个数的规律,明确”3“的重要性,根据动态规划,某个大于4的数i都可以拆分成 3+ (i-3),所以dp[i]=dp[i-3]*3。由此可得所有数
方法一:动态规划 自底向上
int cuttingRope(int n) {
if(n<=3) return n-1;
vector<long>dp(n+1,1);
dp[2]=2;
dp[3]=3;
dp[4]=4;
for(int i=5;i<=n;i++){
dp[i]=dp[3]*dp[i-3];
dp[i]=dp[i]%10000;
}
return dp[n];
}
方法二:自顶向下,空间优化
//优化空间
int cuttingRope(int n) {
if(n<=3) return n-1;
long result=1;
while(n>4){
result=(result*3)%1000000007;
n-=3;
}
return (result*n)%1000000007;
}