剑指 Offer 14- I. 剪绳子 - 力扣(LeetCode) (leetcode-cn.com)
目录
把【长度>=4】的绳子任意切成【长度>1】两段,那么这两段长度之积一定不小于原长度。
对于任意一段长度超过4的绳子,我们均可以把它切割成长度在4以内的小段来增大它各段的乘积
如果把一段绳子切割成小段,只要其中存在长度超过4的小段,那么当前的切割方式产生的乘积一定不是最大的。只有把它所有的小段的长度切割到4以内的时候,才有可能得到最大的乘积。
如果绳子的长度在5以上,切出长度为3的小段一定比长度为2的小段乘积得到的增益更大。
贪心
运行结果
代码
时间复杂度:O(1)
空间复杂度:O(1)
class Solution {
public:
int cuttingRope(int n) {
if (n == 2) return 1; //处理两个特殊情况
else if (n == 3) return 2;
int x = n / 3, r = n % 3;
if (!r) return pow(3, x);
else if (r & 1) return pow(3, x - 1) * 4;
else return pow(3, x) * 2;
}
};
分析
对于一整段绳子,我们可以通过切割的方式缩减它的长度,增大它的乘积
要把它切割成多少份才能得到最大的乘积呢?
我们从较小的数开始找规律
绳子长度为1,不切割
绳子长度为2,切割后可得1 * 1,结果变小了
绳子长度为3,切割后可得1 * 2或者1 * 1 * 1结果变小了
绳子长度为4,切割后可以有2*2,结果没有增大
绳子长度为5,切割后可以得到2*3 = 6 > 5结果增大了
绳子长度为6,切割一次后可以得到2*4,3*3,均大于6
绳子长度为7,切割一次后可得得到4*3,2*2*2*1,结果均大于7
……
我们从小到大开始考虑,可以逐渐发现重复子问题(动态规划解法不难理解,已经放到了最后)
我们还可以发现,把长度较长的绳子切割成小段,似乎只要每段长度>1,各段长度之积就会大于原长度。可是这里的较长具体是多长?这个发现是否正确?我们接下来就来证明这个结论。
把【长度>=4】的绳子任意切成【长度>1】两段,那么这两段长度之积一定不小于原长度。
我们把较长的一段绳子切成两段,一段长度是l,另一段长度是n-l。
根据对称性,我们不妨令n-l >= l
该结论可以表示为:
l * (n-l) >= n
上式等价于
n/l > l/(l-1)
左边:根据条件 n-l >= l,我们有: n / l >= 2
右边:l/(l-1) = 1 + 1/(l-1) 随着l的增大而减小,当l=2时取到最大值2
因此:
n/l >= 2 >= l/(l-1)
所以该结论成立。并且只有在l = n/2和l=2同时满足,即n==4的情况下成立。
此结论说明:
对于任意一段长度超过4的绳子,我们均可以把它切割成长度在4以内的小段来增大它各段的乘积
也就是说
如果把一段绳子切割成小段,只要其中存在长度超过4的小段,那么当前的切割方式产生的乘积一定不是最大的。只有把它所有的小段的长度切割到4以内的时候,才有可能得到最大的乘积。
小于4的正整数只有1,2,3
显然长度是1的小段并不会增大各段的乘积,因此可供考虑的就只有2和3了。那么每小段长度是2好呢,还是3好呢?
不难发现:
当n = 5时,(n-3)*3 == (n-2)*2
当n > 5时,(n-3)*3 > (n-2)*2
因此:
如果绳子的长度在5以上,切出长度为3的小段一定比长度为2的小段乘积得到的增益更大。
如果绳子的长度为4,由于2*2 > 3*1,因此切割成2*2更划算
至此,我们的方案就可以确定了:
只要绳子长度>4,就一直切出长度为3的小段,直到长度<=4
最后剩下的长度可能为2,3,4
而又因为4 == 2 * 2,也就是说切割之后乘积不变,那我们大可不必切割,因为最终要求的只是最大的乘积,不用考虑切割次数。
因此不论最后一小段长度是多少,都可以直接乘入我们的乘积里。
由于每次均切出长度为3的小段,存在大量重复操作,那么我们不必一个一个的切割,可以借用数学方法直接一步到位。
最终方案
任意一个n都可以写成 3x + r的形式,其中x >= 0,r = 0, 1, 2
r = 0: 最后一段长度是3,乘上
r = 1: 最后一段长度不能是1,应该是4,从乘积里除掉一个3,然后乘上一个4
r = 2: 最后一段长度是2,乘上
动态规划
运行结果
代码
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
int cuttingRope(int n) {
if (n == 2) return 1;
if (n == 3) return 2;
vector<int> product(n + 1); //product[i]表示长度为i的绳子能贡献出的最大值
product[1] = 1;
product[2] = 2; //不经切割,最大能贡献2
product[3] = 3; //不经切割,最大能贡献3
for (int i = 4; i <= n; ++i) {
for (int j = 1; j <= i / 2; ++j) {
product[i] = max(product[i], product[j] * product[i - j]);
}
}
return product[n];
}
};