【算法】【动态规划篇】第5节:剪绳子问题

15 篇文章 1 订阅
14 篇文章 2 订阅

本期任务:介绍算法中关于动态规划思想的几个经典问题

【算法】【动态规划篇】第1节:0-1背包问题

【算法】【动态规划篇】第2节:数字矩阵问题

【算法】【动态规划篇】第3节:数字三角形问题

【算法】【动态规划篇】第4节:硬币找零问题

【算法】【动态规划篇】第5节:剪绳子问题

【算法】【动态规划篇】第6节:最低票价问题

【算法】【动态规划篇】第7节:最长子串问题

【算法】【动态规划篇】第8节:最大正方形问题

【算法】【动态规划篇】第9节:乘积最大子数组问题

【算法】【动态规划篇】第10节:最长连续序列问题


一、问题描述

"""
    剪绳子问题
        给你一根长度为N的绳子,请把绳子剪成M段(m,n都是整数),每段绳子的
        长度记为k[0],k[1],k[2]…. 请问如何剪绳子使得k[0],k[1],k[2]
        的乘积最大
        例如 绳子长度8 最大乘积18 = 2*3*3

        输入:n=8
        输出:18

    """

二、算法思路

本题的解法与硬币找零问题大同小异,可以阅读【算法】【动态规划篇】第4节:硬币找零问题,加深对此类问题的理解。

1. 策略选择

一个模型:

  • 剪绳子问题是典型的“多阶段决策求最优解”问题,每一次决策有最多有n种选择,直到目标值n为0时结束;最优解是各段长度乘积的最大值。

三个特征:

  • 重复子问题:

    • 不同的决策序列,到达某个相同的阶段时,可能会产生重复的状态。
    • 本题中,前两段长度分别为 ( 2 , 2 ) (2,2) (2,2) ( 1 , 3 ) (1,3) (1,3)都能进入目标值为4的状态
  • 无后效性:

    • 前面阶段的状态确定之后,不会被后面阶段的决策所改变。一般而言,满足多阶段决策最优解模型的问题都满足无后效性,特例情况,如八皇后问题解数独问题等。
  • 最优子结构:

    • 后面阶段的状态可以通过前面阶段的状态推导出。
    • 本题中,目标值为8的状态可以由目标值为1-7的状态来推导。

综上所述,本问题满足一个模型、三个特征,所以可以使用动态规划来求解。
当然,凡是能用动态规划解决的问题,都可以用回溯思想来暴力求解,具体实现代码在文末给出,更多关于回溯思想的应用,可以参照:【算法】【回溯篇】第7节:0-1背包问题


三、Python代码实现

1. 动态规划解法

class Solution():

    def dp(self, n):
        """
        使用动态规划求解剪绳子问题,思路:维护一个长为n的数组,自底向上求解最优值!!!
        :param n: 绳子长度
        :return: 最大乘积值
        """
        if n < 2:  # 长度为1,无法剪
            return 0
        if n == 2:  # 长度为2,只能剪成1*1
            return 1
        if n == 3:  # 长度为3,剪成2*1 > 1*1*1
            return 2

        res = [-1] * (n + 1)  # 初始化数组
        res[0] = 1  # 约定f(0)=1

        for i in range(1, n + 1):  # 依次计算f(1)--f(8)
            res[i] = max([(j + 1) * res[i - j - 1] for j in range(i)])

        return res[-1]


def main():
    n = 8
    client = Solution()
    print(client.dp(n))


if __name__ == '__main__':
    main()

运行结果:

18

2. 回溯解法

class Solution():

    def trackback(self, n):
        """
        使用回溯法求解剪绳子问题
        :param n: 绳子长度
        :return: 最大乘积值
        """
        if n < 2:  # 长度为1,无法剪
            return 0
        if n == 2:  # 长度为2,只能剪成1*1
            return 1
        if n == 3:  # 长度为3,剪成2*1 > 1*1*1
            return 2

        self.max_v = n  # n大于0,所以初值设为n
        self.helper(n, 1)  # 回溯暴力求解

        return self.max_v

    def helper(self, n, v):
        """一次遍历完所有可能"""
        if n == 0:  # 当绳长为0时进行结算
            if self.max_v < v:
                self.max_v = v
            return

        for i in range(1, n + 1):  # 每次分段的长度介于[1, n]
            self.helper(n - i, v * i)


def main():
    n = 8
    client = Solution()
    print(client.trackback(n))


if __name__ == '__main__':
    main()

运行结果:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值