算法训练Day41&42

2023年4月11日

        首先感慨一下,不知不觉坚持一个多月了,不管学的深还是浅,至少学到了很多新东西,总归是有收获的,尤其是贪心部分和递归部分,学到了一些新知识,希望自己能坚持到底,完成入门的算法学习,完整的过一遍。

#整数拆分 Loading Question... - 力扣(LeetCode)

        给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。

        如果不告诉我这道题用动态规划,我应该是想不到需要使用动态规划的方法的。

        首先需要分析dp[i]的含义,dp[i]就是将数字i拆成若干数字后的最大乘积

        接下来就是确定递推式,很容易想到将 i 拆分成 j 和 i-j 两个数,j从1到  i-1 进行遍历,不断计算当拆成两个数的乘积的最大值,例如 5=1+4=2+3=3+2=4+1,这里也很明显能发现,是有重叠部分的,因为2*3和3*2的结果是一样的,所以这也就是为什么只拆分 i 而不拆分 j 的一个原因。同理,如果想要获取多于两个数的乘积,只需要获取dp[i-j]的值和 j 进行相乘,就相当于有多个数进行相乘了。

        那么就能得到递归式 dp[i] = max({dp[i], (i - j) * j, dp[i - j] * j}) , 为什么还有一个dp[i]呢,只不过在递归公式进行推导的时候取最大值罢了

        下面就是数组的初始化,很明显dp[0]和dp[1]是不能被初始化的,因为二者是没法拆分成至少两个正整数的和的,所以只初始化dp[2]=1,这个是没有任何异议的

        遍历顺序很明显的从前到后,因为由dp[i-j]推dp[i],而如果想要拆分的乘积最大,很明显要让数尽可能地接近,因为在和一定的情况下,一定是均分后的乘积最大,就像周长相等的情况下,正方形的面积一定是最大的,那这样的话就不需要遍历那么多了,只需要遍历“一半”即可,整体的代码如下

class Solution {
public:
    int integerBreak(int n) {
        vector<int> dp(n + 1);
        dp[2] = 1;
        for (int i = 3; i <= n ; i++) {
            for (int j = 1; j <= i / 2; j++) {
                dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));
            }
        }
        return dp[n];
    }
};

        下面是贪心的解法

class Solution {
public:
    int integerBreak(int n) {
        if (n == 2) return 1;
        if (n == 3) return 2;
        if (n == 4) return 4;
        int result = 1;
        while (n > 4) {
            result *= 3;
            n -= 3;
        }
        result *= n;
        return result;
    }
};

#不同的二叉搜索树 Loading Question... - 力扣(LeetCode)

        这道题的创新点在我看来,就是通过子树的形状来推出递推公式,通过观察子树的个数来推出最终的个数

        递推公式是 dp[i] += dp[j - 1] * dp[i - j]; ,j-1 为j为头结点左子树节点数量,i-j 为以j为头结点右子树节点数量

        

class Solution {
public:
    int numTrees(int n) {
        vector<int> dp(n + 1);
        dp[0] = 1;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= i; j++) {
                dp[i] += dp[j - 1] * dp[i - j];
            }
        }
        return dp[n];
    }
};

#背包问题 0-1初步

        如果用暴力解法,就是将所有的可能的组合情况一一列举,然后判断每个组合的重量大小,然后将符合重量的组合再进行价值的筛选,筛选出符合条件的结果。

        而采用动态规划,代码不仅会更加简洁,而且容易理解,这里主要介绍用一维数组(滚动数组)的情况,因为二维数组已经可以说烂熟于心了,所以这里只给出代码

        

void test_2_wei_bag_problem1() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagweight = 4;

    // 二维数组
    vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));

    // 初始化
    for (int j = weight[0]; j <= bagweight; j++) {
        dp[0][j] = value[0];
    }

    // weight数组的大小 就是物品个数
    for(int i = 1; i < weight.size(); i++) { // 遍历物品
        for(int j = 0; j <= bagweight; j++) { // 遍历背包容量
            if (j < weight[i]) dp[i][j] = dp[i - 1][j];
            else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

        }
    }

    cout << dp[weight.size() - 1][bagweight] << endl;
}

int main() {
    test_2_wei_bag_problem1();
}

        而对于一维数组来说,首先需要明确的是如何从二维数组变为一维数组,在二维数组中,我们有一个点,就是由dp[i-1]推dp[i]的情况,当时如果不放入当前物品i,那么dp[i][j]==dp[i-1][j],即在当前背包大小下,我不放入i物品,那么此时背包中的价值就是只放入i-1物品的那时候的背包的最大值,这就是将二维数组转为一维数组的关键点,就是将 i 抹掉,只考虑当背包容量为j的时候背包中物品的最大值。

        接下来很容易得出递推公式 

dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

        其中dp[j]就是不放入当前物品的情况,相应的后面就是放入当前物品的情况,我只需要用背包容量减去当前物品的重量,找到没有这个物品的时候背包的最大价值,然后再加上这个物品的价值就可以得到放入当前物品后背包的最大值

         下面就是确定遍历顺序,这就是一维数组跟二维数组的不同之处,二维数组两个for循环交换顺序是不会影响结果的,但是一维数组就不行,因为这时候我不仅需要用到上一次遍历的值,而且由于我是一维的,我是不能将一个物品放入两遍的,这时候就能推出遍历顺序是需要从后向前进行遍历的,如果我从前向后进行遍历,我不仅用不到上一次遍历的值,而且会将一个物品放入两遍,这与我们的要求是不符的。

        那么就可以得出以下的代码

void test_1_wei_bag_problem() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagWeight = 4;

    // 初始化
    vector<int> dp(bagWeight + 1, 0);
    for(int i = 0; i < weight.size(); i++) { // 遍历物品
        for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    cout << dp[bagWeight] << endl;
}

int main() {
    test_1_wei_bag_problem();
}

        动态规划的入门就开始了,依然是要学习到思想学习到方法,需要学习到当这个题需要用动态规划的时候的写法就够了,多理解动态规划五部曲,同时要多学习什么样的题需要使用到动态规划的这种思想

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值