面试题7---剪绳子详解

1.题目

给你一根长度为n的绳子,请把绳子剪成m段(m,n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],…k[n]。请问k[0]乘k[1]乘…k[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

2.涉及知识点

动态规划

基本思想:动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治算法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解决这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。具体的动态规划算法多种多样,但他们具有相同的填表格式。(以上说明来自百度百科)。
上文翻译:当你遇到一个问题,并想要得到它的解1,但这个问题可以拆成许多小问题,你想要得到解1,就必须得到小问题的解2,解3。同理,这些小问题可以拆成许多小小问题,你想要得到解2,解3,就必须得到小小问题的解4,解5,解6…。而且除了最后的一层,其它所有获取解的方法相同。因为当你获取一个解之后,下次再利用时就不需要重复计算,只需将这个解存储起来,下次直接调用,可大大减少时间复杂度。
这里通过一个实例来加深对动态规划的理解,经典的数字三角形问题
在这里插入图片描述题目描述:在上面的数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或右下走。只需要求出这个最大和即可,不必给出具体路径。
分析解答:为了方便代码书写,我们可以将上述三角形用二维数组存储为如下形式。
在这里插入图片描述可以看出每走第n行第m列时有两种后续:向下或者向右下,只有最后一行可以直接确定,当做边界条件,所以我们自然而然想到递归求解。
解题思路

  • 用二维数组存放数字三角形,D(r,j):第r行第j个数字(r,j从1开始计算)。MaxSum(i,j):从D(r,j)到底边的各条路径中,最佳路径的数字之和。问题:求MaxSum(1,1)。
  • 典型的递归问题,从D(r,j)出发,下一步只能走D(r+1,j)或者D(r+1,j+1)。故对于N行的三角形:
if(r==N)
  MaxSu(r,j)=D(r,j);
else
  MaxSum(r,j)=Max{MaxSum(r+1,j),MaxSum(r+1,j+1)}+D(r,j)

完整代码

#include<iostream>
#include<algorithm>
#define MAX 101
using namspace std;
int D[MAX][MAX];
int n;
int MaxSum(int i,int j)
{
  if(i==n)
    return D[i][j];
  int x=MaxSum(i+1,j);
  int y=MaxSum(i+1,j+1);
  return max(x,y)+D[i][j];
}
int main()
{
  int i,j;
  cin>>n;
  for(i=1;i<=n;i++)
  for(j=1;j<=i;j++)
  cin>>D[i][j];
  cout<<MaxSum(1,1)<<endl;
}

其实仔细观察,上述方法虽然看起来简单,但时间复杂度是非常大的,对很多位置都进行了不必要的计算,既然是不必要,那么我们就有方法去舍去那些不必要的计算,用一个辅助空间来保存已经计算过的数据即可,下面对上述代码加以改进。

#include<iostream>
#include<algorithm>
#define MAX 101
using namspace std;
int D[MAX][MAX];
int maxSum[MAX][MAX];
int n;
int MaxSum(int i,int j)
{
 if(maxSum[i][j]!=-1)
   return maxSum[i][j];
  if(i==n)
    return D[i][j];
  int x=MaxSum(i+1,j);
  int y=MaxSum(i+1,j+1);
  return max(x,y)+D[i][j];
}
int main()
{
  int i,j;
  cin>>n;
  for(i=1;i<=n;i++)
  for(j=1;j<=i;j++)
  {
    cin>>D[i][j];
    maxSum[i][j]=-1;
    }
  cout<<MaxSum(1,1)<<endl;
}

既然可以用递归的思想写出来,我们都知道,递归一般情况下是可以转化为递推的,请看下面代码。

#include<iostream>
#include<algorithm>
#define MAX 101
using namspace std;
int D[MAX][MAX];
int maxSum[MAX][MAX];
int n;
int main()
{
  int i,j;
  cin>>n;
  for(i=1;i<=n;i++)
  for(j=1;j<=i;j++)
    cin>>D[i][j];
for(i=1;i<=n;i++)
   maxSum[n][i]=D[n][i];
for(i=n-1;i>=1;i--)
{
  for(j=1;j<=i;j++)
    maxSum[i][j]=max(maxSum[i+1][j],maxSum[i+1][j+1])+D[i][j];
}
cout<<maxSum[1][1]<<endl;
}

这就是标准的动态规划解题过程。

3.就题论题

首先定义函数f(n)为把长度为n的绳子剪成若干段后各段长度乘积的最大值。在剪第一刀的时候,我们有n-1种可能的选择,也就是剪出来的第一段绳子的可能长度分别为1,2…,n-1.因此f(n)=max(f(i)乘f(n-i)),其中0<i<n。这是一个从上至下的递归公式。由于递归会有很多重复的子问题,从而有大量不必要的重复计算。一个更好的办法是按照从下而上的顺序计算,也就是说我们先得到f(2),f(3),再得到f(4),f(5),直到得到f(n)。
当绳子的长度为2时,只可能剪成长度为1的两段,因此f(2)等于1,当绳子的长度为3时,可能把绳子剪成长度分别为1和2的两段或者长度都为1的三段,由于1乘2>1乘1乘1,因此f(3)=2。下面是这一思路对应的参考代码。

int maxProductAfterCutting_solution(int length)
{
  if(length<2)
     return 0;
  if(length==2)
    return 1;
  if(length==3)
    return 2;
  int* products=new int[length+1];
  products[0]=0;
  products[1]=1;
  products[2]=2;
  products[3]=3;
  int max=0;
  for(int i=4;i<=length;i++)
  {
    max=0;
    for(int j=1;j<=i/2;j++)
    {
      int product=products[j]*productis[i-j];
      if(max<product)
        max=product;
        products[i]=max;
    }
  }
  max=products[length];
  delete[] products;
  return max;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赶路的苟狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值