动态规划算法的设计与分析

发布于 2023-11-08  387 次阅读


内容纲要

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、问题与总结

问题:以上题目对动态规划思想有较高要求,使用了很多时间

总结:总体耗时较长,且对动态规划思想的了解更加熟练

4、完成情况证明

世界のネズミは彼らが望むものに依存し、彼らは彼ら自身から誰も求めません
最后更新于 2023-11-08