目录
刷题日期:20:4219 星期二2021年3月23日
个人刷题记录,代码收集,来源皆为leetcode
经过多方讨论和请教,现在打算往Java方向发力
主要答题语言为Java
题目:
剑指 Offer 14- I. 剪绳子
难度中等197
给你一根长度为 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
题目分析
根据提示绳子的长度在[2,58]。
这题在书中属于 动态规划与贪婪算法 部分的内容,因此肯定会用到相关方法。
在应用动态规划解决问题的时候,我们总是从解决最小问题开始,并把已经解决的子问题的最优解存储下来(一维或者二维数组),并把子问题的最优解组合起来逐步解决大的问题。
O(n^2)时间复杂度和O(n)空间复杂度
而贪婪算法应用时都需要用数学方法来证明贪婪选择是正确的。
O(n)时间复杂度和O(1)空间复杂度
初始解答:
书上的动态规划算法虽然复杂,但是也是分析问题的一种方法,因此尝试实现。
放弃实现书上得了,写的太长了,评论中也有用动态规划算法的,明显就没有那么多。两种方法都来自官方精选,在方法一中。
class Solution {
public int cuttingRope(int n) {
int[] dp = new int[n+1]; //定义数组辅助
dp[2] = 1; //这是当绳子为小量,1、2、3时的解答,直接赋值
// 小于2的话至少要求切一刀都没法满足,所以提示里就没有涉及
//dp[3] = 2;
for(int i = 3; i < n+1; i++) { //少定义一步的话就从3开始,书里是4
//第一层循环递增,所以计算顺序自下而上
for(int j = 2; j < i; j++) {
//加上循环就是i以下所有可能性的比较。
//因为这里使用了Math.max方法,所以不用像书里那样比较
//j即是将n从第j处分割
//Math.max(j*(i-j),j*dp[i-j]) 中 j*(i-j)指的是分割一次后的乘积;j*dp[i-j]指
//分割一次后,剩余部分继续分割后的最大乘积,前面已经求解过,所以只需要取结果
//下面综合起来就是,但j取不同时,与前一次j取值后的dp[i]比较,取最大值,直到j遍历完
dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]));
//这里比较的是通用的当已知长度为j的最优,求长度为i的最优,要比较的比书中多了一项
}
}
return dp[n];
}
}
执行结果: 通过
显示详情
执行用时:1 ms, 在所有 Java 提交中击败了44.82%的用户
内存消耗:35.2 MB, 在所有 Java 提交中击败了72.33%的用户
试着改一改,看有没有什么能简化的,结论都在代码里了,不得不佩服大佬的思路啊
class Solution {
public int cuttingRope(int n) {
int[] dp = new int[n+1]; //定义数组辅助
dp[2] = 1; //这是当绳子为小量,1、2、3时的解答,直接赋值
// 小于2的话至少要求切一刀都没法满足,所以提示里就没有涉及
// dp[3] = 2; //这一句不能加,否则当n=2时,就越界了。
for(int i = 3; i < n+1; i++) { //少定义一步的话就从3开始,书里是4
//比较项也必须是n+1,否则当n为10的时候,输出为0
//第一层循环递增,所以计算顺序自下而上
for(int j = 2; j < i; j++) {
//加上循环就是i以下所有可能性的比较。
//因为这里使用了Math.max方法,所以不用像书里那样比较
//j即是将n从第j处分割
//Math.max(j*(i-j),j*dp[i-j]) 中 j*(i-j)指的是分割一次后的乘积;j*dp[i-j]指
//分割一次后,剩余部分继续分割后的最大乘积,前面已经求解过,所以只需要取结果
//下面综合起来就是,但j取不同时,与前一次j取值后的dp[i]比较,取最大值,直到j遍历完
dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]));
// dp[i] = Math.max(dp[i], (j * (i - j))); //这里同样不能少比较,否则n=10时计算错误
// dp[i] = Math.max(dp[i], j * (i - j), j * dp[i - j]); //max只能比较两项,所以必须那样写
//这里比较的是通用的当已知长度为j的最优,求长度为i的最优,要比较的比书中多了一项
}
}
return dp[n];
}
}
尝试贪婪算法,即方法二
class Solution {
public int cuttingRope(int n) {
if(n < 4) return n-1; //规律啊,一条代码就顶书里三条
int res = 1; //初始化,为0就没得算了
while(n > 4) { //即从5开始
res *= 3; //贪婪的规则就是能减3就减3,直接按3来算面积
n -= 3; //剩下的再进行判断,一轮减一次
}
return res * n; //这里的n已经是1、2、3其中的一个了
//如果n=4或者7、10这种,4的时候直接乘4就好,因为最大2*2=4
//条件设置的巧妙,所以把4的情况直接包含进来了,循环和判断中才没有交集
}
}
执行结果:
通过
显示详情
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:35.3 MB, 在所有 Java 提交中击败了45.19%的用户
其他方法基本都是这两种的变体。
学习他人:
方法一:
思路一:动态规划
作者:edelweisskoko
链接:https://leetcode-cn.com/problems/jian-sheng-zi-lcof/solution/jian-zhi-offer-14-i-jian-sheng-zi-huan-s-xopj/
来源:力扣(LeetCode)
class Solution {
public int cuttingRope(int n) {
int[] dp = new int[n + 1];
dp[2] = 1;
for(int i = 3; i < n + 1; i++){
for(int j = 2; j < i; j++){
dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]));
}
}
return dp[n];
}
}
方法二
思路二:贪心 链接同上
class Solution {
public int cuttingRope(int n) {
if(n < 4){
return n - 1;
}
int res = 1;
while(n > 4){
res *= 3;
n -= 3;
}
return res * n;
}
}
题目:
剑指 Offer 14- II. 剪绳子 II
难度中等84
给你一根长度为 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。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
2 <= n <= 1000
题目分析
加上了后面的取模,绳子也变长了,正好检验自己的学习效果
此题与 面试题14- I. 剪绳子 主体等价,唯一不同在于本题目涉及 “大数越界情况下的求余问题” 。
建议先做上一道题,在此基础上再研究此题目的大数求余方法。
作者:jyd
链接:https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/solution/mian-shi-ti-14-ii-jian-sheng-zi-iitan-xin-er-fen-f/
来源:力扣(LeetCode)
初始解答:
先使用贪婪算法尝试
class Solution {
public int cuttingRope(int n) {
if(n < 4) return n-1;
int res = 1;
while(n > 4) {
res *= 3;
n -= 3;
}
if((res * n) > 1000000007) {
return(res * n) - 1000000007;
}
return res * n;
}
}
取模也实现了,但是在第120项报错了。
参考了方法一,没能通过是因为我定义的res是int,不够存储那么大的数。
class Solution {
public int cuttingRope(int n) {
if(n < 4) return n-1;
long res = 1;
while(n > 4) {
res *= 3;
res = res % 1000000007;
n -= 3;
}
return (int)(res * n % 1000000007);
}
}
执行结果:通过
显示详情
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:35.5 MB, 在所有 Java 提交中击败了16.25%的用户
这一题已经不能用动态规划了,取余之后max函数就不能用来比大小了。
学习他人:
方法一:
pipiL1 2020-02-12
java
class Solution {
public int cuttingRope(int n) {
if(n == 2)
return 1;
if(n == 3)
return 2;
long res = 1;
while(n > 4){
res *= 3;
res = res % 1000000007;
n -= 3;
}
return (int)(res * n % 1000000007);
}
}
方法二
2020-08-25
写一下自己的思路:
- 感觉对于大数来说,需要注意的点有很多;
- 一个是进行强转的时候,需要对最终的结果进行强转,也就是对取余以后的结果进行强转;
- 还有一个快速幂的技巧,也是需要搞清楚的。
class Solution {
public int cuttingRope(int n) {
if (n <= 3) {
return n - 1;
}
int a = n % 3;
int b = n / 3;
if (a == 0) {
return (int) pow(3, b);
} else if (a == 1) {
return (int) ((pow(3, b - 1) * 4) % 1000000007);
} else {
return (int) ((pow(3, b) * 2) % 1000000007);
}
}
private long pow(long base, int num){
long res = 1;
while(num > 0){
if((num & 1) == 1){
res *= base;
res %= 1000000007 ;
}
base *= base;
base %= 1000000007;
num >>= 1;
}
return res;
}
}
方法三
wulinL3 (编辑过)2020-09-09
如果你看不懂快速幂求余,那就看看这个吧
class Solution {
public int cuttingRope(int n) {
if(n <= 3)
return n - 1;
int b = n % 3, p = 1000000007;
long ret = 1;
int lineNums=n/3; //线段被我们分成以3为大小的小线段个数
for(int i=1;i<lineNums;i++) //从第一段线段开始验算,3的ret次方是否越界。注意是验算lineNums-1次。
ret = 3*ret % p;
if(b == 0)
return (int)(ret * 3 % p); //刚好被3整数的,要算上前一段
if(b == 1)
return (int)(ret * 4 % p); //被3整数余1的,要算上前一段
return (int)(ret * 6 % p); //被3整数余2的,要算上前一段
}
}
总结
以上就是本题的内容和学习过程了,本题已经相当具有难度了,还是多学多练,争取早日自己能够摸索出这类题的答案,培养自己的解题逻辑和习惯。
欢迎讨论,共同进步。