golang刷leetcode 技巧(14)剪绳子(I,II)整数拆分

2022-08-02 18:40:22 浏览数 (1)

剪绳子 I

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m] 。请问 k[0]*k[1]*...*k[m] 可能的最大乘积是多少?例如,当绳子的长度是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

解题思路

1,可以用动态规划解,用dp[i]表示长度为i的绳子的最大值

2,如果从长度为i的绳子上拆出一段j,这时候 积为j*dp[i-j]

3,dp[i]的值就为j取值从1到i-1 时候积的值,以及(i-j)*j 中最大值

4,状态转移方程为dp[i]=max(dp[i-j]*j,dp[i],(i-j)*j

代码实现

代码语言:javascript复制
func cuttingRope(n int) int {
    dp:=make([]int,n 1)
    dp[1]=1
    for i:=2;i<=n;i  {
        m:=0
        for j:=i-1;j>0;j--{
                m=max(dp[i-j]*j,m,(i-j)*j)
        }
        dp[i]=m
    }
    return dp[n]
}

func max(a,b,c int)int{
    if a>=b && a>=c{
        return a
    }

    if b>=c && b>=a{
        return b
    }
    return c
}
/*
我们考虑最后一步的情况,即最后剪的一下,会把绳子分为两部分,且两部分的结果互不影响

定义 dp[i] 表示长度i的绳子能得到的最大乘积

则 dp[i] 等于 在绳子区间[0, i)之间剪开的两部分乘积最大值

如果剪开位置为k,则区间分为[0, k)和[k, i)两部分

第一部分长度为k, 第二部分长度为i-k

第二部分存在剪和不剪两种情况,剪的时候值为dp[i-k],不剪的时候取(i-k)

于是得到状态转换方程:

dp[i] = max{ k * dp[i-k], k * (i-k)} (2<=k<=i)

class Solution {
public:
    int integerBreak(int n) {
        vector<int> dp(n 1, 0);
        dp[1] = 1;
        dp[2] = 1;
        for (int i = 3; i<=n; i  ){
            for (int k = 2; k <= i-1; k  ){
                dp[i] = max(dp[i], max(k*(i-k), k*dp[i-k]));
            }
        }
        return dp[n];
    }
};
*/
剪绳子 II

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m] 。请问 k[0]*k[1]*...*k[m] 可能的最大乘积是多少?例如,当绳子的长度是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

解题思路:

1,由于取模了,所以不能用动态规划来解了

2,我们可以找规律来解

3,我们可以参照整数拆分来求解,在整数拆分基础上取模

代码实现

代码语言:javascript复制
func cuttingRope(n int) int {
 if n<=3{
     return 1*(n-1)
 }
 res:=1
  if n%3==2{
      res*=2
      n-=2
  }
  if n%3==1{
      res*=4
      n-=4
  }
  for i:=0;i<n/3;i  {
      res=res*300000007
  }
  return res
}
整数拆分

给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。返回你可以获得的最大乘积。

示例 1:

输入: 2

输出: 1

解释: 2 = 1 1, 1 × 1 = 1。

示例 2:

输入: 10

输出: 36

解释: 10 = 3 3 4, 3 × 3 × 4 = 36。

说明: 你可以假设 n 不小于 2 且不大于 58。

解题思路:

1,当绳子长度为1,2,3 的时候只能拆成两段1,n-1

2,当拆分因子中没有1的时候总有 积>=和

3,因此我们可以把绳子拆出更多3,没法拆出3的时候拆成2

A,当n%3==2时候,pow(3,n/3)*2

B,当n%3==1时候,pow(3,n/3-1)*4

C,当n%3==0时候,pow(3,n/3)

首先,任何一个数字 n,都可以被分为有限个小数字之和。

根据常理:一般这 M 个数字的乘积要大于原数字 n。

其次,所有数字n 都可以通过对一个因子 xx 求整数部分 a(a = n // x) 和余数部分 b( b = n % x);

即得出数字 n 由 a 个 x 和 1 个 b 相加而成。

问题转化:是否有优先级最高的因子 x存在?若有,我们就可以把问题转化为求 x^a * b 这个表达式的最大值。

例如:2 = 1 1,1 * 1 < 2,因此 2 比 1 1 更优;

例如:3 = 1 2,1 * 2 < 3,因此 3 比 1 和 2 更优;

例如:4 = 2 2,2 * 2 = 4,因此可以认为 4 与 2 等价,因此见到 4 就拆分;

例如:5 = 2 3;因为每个 5都可以拆分为 2 3,而 2 * 3 = 6 > 5,因此见到 5 就拆分。

例如:6 = 3 3 = 2 2 2;因为 3 * 3 > 2 * 2 * 2 > 6。因此见到 66 就拆分,并且 3是比 2更优的因子。

易推出:大数字都可以被拆分为多个小因子,以获取更大的乘积,只有 2和 3 不需要拆分。

n 拆分 乘积

2 1 1 1 不拆分,2 比 1 1 更优

3 1 2 2 不拆分,3 比 1 2 更优

4 2 2 4 拆分,2 与 4 等价

5 2 3 6 拆分

6 3 3 9 拆分,3 3 比 2 2 2 更优

7 2 2 3 12 拆分,但不能拆成 1 3 3

观察以上枚举,我们可以列出以下贪心法则:

第一优先级:3;把数字 n 拆成尽可能多的 3 之和;

特殊情况:拆完后,如果余数是 1;则应把最后的 3 1 替换为 2 2,因为后者乘积更大;

第二优先级:2;留下的余数如果是 2,则保留,不再拆为 1 1。

算法流程:

当 n <= 3 时,按照贪心规则应直接保留原数字,但由于题目要求必须拆分,因此必须拆出一个 1,即直接返回 n - 1;

求 n除以 3 的整数部分 a和余数部分 b;

当 b == 0时,直接返回 3^a;

当 b == 1 时,要将一个 1 3 转换为 2 2,此时返回 3^{a-1} * 2*2

当b==2 时,返回3^{a}*2

代码实现

代码语言:javascript复制
func integerBreak(n int) int {
  if n<=3{
      return 1*(n-1)
  }
  res:=1
  if n%3==1{
     res*=4
     n-=4
  }

  if n%3==2{
      res*=2
      n-=2
  }
  for i:=0;i<n/3;i  {
      res*=3
  }   
  return res
}
dp

0 人点赞