剪绳子
关键词:位运算
题目描述 给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]*k[1]*…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
示例 1:
1 2 3 输入: 2 输出: 1 解释: 2 = 1 + 1 , 1 × 1 = 1
示例2:
1 2 3 输入: 10 输出: 36 解释: 10 = 3 + 3 + 4 , 3 × 3 × 4 = 36
思路 思路一:
暴力递归(超出时间限制)
我们往往会在头脑中形成一种很直观的暴力解法,就是列举出所有的情况,找到乘积最大的那个解。
设 F(n)为长度为 n的绳子可以得到的最大乘积,对于每一个 F(n)),可以得到如下分解:
从上图看出我们可以把求解 F(n)的问题分解成求解 F(n−1) 的问题,以此类推,直到求解到 F(2)时,F(2)=1,递推回去,问题就得到了解决。这用到的就是分治的思想。
分治思想的解决方法往往是递归,注意到我们每次将一段绳子剪成两段时,剩下的部分可以继续剪,也可以不剪, 因此我们得到了递归函数
F(n)=max(i×(n−i),i×F(n−i)),i=1,2,…,n−2。
复杂度分析
时间复杂度:O(N^2),对于每一个 i调用一次递归,递归的时间复杂度为 O(N),故时间复杂度为 O(N^2)。
空间复杂度:O(N^2)。
思路二:
记忆化技术(自顶向下)
上述暴力解法会超时,但是很多进阶解法往往是暴力解法的优化。注意到上述代码中超时的原因主要是因为重复计算了 F(n),为了避免重复计算可以使用 记忆化(memoization)技术。
记忆化技术的代码中经常需要建立函数 memoize 辅助实现。我们使用数组 f 来保存长度为 i时的最大长度 f[i] ,最后返回 f[n]即可。
复杂度分析
时间复杂度:O(N^2),原因同上,时间复杂度仍然为 O(N^2),只是采用记忆化减少部分计算时间
空间复杂度:O(N),使用了数组f。
思路三:
动态规划(自底向上):(本地成功,力扣失败,报错超了int 的范围)
同样地,我们也可以使用动态规划,从已知值 F(2)逐步迭代到目标值 F(n),它是一种自底向上的方法。
算法
建立一维动态数组 dp:
边界条件:dp[1] = dp[2] = 1,表示长度为 2 的绳子最大乘积为 1;
状态转移方程:dp[i] = max(dp[i], j * max(i - j, dp[i - j])),可以这样理解:
复杂度分析
时间复杂度:O(N^2).
空间复杂度:O(N)。
思路四:
动态规划优化解法
算法
我们发现任何大于 3 的数都可以拆分为数字 1,2,3 的和,且它们对 3 的余数总是 0,1,2,因此我们可以仅用 dp[0],dp[1],dp[2] 表示所有大于 3 的值,这样空间复杂度可降到 O(1)。
这样重复使用 dp 数组,只须一趟遍历即可完成,可使时间复杂度降到 O(N)。
复杂度分析
时间复杂度:O(N).
空间复杂度:O(1)。
思路五:
数学方法 (在面试时尽量按照常规思路去解)
贪心法则:尽可能分解出多的 3,3 的个数为 a,得到余数 b 可能为 0,1,2:
b = 0,返回 3^a;
b = 1,我们将末尾的 3+1 分解成 2×2,因此返回 3 < 4
b = 2,返回 3^a×2;代码 思路一:
1 2 3 4 5 6 7 8 9 10 int cuttingRope (int n) { if (n == 2 ) return 1 ; int res = -1 ; for (int i = 1 ; i <= n; i++) { res = max (res, max (i * cuttingRope(n - i), i * (n - i))); } return res; }
思路二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { public : int f[59 ] = {0 }; int memoize (int n) { if (n == 2 ) return 1 ; if (f[n] != 0 ) return f[n]; int res = -1 ; for (int i = 1 ; i <= n; i++) { res = max (res, max (i * (n - i), i * memoize(n - i))); } f[n] = res; return res; } int cuttingRope (int n) { if (n == 2 ) return 1 ; int res = -1 ; for (int i = 1 ; i <= n; i++) { res = memoize(i); } return res; } };
思路三:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int cuttingRope (int n) { if (n < 2 || n > 58 ) return 0 ; if (n == 2 ) return 1 ; int dp[n + 1 ]; dp[0 ] = 1 ; dp[1 ] = 1 ; dp[2 ] = 1 ; for (int i = 3 ;i <= n; i++){ for (int j = 1 ; j <= i; j++){ dp[i] = max (dp[i], j * max (i - j, dp[i - j])); } } return dp[n]; }
思路四:
1 2 3 4 5 6 7 8 9 10 11 12 int cuttingRope (int n) { if (n < 2 || n > 58 ) return 0 ; if (n == 2 ) return 1 ; int dp[3 ] = {0 , 1 , 1 }; for (int i = 3 ; i <= n; i++){ dp[i % 3 ] = max(max(max(dp[(i - 1 ) % 3 ], i - 1 ), 2 * max(dp[(i - 2 ) % 3 ], i - 2 )), 3 * max(dp[(i - 3 ) % 3 ], i - 3 )); } return dp[n % 3 ]; }
思路五:
1 2 3 4 5 6 7 8 9 10 int cuttingRope (int n) { if (n < 2 || n > 58 ) return 0 ; if (n < 4 ) return n - 1 ; int a = n / 3 ; int b = n % 3 ; if (b == 0 ) return pow (3 , a); else if (b == 1 ) return pow (3 , a - 1 ) * 4 ; else if (b == 2 ) return pow (3 , a) * 2 ; }
知识点
max函数
vector的用法
c++数组