1、专题名称
动态规划算法的设计与分析
2、实践内容
2.1 实践内容 1
(1)题目要求
给你一个整数数组 coins
,表示不同面额的硬币;以及一个整数 amount
,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1
。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:
输入:coins = [2], amount = 3
输出:-1
示例 3:
输入:coins = [1], amount = 0
输出:0
提示:
1 <= coins.length <= 12
1 <= coins[i] <= 2
^31^- 1
0 <= amount <= 10
^4^
(2)算法设计
列出动态规划转移方程,按顺序遍历即可.
(3)代码实现
第一次代码
class Solution {
public:
int sum = 0;
int flag = 0;
void dfs(vector<int>& coins,int temp,int amount,int num){
for(int i = num ; i >= 0 ; i--){
int j = i,amount1 = amount;
if(sum!=0&&temp>=sum)
break;
while(j>=0){
while(amount1>=coins[j]){
amount1-=coins[j];
temp ++;
dfs(coins,temp,amount1,i);
}
j--;
if(amount1 == 0){
flag = 1;
if(sum == 0)
sum = temp;
else
sum = min(sum,temp);
break;
}
}
}
}
int coinChange(vector<int>& coins, int amount) {
sort(coins.begin(),coins.end());
if(!amount)
return 0;
else
dfs(coins,0,amount,coins.size()-1);
if(!flag)
sum = -1;
return sum;
}
};
第二次代码
此时已经基本优化成贪心的最简了
class Solution {
public:
int sum = 0;
int flag = 0;
void dfs(vector<int>& coins,int temp,int amount,int num){
int j = num,amount1 = amount;
if(amount1 == 0){
flag = 1;
// cout << "sum:" << sum << " " << temp << " " << j << endl;
if(sum == 0)
sum = temp;
else
sum = min(sum,temp);
return;
}
if(j<0)
return;
if(sum!=0 && sum <= amount1/coins[j])
return;
while(j>=0){
if(sum!=0 && temp>=sum-1)
return;
for(int k = amount1/coins[j]; k > 0 ; k--){
if(sum!=0 && temp>=sum-1)
return;
if(k <= amount1/coins[j]-sum+1 && sum!=0)
return;
// cout << amount1 << " " << temp << " " << k << endl;
if(amount1!=0 && k!=0)
dfs(coins,temp+k,amount1-k*coins[j],j-1);
}
j--;
}
}
int coinChange(vector<int>& coins, int amount) {
sort(coins.begin(),coins.end());
if(!amount)
return 0;
else
for(int i = coins.size()-1 ; i >= 0 ; i--)
dfs(coins,0,amount,i);
if(!flag)
sum = -1;
return sum;
}
};
优化后代码
参考官方答案,记忆化搜索
class Solution {
vector<int>count;
int dp(vector<int>& coins, int rem) {
if (rem < 0) return -1;
if (rem == 0) return 0;
if (count[rem - 1] != 0) return count[rem - 1];
int Min = INT_MAX;
for (int coin:coins) {
int res = dp(coins, rem - coin);
if (res >= 0 && res < Min) {
Min = res + 1;
}
}
count[rem - 1] = Min == INT_MAX ? -1 : Min;
return count[rem - 1];
}
public:
int coinChange(vector<int>& coins, int amount) {
if (amount < 1) return 0;
count.resize(amount);
return dp(coins, amount);
}
};
动态规划
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<int>dp(amount+1,amount + 1);
dp[0] = 0;
for(int i = 1 ; i <= amount ; i++){
for(int j = 0 ; j < coins.size() ; j++){
if(i >= coins[j])
dp[i] = min(dp[i],dp[i-coins[j]]+1);
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
};
(4)算法分析
时间复杂度:O(n)
空间复杂度:O(1)
算法优点:时间复杂度和空间复杂度都低。
缺点:要用动态规划思想来解决,比较难想
2.2 实践内容 2
(1)题目要求
给你一个整数数组 nums
,请你找出并返回能被三整除的元素最大和。
示例 1:
输入:nums = [3,6,5,1,8]
输出:18
解释:选出数字 3, 6, 1 和 8,它们的和是 18(可被 3 整除的最大和)。
示例 2:
输入:nums = [4]
输出:0
解释:4 不能被 3 整除,所以无法选出数字,返回 0。
示例 3:
输入:nums = [1,2,3,4,4]
输出:12
解释:选出数字 1, 3, 4 以及 4,它们的和是 12(可被 3 整除的最大和)。
提示:
1 <= nums.length <= 4 * 10^4
1 <= nums[i] <= 10^4
(2)算法设计
根据题目要求,分别对x%3==0 x%3==1 x%3==2
三种情况进行处理
(3)代码实现
第一次提交
class Solution {
public:
int maxSumDivThree(vector<int>& nums) {
sort(nums.begin(),nums.end());
int sum = 0,m1 = -1,m2 = -1;
vector<int> n1,n2;
for(auto x : nums){
if(x%3 == 0)
sum += x;
if(x%3 == 1){
n1.push_back(x);
m1 ++;
}
if(x%3 == 2){
n2.push_back(x);
m2 ++;
}
}
while(1){
int sum1 = 0,sum2 = 0,sum3 = 0,j = 0,summax;
if(m1-2>=0)
sum1 += n1[m1] + n1[m1-1] + n1[m1-2];
if(m2-2>=0)
sum2 += n2[m2] + n2[m2-1] + n2[m2-2];
for(int i = 0 ; i <= 2 ; i++)
{
j = i+1;
if(m1-i<0||m2-i<0){
j--;
break;
}
sum3 += n1[m1-i] + n2[m2-i];
}
summax = max(sum1,max(sum2,sum3));
if(summax == 0)
break;
if(summax == sum3){
m1 -= j;
m2 -= j;
}
else if(summax == sum1)
m1 -= 3;
else if(summax == sum2)
m2 -= 3;
sum += summax;
}
return sum;
}
};
修改后代码
class Solution {
public:
int maxSumDivThree(vector<int>& nums) {
int N = nums.size();
int remainder[3] = {0};
for(int i = 0; i < N; i++){
int a,b,c;
a = remainder[0] + nums[i];
b = remainder[1] + nums[i];
c = remainder[2] + nums[i];
remainder[a%3] = max(remainder[a%3], a);
remainder[b%3] = max(remainder[b%3], b);
remainder[c%3] = max(remainder[c%3], c);
}
return remainder[0];
}
};
(4)算法分析
时间复杂度:O(1)
空间复杂度:O(1)
算法优点:空间复杂度低,时间复杂度低。
缺点:要结合题目要求,非常难想
2.3 实践内容 3
(1)题目要求
你将会获得一系列视频片段,这些片段来自于一项持续时长为 time
秒的体育赛事。这些片段可能有所重叠,也可能长度不一。
使用数组 clips
描述所有的视频片段,其中 clips[i] = [starti, endi]
表示:某个视频片段开始于 starti
并于 endi
结束。
甚至可以对这些片段自由地再剪辑:
- 例如,片段
[0, 7]
可以剪切成[0, 1] + [1, 3] + [3, 7]
三部分。
我们需要将这些片段进行再剪辑,并将剪辑后的内容拼接成覆盖整个运动过程的片段([0, time]
)。返回所需片段的最小数目,如果无法完成该任务,则返回 -1
。
示例 1:
输入:clips = [[0,2],[4,6],[8,10],[1,9],[1,5],[5,9]], time = 10
输出:3
解释:
选中 [0,2], [8,10], [1,9] 这三个片段。
然后,按下面的方案重制比赛片段:
将 [1,9] 再剪辑为 [1,2] + [2,8] + [8,9] 。
现在手上的片段为 [0,2] + [2,8] + [8,10],而这些覆盖了整场比赛 [0, 10]。
示例 2:
输入:clips = [[0,1],[1,2]], time = 5
输出:-1
解释:
无法只用 [0,1] 和 [1,2] 覆盖 [0,5] 的整个过程。
示例 3:
输入:clips = [[0,1],[6,8],[0,2],[5,6],[0,4],[0,3],[6,7],[1,3],[4,7],[1,4],[2,5],[2,6],[3,4],[4,5],[5,7],[6,9]], time = 9
输出:3
解释:
选取片段 [0,4], [4,7] 和 [6,9] 。
提示:
1 <= clips.length <= 100
0 <= starti <= endi <= 100
1 <= time <= 100
(2)算法设计
比较常规的动态规划问题,按顺序遍历数组,保证dp[]数组的每一项都最小即可
(3)代码实现
class Solution {
public:
int videoStitching(vector<vector<int>>& clips, int time) {
vector<int>dp(time + 1,time + 1);
dp[0] = 0;
for(int i = 1 ; i <= time ; i++){
for(auto x: clips){
if(x[0]<i&&x[1]>=i)
dp[i] = min(dp[i],dp[x[0]]+1);
}
}
return dp[time] == time + 1 ? -1 : dp[time];
}
};
(4)算法分析
时间复杂度:O(1)
空间复杂度:O(n)
算法优点:稍微易想。
缺点:使用auto遍历vector数组,占用了一定空间
3、问题与总结
问题:以上题目对动态规划思想有较高要求,使用了很多时间
总结:总体耗时较长,且对动态规划思想的了解更加熟练
Comments NOTHING