汇总:剑指offer算法合集
题目
给你一根长度为 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:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
2 <= n <= 58
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/jian-sheng-zi-lcof
解题思路
-
这道题首先想到的就是动态规划,用dp[i]表示长度为i的绳子剪成m端后长度的最大乘积,如果当前已经减了一段长度为j,那么剩下那段长度如果不再剪的话总乘积就是
j*(i - j)
,如果继续剪的话,那总乘积就是j*dp[i-j]
,而长度i的最大乘积就是取这两种情况的最大值,因此转移方程为:
d p [ i ] = m a x ( j ∗ ( i − j ) , j ∗ d p [ i − j ] ) dp[i] = max( j * (i-j), j * dp[i - j]) dp[i]=max(j∗(i−j),j∗dp[i−j]) -
但今天我们用另一种思路解答本题,如果对这些长度多次进行尝试,我们可以发现,不管剪多少次,当剪出来的长度接近总长度的平均分配(除全剪为1外),值就越大,比如对于10来说,如果剪3次,均衡点剪成
3*3*4 = 36
,会比不均衡剪成2*3*5=30
或2*4*4=32
这些要大,因此对于每一次剪数,我们都计算它平均分配情况下的乘积,就是这种剪数的最大乘积,然后循环比较每一种剪数的最大乘积,得出最终的最大乘积 -
如果推导一番,其实会发现尽可能均分为3是最优的,但这里针对面试,也就没必要追究这些了,能用上面的方法解出来已经不错了
复杂度分析
循环尝试n-2中剪法,时间复杂度为O(n)
,然后在每种剪法中根据剪的段数循环计算乘积,时间复杂度为O(n)
,因此总的时间复杂度为O(n²)
,只用到了常数级变量,空间复杂度为O(1)
代码实现
class Solution {
public int cuttingRope(int n) {
//当n=2时,只有一种剪法,直接返回其乘积即可
if (n == 2) return 1;
//max是最终最大乘积
//part是当前剪的每一段的平均长度
//pow是当前剪数下的最大乘积
//remain是无法平均分配剩余的长度
//例如10平均分2段,则part = 10/2 = 5,remain = 0
//10平均分3段,则part = 10/3 = 3,remain = 1
int max = 0, part, pow, remain;
//i代表当前剪的段数目,剪1次就会分2段,因此从2开始
//长度为n的绳子最多可以剪n-1次变成n段均分长度为1的段
//但由于均分全为1的长度乘积只有1,所以可以排除
//因此最多剪n-2次分成n-1段即可
for (int i = 2; i < n; i++) {
//乘积初始值为1,如果是0的话相乘就都是0了
pow = 1;
//长度均分,也就是长度除以剪数
part = n / i;
//长度均分后剩余的长度
remain = n % i;
//把这些均分的长度相乘
for (int j = 0; j < i; j++) {
//如果有剩余的长度
if (remain > 0) {
//就把它平均分配给均分后的那些段,每个分配1
pow *= part + 1;
remain--;
} else {
//由于剩余的长度必定没有均分的长度长
//因此必定在后面某一次循环后剩余长度分配完了
//就乘以均分的长度即可
pow *= part;
}
}
//比较每种剪数的最大乘积,取最终的最大乘积
max = Math.max(max, pow);
}
return max;
}
}