剑指offer 学习笔记 动态规划与贪婪算法

如果面试题是求一个问题的最优解(通常是求最大值或最小值),而且
1.该问题能分解成若干个子问题。
2.子问题也存在最优解,如果把小问题的最优解组合起来能够得到整个问题的最优解,即整体问题的最优解依赖于各个子问题的最优解。
3.这些小问题之间有重叠的问题,即在分解大问题的过程中反复出现相同子问题。
就可以使用动态规划来解决这个问题。

动态规划算法中为了避免重复子问题的求解,我们可以用从下往上的顺序,先计算小问题的最优解并存储下来,再以此为基础求取大问题的最优解。

贪婪算法每一步都做出当前最优的选择。

面试题14:剪绳子。给你一段长度为n的绳子,请把绳子剪m次(m、n都是整数,并且n>1、m>=1),并且每段绳子长度都为整数,记为k[0]、k[1]…k[m]。请问k[0]*k[1]*…*k[m]可能的最大乘积是多少?例如,当一段绳子长为8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

方法一:动态规划。首先定义函数f(n)为把长度为n的绳子剪成若干段后各段长度乘积的最大值。在剪第一刀的时候,我们有n-1种可能的选择,也就是剪出来的第一段绳子的可能长度分别为1,2,…,n-1。因此f(n)=max(f(i)*f(n-i)),其中0<i<n。这是一个从上至下的递归公式,由于递归会产生很多重复的子问题,从而有大量不必要的计算。更好的方法是从下至上计算,也就是先得到f(2)、f(3),再得到f(4)、f(5),直到得到f(n):

#include <iostream>
using namespace std;

int MaxProductAfterCutting(int length) {
    if (length < 2) {    // 绳子长度小于2时不符合题意
        return 0;
    }
    if (length == 2) {    // 绳子长为2时只能剪成1*1
        return 1;
    }
    if (length == 3) {    // 绳长为3时只能剪成1*2
        return 2;
    }

    int* products = new int[length + 1];    // 存放子问题的最优解,其实只需要length长度的数组
                                            // 如输入绳长为5,求f(5),只需f(1)~f(5)
                                            // 但为了书写方便,如需要f(3)时只需找数组下标为3的元素而不用找下标为2的元素
                                            // 需要将数组长扩充一位,其中第0个元素不会使用到
    products[0] = 0;    // 0号元素随便初始化,不会用到
    products[1] = 1;    
    products[2] = 2;
    products[3] = 3;    // 下标1~3存放绳长即可,下标4~length存放的才是绳长为下标时的最优解
                        // 因为在绳长为1时,不能再分割
                        // 绳长为2时,若分割,只能分成1*1=1,不分割时为2,此时最优解为2
                        // 绳长为3时,若分割,只能分成1*2=2,不分割时为3,此时最优解为3
                        // 由于题目要求必须分割一次,因此当输入绳长大于3时,用到绳长为1~3时的最优解时,必定已经被分割过一次了

    int max;    // 存放绳长为a时的最大值,a为任意中间子问题解
    for (int i = 4; i <= length; ++i) {    // 从绳长为4开始
        max = 0;    // 每次开始找绳长为a的最优解时先初始化max
        for (int j = 1; j <= i / 2; ++j) {
            int product = products[j] * products[i - j];

            if (max < product) {
                max = product;
            }
        }
        products[i] = max;
    }

    delete[] products;
    return max;
}

int main(int argc, char **argv) {
    cout << MaxProductAfterCutting(8) << endl;
    return 0;
}

动态规划的时间复杂度为O(n²),空间复杂度为O(n),n为绳长。

方法二:贪婪算法。按以下策略剪绳子,得到的各段绳子的长度的乘积将最大:当n>=5时,尽可能多地剪长度为3的绳子;当剩下的绳子长度为4时,把绳子剪成两段长度为2的绳子,数学证明在代码后:

#include <iostream>
using namespace std;

int MaxProductAfterCutting(int length) {
    if (length < 2) {    // 绳子长度小于2时不符合题意
        return 0;
    }
    if (length == 2) {    // 绳子长为2时只能剪成1*1
        return 1;
    }
    if (length == 3) {    // 绳长为3时只能剪成1*2
        return 2;
    }

    int timesOf3 = length / 3;    // 当前输入的length长能分出几段长为3的绳段
    if (length - 3 * timesOf3 == 1) {    // 如果每次取一段长为3绳段,最后能剩下一段长为4的绳段时
        --timesOf3;    // 最后的长为4的绳段不再分为1和3
    }
    int timesOf2 = (length - timesOf3 * 3) / 2;    // 最后剩下的是2或4时能分为几个2

    return (int)pow(3, timesOf3) * (int)pow(2, timesOf2);
}

int main(int argc, char **argv) {
    cout << MaxProductAfterCutting(8) << endl;
    return 0;
}

以下为LeetCode上的数学推导过程:https://leetcode-cn.com/problems/jian-sheng-zi-lcof/solution/mian-shi-ti-14-i-jian-sheng-zi-tan-xin-si-xiang-by/

在这里插入图片描述
在这里插入图片描述
C++pow函数的时间复杂度取决于底层架构,x86上这是一个固定时间的操作,时间复杂度为O(1),空间复杂度为O(1)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值