算法 64式 8、动态规划算法整理_第1部分_1到15题

1 算法思想

动态规划

1.1含义

把问题分解成多阶段或多个子问题,顺序求解各个子问题,最后一个子问题就是初始问题的解。

概念

阶段: 问题分成的顺序的几个环节。例如最长递增子序列中每个字符就是一个阶段。

状态: 描述问题当前状况的数字量。可以表示状态特征,例如最长递增子序列中dp[x]表示以x结尾的字符串的最长递增子序列长度,就是一个状态。

决策:从某阶段状态到下一阶段某状态的选择。例如数塔问题中取第i行第j个数有两种方案: 取第i-1行第j1个数或取第i-1行第j个数后再取第i行第j个数。

状态转移方程:数字量之间的递推关系。例如最长递增子序列中状态转移方程为:

F[i]={1, i=1

{max{1, F[j] + 1}, j < i && aj>=ai

 

1.2 性质

最优子结构: 问题最优解包含子问题的最优解。

无后效性:某阶段状态(数字量例如dp[x])确定后,就不受这个状态以后决策的影响。即以后不影响以前。

解释:

最优子结构: 数塔问题中,假设9->12->10->8->10是最优路径,那么12->10->8->1也是12到达最后一层的最大和

1.3适用

子问题重叠+无后效性+最优子结构的问题

为了解决子问题重叠的问题,可以采用备忘录法,用表格记录到已经计算过的状态值。

1.4通用解法

动态规划算法:

1 确定状态转移方程

2 初始化边界状态的值

3 设定循环来递推状态的值

4 返回目标状态的值

总结:

方程->边界->循环

 

1.5经典例题讲解

0-1背包问题

不同草药,采每一株需要一些时间,每一株有自己价值,如何让采到的价值最大。

输入:第一行有两个整数T(1<=T<=1000)和M(1<=M<=100),T代表总共能够用来采药的时间,M代表山洞里的草药数目。

接下来的M行,每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值

输出:在规定时间内,可以采到的草药的最大总价值

问题抽象:

有一个容量为V的背包,和一些物品。这些物品分别有两个属性,体积w和价值v,每种物品只有1个。

要求背包装下价值尽可能多的物品,求该最大价值,背包可以不被装满。

分析状态转移方程:

用dp[i][j]表示总体积不超过j的情况下,前i个物品能达到的最大价值。

边界状态:

dp[0][j] = 0,(0<=j<=V)

根据每个物品是否放入背包,每个状态有两个状态转移来源

若物品i放入背包,设其体积为w,价值为v,则有

dp[i][j]=dp[i-1][j-w] + v

不放入背包,则有

dp[i][j]=dp[i-1][j]

所以有:

dp[i][j]=max{ dp[i-1][j-w] + v, dp[i-1][j]}

注意: j-w的值不能为负数

//定义背包

typedef struct List

{

       int w;//体积

       int v;//价值

}List;

 

 

int max(int a,int b)

{

       return a > b ? a:b;

}

 

 

int main(int argc,char* argv[])

{

       int s;//总容积

       int n;//n行

       int i,j;

       while(EOF!=scanf("%d %d",&s,&n))

       {

              List list[101];

              //int dp[101][1001];

              int dp[1001];//优化

              for(i = 1 ; i <= n ; i++)

              {

                     scanf("%d %d",&list[i].w,&list[i].v);

              }

              //初始化边界状态的值

              for(i = 0 ; i <= s ; i++)

              {

                     //dp[0][i] = 0;

                     dp[i] = 0;

              }

 

        //设定循环来递推状态的值

              //对于时间足够的情况,状态来源是:dp[i][j]为两者之中的最大值

              for(i = 1 ; i <= n ; i++)

              {

                     for(j = s; j >= list[i].w ; j--)

                     {

                            //dp[i][j] = max(dp[i-1][j],dp[i-1][j-list[i].w] + list[i].v);

                            //优化:必须倒序更新每个dp[j]的值,j小于list[i].w的各dp[j]不做更新,保持原值,即等价与dp[i][j] = dp[i-1][j]

                            dp[j] = max(dp[j],dp[j-list[i].w] + list[i].v);

                     }

                     /*

                     for(j = list[i].w-1; j >= 0 ; j--)

                     {

                            dp[i][j] = dp[i-1][j];

                     }

                     */

              }

 

        //返回目标状态的值

              //printf("%d\n",dp[n][s]);

              printf("%d\n",dp[s]);

       }

       system("pause");

       getchar();

       return 0;

}

 

 

1.6动态规划与其他算法的区别

动态规划与贪心的区别:

贪心是在当前状态进行局部最优的选择,这种选择以来过去所做的选择。与贪心算法不同的是,动态规划分解的子问题往往不独立

动态规划与分治的联系:

基本思想都是将待求解问题分解成若干个子问题,先求解子问题,然后从子问题的解得到原问题的解。

 

1.7时间复杂度与空间复杂度

时间复杂度=状态数量*每次状态转移的时间复杂度

空间复杂度=申请的数组大小

 

 

2 动态规划系列

类别-编号

题目

遁去的1

1

N阶楼梯上楼问题

N阶楼梯上楼问题:一次可以走两阶或者一阶,问有多少种上楼方式(要求采用非递归)

输入:一个整数N(1<=N<90)

输出:输出阶数为N时的上楼方式的个数

输入:

4

输出:

5

计算机考研—机试指南

https://blog.csdn.net/qingyuanluofeng/article/details/47186333

思路:

当n>2时,最后一步的行走方式可能:1)走到N-1阶台阶,走一步形成,

上楼方式与原问题中到达n-1阶方式数量相同为F[n-1]。2)走到N-2阶台阶,

走两步形成,上楼方式与原问题中到达n-2阶台阶的方式数量相同,为F[n-2]

关键:

1 递推求解在于确定数列的递推关系

F[n]={n,n=1或2

     {F[n-1]+F[n-2],n>2

 

代码:

#define N 91

//关键,不能使用递归,则我们需要预处理

 

int main(int argc,char* argv[])

{

       _int64 F[N];

       F[1] = 1;

       F[2] = 2;

       for(int i = 3 ; i <= 90 ; i++)

       {

              F[i] = F[i-1] + F[i-2];

       }

       int n;

       while(EOF!=scanf("%d",&n))

       {

              printf("%ld\n",F[n]);

       }

       system("pause");

       getchar();

       return 0;

}

2

不容易系列之一

给N个网友写信,所有信全部装错信封有多少种可能的错误方式

输入:n(1

输出:错误的方式

输入:

2

3

输出:

1

2

计算机考研—机试指南

https://blog.csdn.net/qingyuanluofeng/article/details/47186349

递推求解:n=1,0种,n=2,1种,n=3,2种,n=4,8种。设F[n]为n个信封的装错方式总数。

         假设n号信封装的是k号信封,而n号信封中的信装在m号信封里。将k==m分为两类

               1)若k!=m,交换n号信封和m号信封,则n号信封对了,m号信封中是装的k号信,即除n号信封外,

               其余n-1个信封全部装错。装错方式为F[n-1]。又由于m的n-1个可能取值

               这类装错方式总数为(n-1)*F[n-1]。在n-1个信封装错的F[n-1]基础上,

               将n号信封所装的信与n-1个信封中任意一个信封(n-1中选择)所装的新交换,所得信全部错误。

               2)若k=m,交换n号信和m号信后,n号信封和m号中恰好对了,除它们之外剩余的n-2个信封全部装错,

               装错方式为F[n-2],又由于m的n-1个取值,装错方式总数为

               (n-1)*F[n-2]。可理解为在n-2个信封全部装错基础上,交换n号信封和1到n-1号信封中任意1刚和,共有n-1种选择。

关键:

1 错排公式F[n]={0,n=1

               {1,n=2

                        {(n-1)*F[n-1] + (n-1)*F[n-2],n>=2

2 递推求解技巧:逆推,从倒数第二步或者倒数第一步如何到达最终状态,找到递推关系式

3 递推求解往往数值较大,用long long 或 _int64 ,打印用%ld

4 分析问题,将每一个问题分割成规模较小的几个问题,分割过程要不遗漏和不重复,要得到递推关系式

 

代码:

#define N 21

 

int main(int argc,char* argv[])

{

       _int64 F[N];

       F[1] = 0;

       F[2] = 1;

       for(int i = 3 ; i <= N ; i++)

       {

              F[i] = (i-1)*F[i-1] + (i-1)*F[i-2];

       }

       int n;

       while(EOF!=scanf("%d",&n))

       {

              printf("%ld\n",F[n]);

       }

       system("pause");

       getchar();

       return 0;

}

3

最长递增子序列问题

在一个已知的序列{a1,a2,...,an}中,取出若干数组组成的序列{ai1,ai2,..,aim},其中下标i1,i2,...,im保持递增,即新增数列中的各个数之间依旧保持原数列中的先后顺序,那么称新的序列为原序列的一个子序列。若在序列中,下标ix > iy时,aix > aiy,称这个子序列为原序列的一个递增子序列。

计算机考研—机试指南

https://blog.csdn.net/qingyuanluofeng/article/details/47186395

思路:F[i]表示递增子序列以ai结尾的最长长度,F[1] = 1.假设F[1]到F[x-1]值都已经确定,以ax结尾的递增子序列,

除了长度为1情况,其他情况中ax都是紧跟在由ai(i

一次比较ax与其之前所有的ai(i

由以ai结尾的最长递增子序列再加上ax得到新的序列,长度也可以确定,取所有这些长度的最大值。当没有ai

关键:

1 动态规划公式:F[x] = max{1,F[i]+1 | ai

  F[1] = 1

  F[i] = max{1,F[j] + 1} | aj < ai && j

4

拦截导弹

导弹系统有缺陷,后面炮弹高度<=前一发高度。计算系统能拦截多少导弹。拦截时,必须按照时间顺序,不允许先拦截后面的导弹再拦截前面的导弹。

输入:每组输入两行。第一行:导弹数量k(k<=25)。第二行:k个整数,表示第k枚导弹的高度,按时间顺序,空格分隔

输出:每组输出一行,包含一个整数,表示能拦截多少导弹。

计算机考研—机试指南

https://blog.csdn.net/qingyuanluofeng/article/details/47186409

思路:其实就是求最长非递增子序列(子序列中排在前面的数字不比排在后面的数字小,前面>=后面,降序)。递推关系为:

      F[1] = 1

         F[i] = max{1,F[j]+1} | j < i && aj >= ai

关键:

1 求最长非递增子序列的方法是:遍历整个数组,以当前下标开始,遍历该下标之前的数,将两个数做比较,一旦前面的>=后面的,就将后面的加入到以前面结尾的子序列中,同时

  更新子序列长度

2 定义数组大小时,一定要比题目规定的容量大小加1,因为用累加方式会超过规定数组的大小

3 易错,需要将初始iMax = 1 放在大循环下面,每次i做变动之后重新计数。输入时发生错误:原因是忘记加上&符号

4 时间复杂度为O(n*n),空间复杂度为O(n)

5 最长递增子序列问题特点:将问题分割为许多子问题,每个子问题为确定以第i个数字结束的递增子序列长度,与以排在该数字之前所有比它小的元素结尾的最长递增子序列长度

  有关,且仅与其数字量有关。

 

代码:

int main(int argc,char* argv[])

{

       int iElemArr[N];//保存输入的元素

       int iLenArr[N];//保存以第i号元素结尾的最长非递增子序列长度

       int n,iCnt = 1;

       int i,j;

       while(EOF!=scanf("%d",&n))

       {

              //接受输入信息

              int iTemp = n;

              while(iTemp--)

              {

                     scanf("%d",&iElemArr[iCnt++]);

              }

              //int iMax= 1;

              //对每个元素进行遍历

              for(i = 1 ; i <= n ; i++)

              {

                     //易错,这里需要将初始值iMax放在每次i做变动之后

                     int iMax = 1;

                     //遍历当前元素之前的元素

                     for(j = 1; j < i ; j++)

                     {

                            //如果前面的元素>=后面的元素,就更新以第i个元素结尾的子序列的长度

                            if(iElemArr[j] >= iElemArr[i])

                            {

                                   iMax = max(iMax,iLenArr[j] + 1);

                            }

                     }

                     iLenArr[i] = iMax;//更新以第i号元素结尾的子序列的长度

              }

              int iMMax = -123123123;

              for(i = 1 ; i <= n ; i++)

              {

                     if(iLenArr[i] > iMMax)

                     {

                            iMMax = iLenArr[i];

                     }

              }

              printf("%d\n",iMMax);

       }

       system("pause");

       getchar();

       return 0;

}

5

最长公共子序列

字符串S中按照先后顺序依次取出若干字符,并将它们排列成一个新的字符串,这个字符串就称为原字符串的子串。有2个字符串S1和S2,求字符串S3同时为S1和S2的子串,且要求它的长度最长,确定这个长度。

输入:

abcd

cxbydz

输出:

2

计算机考研—机试指南

https://blog.csdn.net/qingyuanluofeng/article/details/47186443

思路:dp[i][j]表示S1中前i个字符与S2中的前j个字符分别组成的两个前缀字符串的最长公共子串长度,

当i.j较小时,dp[0][j]=0。假设已经求得dp[i][j](0<=i

     如何由这些值推得dp[x][y]。若S1[x]==S2[y],即S1中第x个字符和S2中第y个字符相同,

        且它们各是各自前缀子串的最后字符,那么必存在一个最长公共子串以S1[x]或S2[y]

        结尾,其他部分等价于S1中前x-1个字符和S2中前y-1个字符的最长公共子串。

        dp[x][y]=dp[x-1][y-1] +1.若S1[x]!=S2[y],最长公共子串长度为S1中前x-1个字符和S2中前y,

        个字符的最长公共子串长度  与  S1中前x个字符和S2中前y-1个字符的最长公共子串长度的较大者。

        即在两种情况下得到最长公共子串不会因为其中一个字符串又增加了一个

        字符长度而发生改变。dp[x][y]=max{dp[x-1][y],dp[x][y-1]}

关键:

1 递推条件:{dp[i][0] = 0 (0<=i <=n)

           {dp[0][j] = 0 (0<=j <=m)

                 {dp[i][j] = dp[i-1][j-1] + 1,(S1[i]==S2[j])

                 {dp[i][j] = max{dp[i-1][j],dp[i][j-1]},(S1[i]!=S2[j])

2 时间,空间复杂度均为O(L1*L2)

3 易错,这里会遗漏对初始字符的处理,所以比较的是s1[i-1]==s2[j-1] //if(s1[i]==s2[j])

4 dp[i][j]表示两个字符串中前i,j个字符的最长公共子序列

问题:求两个完全小写的字符串的最长公共子串长度。第一行和第二行分别是两个字符串,没有空格间隔,每个字符串的长度不超过100,输出:最长公共子串长度个数

 

代码:

int main(int argc,char* argv[])

{

       char s1[N],s2[N];

       int dp[N][N];//用于存放S1中以某个字符结尾,S2中以某个字符结尾的最长公共子串长度

       int i,j;

       while(EOF!=scanf("%s %s",s1,s2))

       {

              int iLen1 = strlen(s1);

              int iLen2 = strlen(s2);

              //设定初始状态

              //for(i = 0 ; i < iLen1 ; i++)

              for(i = 0 ; i <= iLen1 ; i++)

              {

                     dp[i][0] = 0;

              }

              //for(j = 0 ; j < iLen2 ; j++)

              for(j = 0 ; j <= iLen2 ; j++)

              {

                     dp[0][j] = 0;

              }

 

              //for(i = 1 ; i < iLen1 ; i++)

              for(i = 1; i <= iLen1;i++)

              {

                     //for(j = 1 ; j < iLen2 ; j++)

                     for(j = 1 ; j <= iLen2 ; j++)

                     {

                            //如果两个结尾字符相同,则计算其余长度

                            //易错,这里会遗漏对初始字符的处理,所以s1[i-1]==s2[j-1]

                            //if(s1[i]==s2[j])

                            if(s1[i-1]==s2[j-1])

                            {

                                   dp[i][j] = dp[i-1][j-1] + 1;

                            }

                            else

                            {

                                   dp[i][j] = max(dp[i-1][j],dp[i][j-1]);

                            }

                     }//for

              }//for

              printf("%d\n",dp[iLen1][iLen2]);

       }//while

       system("pause");

       getchar();

       return 0;

}

6

搬寝室

n件物品,n<2000.准备搬2*k(<=n)件物品。每搬一次的疲劳度和左右手之间的重量差的平方成正比。请求出搬完这2*k件物品后的最低疲劳度是多少

输入:每组输入数据有2行,第一行有2个数n,k(2<=2*k<=n<2000),第二行有n个整数,分别表示n件物品的重量(<2的15次方)

输出:对应每组输入数据,输出数据只有一个表示他的最少的疲劳度,每个一行.

输入:

2 1

1 3

输出:

4

计算机考研—机试指南

https://blog.csdn.net/qingyuanluofeng/article/details/47186477

思路:设a<=b<=c<=d经计算ab,cd配对方案的累计疲劳度<=ac,bd配对方案的累计疲劳度。结论:每一对组合的两个物品重量,是原物品中重量相邻的两个物品。

     先对物品排序。设dp[i][j]表示前j件物品中选择i对物品的最小疲劳度。状态来源:

        1)物品j和j-1未配对,则物品j每被选择,dp[i][j]=dp[i][j-1]

        2) 物品j和j-1配对,等价于配对i-1对,将j-1与j进行配对dp[i][j]=dp[i-1][j-2] + (list[j]-list[j-1])的平方

关键:

1 状态转移方程dp[i][j]= min{dp[i][j-1] ,dp[i-1][j-2] + (list[j]-list[j-1])的平方},其中dp[0][n]=0

2 dp时间复杂度=状态数量*状态转移复杂度=(k*n)*O(1)=O(k*n)

3 你要求出的值是dp[k][n]

4 注意两边的i,j需要配对。i表征k,j表征n,j=2*i

5 程序还没有运行就退出,说明你声明了一个全局变量,而且申请的空间很大,导致崩溃

6 程序发生错误,粗心:scanf("%d",&Arr[n]);dp[0][n] = 0;应该把n改成i。

7 本题技巧,j > 2*i dp[i][j]=dp[i-1][j],j<=2*i,dp[i][j]=INT_MAX,比较dp[i][j]和dp[i-1][j-2]的大小

 

代码:

#define INT_MAX 123123123

#define NN 201

 

int partition(int* Arr,int low,int high)

{

       int iPos = Arr[low];

       while(low < high)

       {

              while(low < high && Arr[high] >= iPos)

              {

                     high--;

              }

              Arr[low] = Arr[high];

              while(low < high && Arr[low] <= iPos)

              {

                     low++;

              }

              Arr[high] = Arr[low];

       }

       Arr[low] = iPos;

       return low;

}

 

void quickSort(int* Arr,int low,int high)

{

       if(low < high)

       {

              int iPos = partition(Arr,low,high);

              quickSort(Arr,low,iPos-1);

              quickSort(Arr,iPos+1,high);

       }

}

 

int main(int argc,char* argv[])

{

       int n,k;

       int dp[NN][NN];

       int Arr[NN];

       int i,j;

       while(EOF!=scanf("%d %d",&n,&k))

       {

              //对状态数组进行初始化

              for(i = 0; i <= n ; i++)

              {

                     dp[0][i] = 0;

              }

 

              //接受输入信息

              //for(i = 0; i < n ; i++)

              //易错,这里的i与j要与下面的进行比对质量时配对

              for(i = 1 ; i <= n ; i++)

              {

                     scanf("%d",&Arr[i]);

              }

 

              //对重量进行快速排序

              quickSort(Arr,1,n);

              //sort(Arr,Arr+1,Arr+1+n);

 

              //开始进行动态规划

              for(i = 1 ; i <= k ; i++)

              {

                     //易错,j必须从2i开始

                     //for(j = 2 ; j <=n ; j++)

                     for(j=2*i ; j <= n ; j++)

                     {

                            //如果j > 2*i表示,最后2个物品可以不配对

                            if(j > 2*i)

                            {

                                   dp[i][j] = dp[i-1][j];

                            }

                            //j<=2*i,最后2个物品必须配对

                            else

                            {

                                   dp[i][j]=INT_MAX;//否则前j件物品配不成i对,所以其状态不能由dp[i][j-1]转移而来,dp[i][j]先设置为正无穷大

                            }

                            //判定是dp[i-1][j-2]的值与其他的值比较,更新这个值

                            if(dp[i][j] > dp[i-1][j-2] + (Arr[j]-Arr[j-1])*(Arr[j]-Arr[j-1]))

                            {

                                   dp[i][j] = dp[i-1][j-2] + (Arr[j]-Arr[j-1])*(Arr[j]-Arr[j-1]);

                            }

                     }

              }

              //最终你所要求的是dp[k][n]

              printf("%d\n",dp[k][n]);

 

       }

       system("pause");

       getchar();

       return 0;

}

7

Greedy Tino

有一堆柑橘,重量为0到2000,总重量不大于2000。从中取出两堆放在扁担两头,且两头重量相等,问符合条件的的每堆重量最大是多少。没有符合条件的分堆方式则输出-1

输入:第一行时t,表示t个测试用例,对每个测试用例,包含一个数字n,表示橘子数。第二行有n个数字,表明每个橘子的重量,1<=n<=100.如果是因为存在重量为0的橘子,导致扁担两边重量为0,那么应该输出0,否则输出-1

 

输入:

1

5

1 2 3 4 5

输出:

7

计算机考研—机试指南

https://blog.csdn.net/qingyuanluofeng/article/details/47186505

思路:橘子放在第一堆或者第二堆造成两堆之间的重量动态变化,设dp[i][j]:表示第i个橘子放入后,第一堆比第二堆重j时,两堆的最大重量和

     初始状态:dp[0][0],dp[0][j](j!=0)为负无穷,表明其他状态不存在

        目标状态:dp[n][0]/2即为所求。因为dp[n][0]表示的是总重量

        橘子的3种摆放方式:放入第一堆,放入第二堆,不放入。设橘子重量为wei[i]

        放入第一堆:dp[i][j]=dp[i-1][j+wei[i]]+wei[i]

        放入第二堆:dp[i][j]=dp[i-1][j-wei[i]]+wei[i]

        不放:      dp[i][j]=dp[i-1][j]

复杂度:总重量<=2000,时间复杂度=状态数量*状态转移复杂度=柑橘总数(n*2*2000)*O(1)=O(4000*m),n表示橘子的数量,

 

关键:

1 注意重量可能为负,需要加偏置值2000

2 注意最后打印与判断是dp[n][0+OFFSET]/2.对于每个橘子,存在-2000到2000的共4000状态量的变化,

所以转移值需要放在状态量变化那层循环中

 

 

代码:

//设置偏置值

#define OFFSET  200

//设置橘子最大数量+1

#define N 101

//设置最大正整数

#define INT_MAX 0x7fffffff

 

int main(int argc,char* argv[])

{

       int dp[N][401];//用于保存当加入dp[i][j]=放入第i个橘子,造成第一堆比第二堆重量重j时两堆的最大总重量

       int wei[N];//保存橘子的重量

       int T;//表示测试用例数

       int n;//表示一共有多少个橘子

       int iCnt = 0,i,j;

       bool haveZero = false;

       while(EOF!=scanf("%d",&T))

       {

              //对于每个测试用例数,获取橘子信息,对于重量为0的橘子进行过滤,不做处理

              while(T--)

              {

                     scanf("%d",&n);

                     for(i = 0 ; i < n ; i++)

                     {

                            scanf("%d",&wei[++iCnt]);

                            if(wei[iCnt]==0)

                            {

                                   haveZero = true;

                                   iCnt--;//去除重量为0的橘子

                            }

                     }

              }

              //初始化无效的状态量

              for(j = -200; j <= 200 ; j++)

              {

                     dp[0][j+OFFSET] = -INT_MAX;

              }

              //设置唯一的正确状态量,dp[0][2000]=0

              dp[0][0+OFFSET] = 0;

              n = iCnt;//更新重量不为0的橘子的数量

 

              //易错,开始进行状态转移,遍历每个柑橘,对每个柑橘,遍历每种状态量

              for(i = 1 ; i <= n ; i++)

              {

                     for(j = -200 ; j <= 200 ; j++)

                     {

                            //易错,每放入一个橘子,重新设置总重量,记录放在第一堆时转移得来的总重量的新值,若为-INT_MAX,表示无法转移

                            int iTemp1 = -INT_MAX,iTemp2 = -INT_MAX;

                            //如果将橘子放入第一堆中,造成第一堆比第二堆的重量差 + wei[i],对是否超过总重量需要进行判断,对是否转移到无效状态进行判断

                            //if(dp[i][ j+OFFSET+wei[i] ] <= 2000 && dp[][])

                            if(j+wei[i] <= 2000 && -INT_MAX!=dp[i-1][ j+wei[i]+OFFSET ])

                            {

                                   iTemp1 = dp[i-1][j+wei[i]+OFFSET] + wei[i];

                            }

                            //如果放在第二堆

                            if(j-wei[i] >= -2000 && -INT_MAX!=dp[i-1][ j-wei[i]+OFFSET ])

                            {

                                   iTemp2 = dp[i-1][j-wei[i]+OFFSET] + wei[i];

                            }

                            //取总重量重的较大值

                            if(iTemp1 < iTemp2)

                            {

                                   iTemp1 = iTemp2;

                            }

                            //与不放橘子的总重量进行比较

                            if(iTemp1 < dp[i-1][j+OFFSET])

                            {

                                   iTemp1 = dp[i-1][j+OFFSET];

                            }

 

                            //更新当前状态量为上述3个状态量中的最大的一个

                            dp[i][j+OFFSET] = iTemp1;

                     }//for

              }//for

              //如果总重量为0,注意加上偏置值

              //if(dp[n][0]==0)

              if(dp[n][0+OFFSET]==0)

              {

                     puts(true==haveZero ? "0":"-1");

              }

              else

              {

                     //易错,注意加上偏置值

                     //printf("%d\n",dp[n][0]/2);

                     printf("%d\n",dp[n][0+OFFSET]/2);

              }

       }

       system("pause");

       getchar();

       return 0;

}

8

采药

不同草药,采每一株需要一些时间,每一株有自己价值,如何让采到的价值最大。

输入:第一行有两个整数T(1<=T<=1000)和M(1<=M<=100),T代表总共能够用来采药的时间,M代表山洞里的草药数目。

接下来的M行,每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值

输出:在规定时间内,可以猜到的草药的最大总价值

计算机考研—机试指南

https://blog.csdn.net/qingyuanluofeng/article/details/47186535

参见经典例题分析

9

Piggy-Bank

有一个储蓄罐,告知空的质量和当前重量,并给定一些钱币的价值和相应的重量,求储蓄罐中最少有多少现金。

输入:包含T组测试用例。第一行。每一个测试用例包含2个整数E和F,表明空储蓄罐的重量和装满钱的重量。<=10,000g,第二行是每个测试用例,包含一个整数N(1<=N<=500),

     给出了各种硬币的数量。接下来是N行,每行表示一种硬币类型,每行包括2个整数,P,W(1<=p<=50000,1<=W<=10000),p是价值,W是重量

输出:

储蓄罐中最少有多少钱

 

输入

3

10 110

2

1 1

30 50

 

10 110

2

1 1

50 30

 

1 6

2

10 3

20 4

输出:

The minimum amount of money in the piggy-bank is 60.

The minimum amount of money in the piggy-bank is 100.

This is impossible.

计算机考研—机试指南

https://blog.csdn.net/qingyuanluofeng/article/details/97750459

分析:完全背包问题的变体。1不求最大值,求最小值。则程序中选择价值小的那个,

2要求钱币和空储蓄罐的重量恰好达到总重量,在背包问题中表现为背包恰好装满。

关键:

1 由于你获取物品的时候是从i = 1开始获取的,因此背包计算的时候i也必须从1开始遍历

 

代码:

 

#define INT_MAX 0x7fffffff

#define N 10001

typedef struct List

{

       int w;//体积

       int v;//价值

}List;

 

int main(int argc,char* argv[])

{

       int T;//测试用例数

       int iEmp;//空储蓄罐重量

       int s;//装满钱的储蓄罐重量

       int n;//n行

       int i,j;

       while(EOF!=scanf("%d",&T))

       {

              while(T--)

              {

                     List list[501];//最多一共会出现500种货币

                     int dp[N];

                     scanf("%d %d",&iEmp,&s);

                     s -= iEmp;//求出钱的重量

                     scanf("%d",&n);

                     //获取输入的钱

                     for(i = 1 ; i <= n ; i++)

                     {

                            scanf("%d %d",&list[i].v,&list[i].w);

                     }

                     //初始化完全背包,除dp[0]外其余状态均不存在

                     for(i = 0 ; i <= s ;i++)

                     {

                            //dp[i] = -INT_MAX;

                            dp[i] = INT_MAX;

                     }

                     dp[0] = 0;

                     //开始背包计算,注意遍历所有物品从物品1开始计算,因为物品0根本没有复制

                     //for(i = 0 ; i <= n ; i++)

                     for(i = 1 ; i <= n ; i++)

                     {

                            //完全背包:顺序

                            for(j = list[i].w ; j <= s; j++)

                            {

                                   //易错,需要判断状态是否可以得到

                                   //if(-INT_MAX!=dp[j-list[i].w])

                                   if(INT_MAX!=dp[j-list[i].w])

                                   {

                                          dp[j] = min(dp[j],dp[j-list[i].w]+list[i].v);

                                   }

                            }

                     }

                     //如果状态不可得

                     //if(-INT_MAX==dp[s])

                     if(INT_MAX==dp[s])

                     {

                            puts("This is impossible.");

                     }

                     else

                     {

                            printf("The minimum amount of money in the piggy-bank is %d\n",dp[s]);

                     }

              }

       }

 

       system("pause");

       getchar();

       return 0;

}

10

珍惜现在,感恩生活

支援灾区。你有n元,市场有m种大米,每种大米都是袋装产品,价格不等,并且只能整袋购买。你最多能采购多少公斤粮食

输入:第一行1个正整数C(表示有C组测试用例),每组测试用例的第一行时两个整数n和m(1<=n<=100,1<=m<=100),分别表示经费的金额和大米的种类,然后是m行数据。

每行包括3个数p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数。

输出:输出能够购买大米最多重量,经费可以不用完

输入:

1

8 2

2 100 4

4 100 2

输出:

400

计算机考研—机试指南

https://blog.csdn.net/qingyuanluofeng/article/details/47186603

关键:

1 多重背包:

介于0-1背包和完全背包之间。容积为V的背包,给定一些物品,每种物品包含体积w,价值v和数量k,求用该背包能装下的最大价值总量。

每种物品可选的数量不再为无穷或者1,而是一个确定的数k。将多重转换为0-1背包。即每种物品被视为k种不同物品,对所有物品求0-1背包。

将原数量为k的物品拆分为若干组,每组物品包含的原物品个数分别为:1,2,4,...,k-2的c+1次方,其中c为使k-2的c+1次方>0的最大整数。类似与

二进制拆分。可以得到0到k之间任意物品价值重量和。对所有这些信物品做0-1背包。

2 多重背包转化为0-1背包后,采用逆序

3 背包问题:外层循环是物体,内层循环是总容积,对每个物品求其价值总和

4 拆分方法:

          while(k-c>0)

                {

                   k -= c ;

                      list[++iCnt].v = v*c;

                      list[iCnt].w = w*c;

                      c *= 2

                 }

                 //注意最后一次拆分是用k乘不是用c乘

                 list[++iCnt].v = k*v;

                 list[iCnt].w = k*w;

 

代码:

 

typedef struct List

{

       int w;//体积

       int v;//价格

}List;

 

int main(int argc,char* argv[])

{

       int T;

       int s,n;

       int i,j;

       int w,v,k;

       while(scanf("%d",&T))

       {

              while(T--)

              {

                     List list[2001];

                     int dp[101];//总共最多100元,相当于容积

                     scanf("%d %d",&s,&n);

                     int iCnt = 0;//用于计数拆分后的物品总数

                     for(i = 0 ; i < n ; i++)

                     {

                            scanf("%d %d %d",&w,&v,&k);

                            //开始进行多重背包的拆分

                            int c = 1;

                            while(k - c > 0)

                            {

                                   k -= c;//拆分

                                   list[++iCnt].v = c*v;

                                   list[iCnt].w = c*w;

                                   c *= 2;

                            }

                            //易错,最后一次的k小于c,也要拆分,这里是乘以k倍而不是c倍

                            list[++iCnt].v = k*v;

                            list[iCnt].w = k*w;

                     }

                     //背包初始化,s为总体积

                     for(i = 0 ; i <= s; i++)

                     {

                            dp[i] = 0;

                     }

                     //背包计算,cnt为物品数,s为总体积

                     for(i = 1 ; i <= iCnt ; i++)

                     {

                            for(j = s; j >= list[i].w ; j--)

                            {

                                   dp[j] = max(dp[j],dp[j-list[i].w] + list[i].v);

                            }

                     }

                     printf("%d\n",dp[s]);

              }

       }

       system("pause");

       getchar();

       return 0;

}

11

连续子数组的最大和:

输入一个整形数组,数组里有整数也有负数。数组中一个或连续的多个整数组成一个子数组。求所有子数组的和的

最大值。要求时间复杂度为O(n)

输入:

输入有多组数据,每组测试数据包括两行。

第一行为一个整数n(0<=n<=100000),当n=0时,输入结束。接下去的一行包含n个整数(我们保证所有整数属于[-1000,1000])。

输出:

对应每个测试案例,需要输出3个整数单独一行,分别表示连续子向量的最大和、该子向量的第一个元素的下标和最后一个元素的下标。若是存在多个子向量,则输出起始元素下标最小的那个。

样例输入:

3

-1 -3 -2

5

-8 3 2 0 5

8

6 -3 -2 7 -15 1 2 2

0

样例输出:

-1 0 0

10 1 4

8 0 3

剑指offer

https://blog.csdn.net/qingyuanluofeng/article/details/39186633

关键:

1 f[i] = {iArr[i],i = 0 或者f(i-1) <= 0

       {f(i-1) + iArr[i],i != 0 并且 f(i-1) > 0

    动态规划:用f[i]表示以第i个数结尾的最大连续和

2 要保存当前的累加和与之前的最大累加和进行比较

  if(lSum > lMaxSum)

 

 

const int MAXSIZE = 100001;

int iArr[MAXSIZE];

 

void maxSubArrSum(int n)

{

       long long lSum = 0,lMaxSum = iArr[0];

       int iNewBegIndex = 0,iNewEndIndex = 0;

       int iBegIndex = 0,iEndIndex = 0;

       for(int i = 0 ; i < n ; i++)

       {

              if(lSum <= 0)

              {

                     lSum = iArr[i];//这里需要重新记录最大连续子数组的起始下标

                     iNewBegIndex = i;

              }

              else

              {

                     lSum += iArr[i];

                     iNewEndIndex = i;

              }

              if(lSum > lMaxSum)

              {

                     lMaxSum = lSum;

                     iBegIndex = iNewBegIndex;//更新最大连续子数组的起始下标

                     iEndIndex = iNewEndIndex;

              }

       }

       printf("%lld %d %d\n",lMaxSum,iBegIndex,iEndIndex);//注意是:%lld,而不是%d

}

12

数塔问题

有形如右图的一个数塔,从顶部出发,在每一结点可以选择向左走或是向右走,一直走到底层,要求找出一条路径,使路径上的数值和最大。

输入:

5

9

12 15

10 6 8

2 18 9 5

19 7 10 4 16

输出:

59

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/47189313

思想:

动态规划:

1 设d[i][j]:表示从i,j出发得到的最大数值和

2 状态迁移方程:d[i][j] = max(d[i+1][j],d[i+1][j+1]) + a[i][j]

3 边界:d[n][j] = a[n][j]

4 所求状态:d[1][1]

 

int numberTower(int iArr[MAXSIZE][MAXSIZE],int n)

{

       int iDP[MAXSIZE][MAXSIZE];

       //初始化动态规划数组

       for(int j = 1 ; j <= n ; j++)

       {

              iDP[n][j] = iArr[n][j];

       }

       //从下向上,状态迁移

       for(int i = n - 1; i >= 1 ; i--)

       {

              for(int j = 1 ; j <= i ; j++)

              {

                     iDP[i][j] = max(iDP[i+1][j],iDP[i+1][j+1]) + iArr[i][j];

              }

       }

       return iDP[1][1];

}

 

void process()

{

      

       int iArr[MAXSIZE][MAXSIZE];

       int n;

       while(EOF != scanf("%d",&n))

       {

              for(int i = 1 ; i <= n ; i++)

              {

                     for(int j = 1 ; j <= i ; j++)

                     {

                            scanf("%d",&iArr[i][j]);

                     }

              }

              printf("%d\n",numberTower(iArr,n));

       }

}

13

TSP之货郎担问题

有n个城市,用1,2,…,n表示,城i,j之间的距离为dij,有一个货郎从城1出发到其他城市一次且仅一次,最后回到城市1,怎样选择行走路线使总路程最短?

如果对于任意数目的n个城市,分别用1~n编

号,则这个问题归结为在有向带权图中,寻找一

条路径最短的哈密尔顿回路问题。

这里,V表示城市顶点,(i,j) ∈E 表示城市之

间的距离,用邻接矩阵C表示城市之间的距离。

输入说明:0标识不可达

输入:

4

0 3 6 7

5 0 2 3

6 4 0 2

3 7 5 0

4

0 8 5 6

6 0 8 5

7 9 0 5

9 7 8 0

输出:

10

23

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/47189319

思想:

1设d(i,V-{i})表示从顶点i出发,经过V-{i}中各顶点一次,回到顶点i的最短路径长度

2d(i,V-{i}) = d(i,|V) = k属于V min{Cik,d(i,!V - {k})}

3边界:d(k,空集) = Cki,k!= i,从顶点k出发,不经过任何顶点,回到顶点i的长度,自然是Cki

4目标状态:d(0,{1,2,3})

难点:如何标识这个集合V,难道用标记法,这是集合上的动态规划问题,可以通过位来进行操作

0001表示第i个节点选中

那初始时:1111...1110

                     n-1个1 1个0

 

难点:

1到底递归求的是什么?

分析即所得,求的是集合

求的是:d(i,V-{i}) = d(iStart,iFull - (1<

2递归传入的参数是什么?

iSart,初始节点,除去初始节点的剩余节点集合,为一个数

3递归基是什么?

递归基是:如果碰到已经求过的,则直接返回;碰到i != g_iStart && s == 0,返回g_dp[i][s] = g_iArr[i][g_iStart];

4如何挑选k?

       for(int k = 0 ; k < n ; k++)

       {

              //如果已经含有k,才挑选

              if(s & (1 << k))

              {

                     //d(i,V-{i}) = d(i,|V) = k属于V min{Cik,d(i,!V - {k})}

                     g_dp[k][s ^ (1<

 

关键:

1 //对于多个状态,只能用递归来做。

2    //记忆化搜索

       if(g_dp[i][s] != -1)

       {

              return g_dp[i][s];

       }

       //递归基

       if(i != g_iStart && s == 0)

       {

              return g_dp[i][s] = g_iArr[i][g_iStart];

       }

3    for(int k = 0 ; k < n ; k++)

       {

              //如果已经含有k,才挑选

              if(s & (1 << k))

              {

                     //d(i,V-{i}) = d(i,|V) = k属于V min{Cik,d(i,!V - {k})}

                     g_dp[k][s ^ (1<

                     //选取最小值

                     if(iMin > g_dp[k][s ^ (1<

                     {

                            iMin = g_dp[k][s ^ (1<

                     }

4 1设d(i,V-{i})表示从顶点i出发,经过V-{i}中各顶点一次,回到顶点i的最短路径长度

2d(i,V-{i}) = d(i,|V) = k属于V min{Cik,d(i,!V - {k})}

3边界:d(k,空集) = Cki,k!= i,从顶点k出发,不经过任何顶点,回到顶点i的长度,自然是Cki

4目标状态:d(0,{1,2,3})

 

const int MAXSIZE = 20;

int g_dp[MAXSIZE][1 << MAXSIZE];//采用集合的方式来做

int g_iArr[MAXSIZE][MAXSIZE];

int g_iStart = 0;

 

 

//对于多个状态,只能用递归来做。

int TSP(int i,int s,int n)

{

       //记忆化搜索

       if(g_dp[i][s] != -1)

       {

              return g_dp[i][s];

       }

       //递归基

       if(i != g_iStart && s == 0)

       {

              return g_dp[i][s] = g_iArr[i][g_iStart];

       }

 

       //开始进行递推,从底向上

       int iMin = 1000000000;

       //选择下一个城市

       for(int k = 0 ; k < n ; k++)

       {

              //如果已经含有k,才挑选

              if(s & (1 << k))

              {

                     //d(i,V-{i}) = d(i,|V) = k属于V min{Cik,d(i,!V - {k})}

                     g_dp[k][s ^ (1<

                     //选取最小值

                     if(iMin > g_dp[k][s ^ (1<

                     {

                            iMin = g_dp[k][s ^ (1<

                     }

              }

       }

       return g_dp[i][s] = iMin;

}

 

void process()

{

       int n;

       while(EOF != scanf("%d",&n))

       {

              if(n <= 0)

              {

                     break;

              }

              memset(g_iArr,0,sizeof(g_iArr));

              for(int i = 0 ; i <= n - 1 ; i++)

              {

                     for(int j = 0 ; j <= n - 1 ; j++)

                     {

                            scanf("%d",&g_iArr[i][j]);

                     }

              }

              int iStart = 0;

              memset(g_dp,-1,sizeof(g_dp));

              //假设选定i=1作为初始节点

              //初始化动态规划数组

              for(int i = 0 ; i <= n-1 ; i++)

              {

                     if(i != iStart)

                     {

                            g_dp[i][0] = g_iArr[i][iStart];

                     }

              }

              TSP(iStart,(1<

              printf("%d\n",g_dp[iStart][(1<

       }

}

14

多段图的最短路径问题

定义: 给定有向带权图G(V,E,W),如果把顶点集合V划分成k个不相交的子集V i ,1≤i ≤k,k≥2,使得E中的任何一条边

(u,v),必有uЄ V i, v ∈ V i+m  ,m≥1,则称这样的图为多段图。

 

输入说明:

首行:顶点个数,顶点从0开始

接下来:每一行分别是:起始结点编号 终止结点编号 权值

输入:

10(顶点个数) 19(边的条数)

0 1 4

0 2 1

0 3 3

 

1 4 9

1 5 8

2 3 1

2 4 6

2 5 7

2 6 8

3 5 4

3 6 7

 

4 7 5

4 8 6

5 7 8

5 8 6

6 7 6

6 8 5

 

7 9 7

8 9 3

 

输出:

15

0 2 3 5 8 9

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/47189329

 

决策的第一阶段:确定图中第k-1段的所有顶点到达收点t的花费最小的通路。把这些信息保存

起来,在最后形成最优决策时使用。用数组cost[i]存放顶点i到达收点t的最小花费,用数组path[i]

存放顶点i到达收点t的最小花费通路上的前方顶点编号

 

决策第二阶段:确定途中第k-2段的所有顶点到收点t的花费最小的通路。这时,利用第一阶段形成的信息来进行

决策,并把决策的结果存放在数组cost和path的相应元素中,依次进行,直到最后确定源点s到收点t的花费最小的

通络。

源点s的path数组中就是最优决策序列

 

思想:

1设cost[i]表示顶点i到收点t的最短花费

2动态规划方程:cost[i] =  min{c[i][j] + cost[j]},1<= j <= n ,并且j != i中

 path[i] = j,j是使得c[i][j] + cost[j]最小的j

3 边界条件:cost[n-1] = 0

4 目标状态:cost[0]

算法步骤:

用route[n]存放从源点s出发到达收点t的最短通路上的顶点编号

1)对所有的i,0<=i

2)令i = n - 2;

3)根据状态迁移方程计算cost[i]和path[i]

4)另i = i -1 ;若i > = 0,转步骤(3),否则转到步骤(5)

5)另i = 0 ,route[i] = 0

6)若route[i] = n-1,算法结束,否则转步骤(7)

7)i = i + 1,route[i] = path[route[i-1],转步骤(6)

 

 

关键:

1 逆序方式 递推  //二重循环,循环n-1次,每次,求出当前结点的最短花费,外层循环:逆序

       for(int i = n-2 ; i >= 0 ; i--)

       {

              //内层循环递增求解

              int iMin = MAX;

              for(int j = i+1 ; j <= n-1 ; j++)

              {

                     //剪枝

                     if(g_iCost[j] != MAX && g_iMatrix[i][j] != -1)

                     {

                            iRet = g_iCost[j] + g_iMatrix[i][j];

                            if(iMin > iRet)

                            {

                                   iMin = iRet;

                                   g_iCost[i] = iMin;

                                   //如何记录最短路径

                                   g_iPath[i] = j;

 

2 顺序方式,递归

       //记忆化搜索,也是递归基

       if(g_iCost[iEnd] != MAX)

       {

              return g_iCost[iEnd];

       }

       //自底向上开始动态规划

       int iMin = MAX;

       int iRet;

       for(int i = 0 ; i <= n-1 ; i++)

       {

              //计算其他状态值,剪枝,cost[i] = min { cost[j] + c[i][j])

              if(i > iEnd && g_iMatrix[iEnd][i] != -1)

              {

                     iRet = dp(i,n) + g_iMatrix[iEnd][i];

                     if(iMin > iRet)

                     {

                            iMin = iRet;

 

3设定path[i] = j;j是使cost[i]取得最小值的j

//如何记录最短路径

g_iPath[i] = j;

 

4 1设cost[i]表示顶点i到收点t的最短花费

2动态规划方程:cost[i] =  min{c[i][j] + cost[j]},1<= j <= n ,并且j != i中

 path[i] = j,j是使得c[i][j] + cost[j]最小的j

 

 

代码:

const int MAXSIZE = 100;

int g_iMatrix[MAXSIZE][MAXSIZE];

int g_iCost[MAXSIZE];

int g_iPath[MAXSIZE];

int g_iRoute[MAXSIZE];

 

const int MAX = 1000000000;

 

//因为存在多个阶段,只能用递归做

int dp(int iEnd,int n)

{

       //记忆化搜索,也是递归基

       if(g_iCost[iEnd] != MAX)

       {

              return g_iCost[iEnd];

       }

       //自底向上开始动态规划

       int iMin = MAX;

       int iRet;

       for(int i = 0 ; i <= n-1 ; i++)

       {

              //计算其他状态值,剪枝,cost[i] = min { cost[j] + c[i][j])

              if(i > iEnd && g_iMatrix[iEnd][i] != -1)

              {

                     iRet = dp(i,n) + g_iMatrix[iEnd][i];

                     if(iMin > iRet)

                     {

                            iMin = iRet;

                     }

              }

       }

       return g_iCost[iEnd] = iMin;

}

 

void process()

{

       int n;

       while(cin >> n)

       {

              int iBeg,iEnd,iDis;

              memset(g_iMatrix,-1,sizeof(g_iMatrix));

              int iEdgeNum ;

              cin >> iEdgeNum;

              for(int k = 0 ; k < iEdgeNum ; k++)

              {

                     cin >> iBeg >> iEnd >> iDis;

                     g_iMatrix[iBeg][iEnd] = iDis;

              }

              //初始化最大距离

              for(int i = 0 ; i <= n - 1 ; i++)

              {

                     g_iCost[i] = MAX;

              }

              //初始化动态规划数组初始状态

              g_iCost[n-1] = 0;

              memset(g_iPath,-1,sizeof(g_iPath));

              dp(0,n);

              cout << g_iCost[0] << endl;

       }

}

15

资源分配问题

资源分配问题:是考虑如何把有限的资源分配给

若干个工程问题。假设资源总数为 r,工程个数为n,给每个工程投入的资源不同,所得的利润也不同,要求把总数为 r 的资源分配给n个工程,以获得最大利润的分配方案

 

输入:

3(工程数) 8(资源数),下面3行,每行9列

0 4 26 40 45 50 52 52 53

0 5 15 40 60 70 73 74 75

0 5 15 40 80 90 95 98 100

输出:

140

0 4 4

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/47189335

决策过程:

把资源r划分为m个相同的部分,每份资源为r/m,m为整数。假定利润函数为

Gi(x)=把x份资源分配给第i个工程得到的利润

G(m) = i从1到n 累加求和Gi(xi)

i从1到n 累加求和xi = m

牛逼:问题转化为把m份资源分配给n个工程,使得G(m)最大的问题

m份资源

首先,把各个工程按顺序编号,按下述方法进行划分:

第一阶段:分别把x=0,1,...,m份资源给第1份个工程,确定在第一个工程在各种不同份额的资源下,

能够得到最大的利润

第二阶段:分别把x=0,1,...,m份资源分配给第1,2个工程,确定第1个工程在各种不同份额的

资源分配下,这两个工程能够得到的最大利润和第二个工程的最优分配份额。

在第n个阶段,分别把x=0,1,2,...,m份资源分配给所有n个工程,确定能够得到的最大利润,

以及在此利润下,第n个工程的最优分配份额。

考虑到把m份资源全部投入给所有n个工程一定能够得到最大利润,因此必须在各个阶段中,

对不同的分配份额计算,得到最大利润,然后取其中的最大者,作为每个阶段能够取得的

的最大利润。再取每个阶段的最大利润中的最大者,以及在该最大利润下的分配方案,

即为整个资源分配的最优决策

思想:

前缀法

1设fi(x)表示把x份资源分配给前i个工程时,所得到的最大利润,

di(x)表示使得fi(x)最大时,分配给第i个工程的资源份额。

在第一阶段,只把x份资源分配给第一个工程,有:

{f1(x) = G1(x)

{d1(x) = x        0<=x<=m

第二阶段,只把x份资源分配给第一、第二两个工程,有:

{f2(x)=max{G2(z) + f1(x-z)}

{d2(x) = 使得f2(x)最大的z    ,0<=x<=m,0<=z<=x

非常重要

第i个阶段,把x份资源分配给前i个工程,有:

Gi(x)=把x分资源分配给第i工程的利润

{fi(x)=max{Gi(z) + fi-1(x-z)}

{di(x) = 使得fi(x)最大的z,0<=x<=m,0<=z<=x

令第i阶段最大利润为gi,则:本质上是遍历各种解决方案

gi = max{fi(1),...,fi(m)}

设qi是使得gi最大时,分配给前面i个工程的资源份额,则:

qi = 使得fi(x)最大的x,这里的qi是整体

di = 使的fi(x)最大的当前工程得到的资源个数

Gi(x) = 把x份资源分配给前i个工程得到的利润

fi(x) = 把x份资源分配给前i个工程得到的最大利润

optg = max{g1,g2,...,gn}

k = 使gi最大的i

optx = 与最大的gi相对应的qi

在每个阶段中,把所得到的所有局部决策值fi(x),di(x),gi,qi保存起来。

最后,在第n阶段结束之后,令全局的最大利润为optg,则:

optg = max{g1,g2,...,gn}

在全局最大利润下,所分配工程项目的最大编号(即所分配工程项目的最大数目)为k,则:

k = 使gi最大的i

分配给前面k个工程的最优份额为:

optx = 与最大的gi相对应的qi[使得fi(x)最大的x]

分配给第k个工程的最优份额为:

optqk = dk(optx)[k是最大的i,optx全局的分配量]

分配给其余k-1个工程的剩余的最优份额为:

optx = optx - dk(optx)

由此回溯,得到分配给前面各个工程的最优份额的递推关系式:

{optqi=di(optx)

{optx=optx-optqi,i = k,k-1,...,1

 

输入:

3(工程数) 8(资源数),下面3行,每行9列

0 4 26 40 45 50 52 52 53

0 5 15 40 60 70 73 74 75

0 5 15 40 80 90 95 98 100

输出:

140

0 4 4

x             0     1     2     3     4     5     6     7     8

G1(x)      0     4     26   40   45   50   52   52   53

G2(x)      0     5     15   40   60   70   73   74   75

G3(x)      0     5     15   40   80   90   95   98   100

第一步:求各个阶段不同分配的份额时在此利润下的分配额。

第一阶段,只把资源的份额分配给第一个工程:

x             0     1     2     3     4     5     6     7     8

f1(x) 0     4     26   40   45   50   52   52   53

d1(x)       0     1     2     3     4     5     6     7     8

g1 = 53 = max{f1(1),f1(2),...,f1(8)}

q1 = 8

第二阶段,只把资源分配给第1、2个工程

x = 1时,f2(1) = max(G2(0) + f1(1),G2(1) + f1(0)) = max(0 + 4,5 + 0) = 5 

x = 2时, f2(2) = max(G2(0) + f1(2),G2(1) + f1(1) ,G2(2) + f1(0))

                        = max(0 + 26,5 + 4,15+0) = 26

累次计算x=3,4...,8时的f2(x)以及d2(x)的值,有:

x             0     1     2     3     4     5     6     7     8

f2(x) 0     5     26   40   60   70   86   100  110

d2(x)       0     1     0     0     4     5     4     4     5

g2 = 110 = max{f2(1),f2(2),...,f2(8)}

q2 = 使g2取得最大的资源分配总数 = 8

同样计算f3(x)以及d3(x)的值有:

x             0     1     2     3     4     5     6     7     8

f3(x) 0     5     26   40   80   90   106  120  140

d3(x)       0     1     0     0     4     5     4     4     4

g3 = 140

q3 = 8

optg = max{g1,g2,g3} = g3 = 140

optg = 140,optx = 8,k = 3

{optqx = dk(optx)

{optx = optx - optqx

optq3 = d3(optx) =d3(8) = 4,optx = optx  - optq3 = 8 - 4 = 4

optq2 = d2(optx) =d2(4) = 4,optx = optx - optq2 = 4- 4 = 0

optq2 = d2(optx) =d1(0) = 0

计算各个工程的最优分配额度

f2(4) = 60

下面求各个阶段的最大利润,以及在此利润下的分配份额

gi=max{fi(1),...,fi(m)}

qi=使得fi(x)最大的x

算法流程:

1计算fi,di。fi(x) = max{Gi(z) + fi-1(x-z)},di(x) = 使fi(x)最大的z,计算方法采取递推方法

2计算gi,qi。gi = max{fi(1),fi(2),...,fi(n)}:对于前i个工程,分配各个资源数下的利润最大值,qi = 使gi最大的x

3计算optg,optq,k。optg = max{g1,g2,...,gn},k=使得全局利润最大的最大工程编号,optq = 使得optx最大的gi对应的qi

4计算optqx。optqx = dk(optx),optx = optx - optqx,直到所有工程计算完毕

 

 

关键:

1 令第i阶段最大利润为gi,则:本质上是遍历各种解决方案

gi = max{fi(1),...,fi(m)}

设qi是使得gi最大时,分配给前面i个工程的资源份额,则:

qi = 使得fi(x)最大的x,这里的qi是整体

di = 使的fi(x)最大的当前工程得到的资源个数

Gi(x) = 把x份资源分配给前i个工程得到的利润

fi(x) = 把x份资源分配给前i个工程得到的最大利润

optg = max{g1,g2,...,gn}

k = 使gi最大的i

optx = 与最大的gi相对应的qi

2

1)计算fi,di。fi(x) = max{Gi(z) + fi-1(x-z)},di(x) = 使fi(x)最大的z,计算方法采取递推方法

2)计算gi,qi。gi = max{fi(1),fi(2),...,fi(n)}:对于前i个工程,分配各个资源数下的利润最大值,qi = 使gi最大的x

3)计算optg,optq,k。optg = max{g1,g2,...,gn},k=使得全局利润最大的最大工程编号,optq = 使得optx最大的gi对应的qi

4)计算optqx。optqk = dk(optx),optx = optx - optqi,直到所有工程计算完毕

 

代码:

const int MAXSIZE = 100;

 

int g_iMatrix[4][9] =

{

       {0,0,0,0,0,0,0,0,0},

       {0,4,26,40,45,50,52,52,53},

       {0,5,15,40,60,70,73,74,75},

       {0,5,15,40,80,90,95,98,100}

};

int g_f[MAXSIZE][MAXSIZE];//g_f[i][x]:表示将x个资源分配给钱前x个工程的最大利润

int g_d[MAXSIZE][MAXSIZE];//g_d[i][x]:表示使得g_f[i][x]最大的z

int g_g[MAXSIZE];          //计算分配给前i个工程的利润最大值,g[i] = max{fi(1),fi(2),...,fi(n)}

int g_q[MAXSIZE];          //g_q[i] = 使得g_g[i]最大的前i个工程的总共资源数

int g_optx;               //全局最大利润值

int g_optq;                            //全局最大利润下的所分配的资源总数

int g_k;                          //全局最大利润下的最大工程编号

int g_optqArr[MAXSIZE];   //g_optqArr[i]:存储第i个工程分配的资源个数

 

 

/*n:表示工程数,m:表示资源数*/

int allocate(int n,int m)

{

       //初始时:设置利润最小

       memset(g_optqArr,-1,sizeof(g_optqArr));

       memset(g_q,-1,sizeof(g_q));

       memset(g_g,-1,sizeof(g_g));

       memset(g_f,-1,sizeof(g_f));

 

       //动态规划数组赋初始值

       for(int j = 0 ; j <= m ; j++)

       {

              //初始,f1(x) = a[1[j],资源数,等于原来个数

              g_f[1][j] = g_iMatrix[1][j];

              g_d[1][j] = j;

       }

 

       //计算g1

       g_g[1] = g_f[1][1];

       g_q[1] = 1;

       for(int x = 2 ; x <= m ; x++)

       {

              if(g_g[1] < g_f[1][x])

              {

                     g_g[1] = g_f[1][x];

                     g_q[1] = x;

              }

       }

 

       //自底向上求各阶段,外层迭代

       for(int i = 2 ; i <= n ; i++)

       {

              //fi(x) = max(gi(z) + fi-1(x-z)),x属于[0,n],z属于[0,x]

              for(int x = 0 ; x <= m ; x++)

              {

                     for(int z = 0 ; z <= x ; z++)

                     {

                            int iResult = g_iMatrix[i][z] + g_f[i-1][x-z];

                            if(g_f[i][x] < iResult)

                            {

                                   g_f[i][x] = iResult;

                                   //更新当前工程最大利润下的份额

                                   g_d[i][x] = z;

                            }

                     }

                     //更新前i个工程,依次分配1,2,...,n个资源的利润最大值

                     if(g_g[i] < g_f[i][x])

                     {

                            g_g[i] = g_f[i][x];

                            g_q[i] = x;

                     }

              }

       }

 

       //计算全局最大利润

       g_optx = g_g[1];

       g_optq = g_q[1];//g_q计算错误

       g_k = 1;

       for(int i = 2 ; i <= n ; i++)

       {

              if(g_optx < g_g[i])

              {

                     g_optx = g_g[i];

                     g_optq = g_q[i];

                     g_k = i;

              }

       }

 

       //计算资源分配方案,optqi = dk(optx),optx = optx - optqi

       for(int i = n ; i >= 1 ; i--)

       {

              g_optqArr[i] = g_d[i][g_optq];

              g_optq -= g_optqArr[i];

       }

       return g_optx;

}

 

/*打印结果:全局最大利润,和资源分配方案*/

void printResult(int n)

{

       cout << g_optx << endl;

       for(int i = 1 ; i <= n ; i++)

       {

              if(i != 1)

              {

                     cout << " " << g_optqArr[i];

              }

              else

              {

                     cout << g_optqArr[i];

              }

       }

       cout << endl;

}

 

void process()

{

       int n;

       int m;//资源个数

       cin >> n >> m;

       {

              allocate(n,m);

              printResult(n);

       }

}

16

最长公共子序列

假定,A=a 1 a 2 …a n 是字母表∑上的一个字符序列,如果存在

∑上的另外一个字符序列S=c 1 c 2 …c j ,使得对所有的k,

k=1,2,…,j,有c k =a ik (其中,1≤ik≤n),是字符序列A的一个下标递增序列,则称字符序列S是A的子序列。

如果∑={x,y,x}, ∑上的字符序列是A=xyzyxzxz,则xxx是A的

一个长度为3的子序列。该子序列中的字符,对应于A的下

标是1,5,7。而xzyzx是A的长度为5的子序列,对应于A的下

标是1,3,4,6,7.

 

输入:

xyxzyxyzzy xzyzxyzxyzxy

xyzyxzxz xzyxxyzx

xzyxxyzx xyzyxzxz(先后顺序不同,答案不同)

输出:

8

xyxzxyzy

6

xzyxzx

6

xzyxxz

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/47189343

搜索状态:

设置二维状态数组si,j。

si,j = 1,ai = bj

si,j = 2,ai != bj,且Li-1,j >= Li,j-1,序列A优先

si,j = 3,ai != bj,且Li-1,j < Li,j-1,序列B优先

设Ln,m=k,Sk=c1c2...ck是An和Bm的长度为k的最长公共子序列,搜索过程从状态字Sn,m开始。

若sn,m=1,an = bm -> 搜索sn-1,m-1

若sn,m=2,an !=bm,且Ln-1,m >= Ln,m-1,序列A优先 -> 搜索sn-1,m

若sn,m=3,                                             sn,m-1

搜索起点:i = n,j = m

搜索终止:i = 0 或 j = 0

先通过初始状态,从上向下,从左向右填表,填表利用递推

然后把表填完,从最后再开始搜索

递推和动态规划的结合

思想:

1)记Ln,m为序列An和Bm的最长公共子序列的长度,则为Li,j为序列Ai和Bj的长度。

2)状态迁移方程:Ln,m = {Ln-1,m-1 + 1,an = bm,n > 0 ,j >0

                                     {max{Ln-1,m  ,Ln,m-1},an!= bm

3)边界:   Li,0 = L0,j = L0,0 = 0,1<=i<=n,1<=j<=m

 

4)目标状态:Ln,m

算法过程:

1填表:

L[i][j] = {L[i-1][j-1] + 1,ai = bj,1<=i<=n,1<=j<=m,

                {max(L[i-1][j] , L[i][j-1]), ai != bj,1<=i<=n,1<=j<=m

                {0,i = 0 或者 j = 0

2搜索:

S[n][m] = {1,an = bm

                {2,an != bm && L[i-1][j] >= L[i][j-1]

                {3,an != bm && L[i-1][j] < L[i][j-1]

3 n = 0 或 m = 0算法结束

 

代码:

int LCS(string& sA,string& sB,vector& vecLCS)

{

       //鲁棒性

       if(sA.empty() || sB.empty())

       {

              return 0;

       }

       int n = sA.size();

       int m = sB.size();

 

       //设定递推数组的初始值

       int iLCSArr[MAXSIZE][MAXSIZE];

       //memset(iLCSArr,0,sizeof(iLCSArr));

 

       //自顶向下递推

       for(int i = 0 ; i < n ; i++)

       {

              for(int j = 0 ; j < m ; j++)

              {

                     if(i != 0 && j != 0)

                     {

                            if(sA.at(i) != sB.at(j))

                            {

                                   iLCSArr[i][j] = max(iLCSArr[i-1][j],iLCSArr[i][j-1]);

                            }

                            else

                            {

                                   iLCSArr[i][j] = iLCSArr[i-1][j-1] + 1;

                            }

                     }

                     else

                     {

                            iLCSArr[i][j] = 0;

                     }

              }

       }

       //测试,打印长度表

       cout << "长度表:" << endl;

       for(int i = 0 ; i < n ; i++)

       {

              for(int j = 0 ; j < m ;j++)

              {

                     cout << iLCSArr[i][j];

              }

              cout << endl;

       }

 

       //填表

       int iMatrix[MAXSIZE][MAXSIZE];

       for(int i = 0 ; i < n ; i++)

       {

              for(int j = 0 ; j < m ; j++)

              {

                     if(sA[i] == sB[j])

                     {

                            iMatrix[i][j] = 1;

                     }

                     else

                     {

                            //序列A优先

                            if(iLCSArr[i-1][j] >= iLCSArr[i][j-1])

                            {

                                   iMatrix[i][j] = 2;

                            }

                            //序列B优先

                            else

                            {

                                   iMatrix[i][j] = 3;

                            }

                     }

              }

       }

 

       //测试,打印填表

       cout << "\n状态表:" << endl;

       for(int i = 0 ; i < n ; i++)

       {

              for(int j = 0 ; j < m ;j++)

              {

                     cout << iMatrix[i][j];

              }

              cout << endl;

       }

       cout << endl;

 

       //搜索

       int iRet = 0;

       for(int i = n -1 ; i >= 0 ;)

       {

              for(int j = m - 1 ; j >= 0 ; )

              {

                     //相等

                     if(iMatrix[i][j] == 1)

                     {

                            iRet++;

                            vecLCS.push_back(sA[i]);

                            i--;

                            j--;

                     }

                     //序列A优先,直接向上走,i--

                     else if(iMatrix[i][j] == 2)

                     {

                            i--;

                     }

                     //序列B优先,直接向左走,j--

                     else if(iMatrix[i][j] == 3)

                     {

                            j--;

                     }

              }

       }

       return iRet;

}

 

void process()

{

       string sA,sB;

       vector vecLCS;

       cout << "请输入两个字符串:" << endl;

       while(cin >> sA >> sB)

       {

              vecLCS.clear();

              printf("%d\n",LCS(sA,sB,vecLCS));

              for(int i = vecLCS.size() - 1; i>= 0 ;i-- )

              {

                     cout << vecLCS.at(i);

              }

              cout << endl;

       }

}

17

仪器维修时间表问题

一台精密仪器的工作时间为 n 个时间单位, 与仪器工作时间同步进行若干仪器维修程序. 一旦启动维修程序, 仪器必须进入维修程序. 如果只有一个维修程序启动, 则必须进入该维修程序. 如果在同一时刻有多个维修程序, 可任选进入其中的一个维修程序. 维修程序必须从头开始,不能从中间插入. 一个维修程序从第 s 个时间单位开始, 持续 t 个时间单位, 则该维修程序在第 s+t-1 个时间单位结束. 为了提高仪器使用率, 希望安排尽可能少的维修时间. 

对于给定的维修程序时间表, 计算最优时间表下的维修时间.

输入数据的第 1 行有 2 个小于 10000 的正整数 n 和 k, n 表示仪器的工作时间单位, k 是维修程序数. 接下来的 k 行中, 每行有 2 个表示维修程序的整数 s 和 t, 该维修程序从第 s 个时间单位开始, 持续 t 个时间单位.

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/47189359

18

有向线段k值问题:

给定一条有向直线 L 以及 L 上的 n+1 个点 x0 < x1 < x2 < …< xn 。有向直线 L 上的每个点 xi 都有一个权 w(xi);每条有向边(xi ,xi-1) ,

也都有一个非负边长 d(xi ,xi-1)。有向直线 L 上的每个点xi 可以看作客户,其服务需求量为w(xi) 。

每条边(xi ,xi-1) 的边长 d(xi ,xi-1) 可以看作运输费用。如果在点 xi 处未设置服务机构,

则将点xi 处的服务需求沿有向边转移到点 xj 处服务机构需付出的服务转移费用为w(xi)*d(xi ,xj) 。

已知在点 x0 处设置了服务机构,求要在直线 L 上增设 k 处服务机构,使得整体服务转移费用最小的费用。

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/50044087

20

饮料供货

大家对每一种饮料的满意度是知道的。微软供应部每天供应总量为V的饮料。每种饮料的单个容量都是2的方幂,比如王老吉,都是2^3 = 8升的,可乐都是

2^5 = 32升的。每种饮料也有购买量的上限。统计数据中用饮料名字,容量,数量,满意度描述每一种饮料。

如何求出保证最大满意度的购买量?

编程之美

https://blog.csdn.net/qingyuanluofeng/article/details/39273027

21

求数组的子数组之和的最大值

一个由N个整数元素的数组,这个数组有很多子数组,子数组之和的最大值是什么?

编程之美

https://blog.csdn.net/qingyuanluofeng/article/details/47187325

22

子数组之和的最大值(二维)

编程之美

https://blog.csdn.net/qingyuanluofeng/article/details/47187343

23

求数组中最长递增子序列:(这里不要求相邻)

写一个时间复杂度尽可能低的程序,求一个一维数组(N个元素)中最长递增子序列的长度。

例如,在序列1,-1,2,-3,4,-5,6,-7中,最长递增子序列的长度为4(如1,2,4,6)

编程之美

https://blog.csdn.net/qingyuanluofeng/article/details/47187365

24

数组分割

有一个无序、元素个数为2*n的正整数数组,要求:如何能把这个数组分割为元素个数为n的两个数组,并使两个子数组的和最接近?

编程之美

https://blog.csdn.net/qingyuanluofeng/article/details/47187427

25

上楼的方式

有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶,2阶或3阶。实现一个方法,计算小孩有多少种上楼梯的方式。

程序员面试金典

https://blog.csdn.net/qingyuanluofeng/article/details/54233352

26

机器人走路的方式

设想有个机器人坐在X*Y网格的左上角,只能向右、向下移动。机器人从(0,0)到(X,Y)有多少种走法?

程序员面试金典

https://blog.csdn.net/qingyuanluofeng/article/details/54233603

27

机器人走路的方式_避免禁区

设想有个机器人坐在X*Y网格的左上角,只能向右、向下移动。假设有些点为“禁区”,机器人不能踏足。设计一种算法,找出一条路径,让机器人从左上角移动到右下角。机器人从(0,0)到(X,Y)有多少种走法?

程序员面试金典

https://blog.csdn.net/qingyuanluofeng/article/details/54233800

28

求堆出箱子的最大高度

给你一堆n个箱子,箱子宽Wi,高Hi,深Di。箱子不能翻转,将箱子堆起来时,下面箱子的宽度、高度和深度必须大于上面的箱子,实现一个方法,搭出最高的一堆箱子,箱堆的高度为每个箱子高度的总和。

程序员面试金典

https://blog.csdn.net/qingyuanluofeng/article/details/54314545

29

叠罗汉问题

有个马戏团正在设计叠罗汉的表演节目,一个人要站在另一个人的肩膀上。出于实际和美观的考虑,在上面的人要比下面的人矮一点、轻一点。已知马戏团每个人的高度和重量,请编写代码计算叠罗汉最多能叠几个人。

程序员面试金典

https://blog.csdn.net/qingyuanluofeng/article/details/54380001

30

给定一个整数数组(有正数有负数),找出总和最大的连续数列,并返回总和

程序员面试金典

https://blog.csdn.net/qingyuanluofeng/article/details/54577889

31

句子分割

你写了一篇长文,却误用“查找/替换”,不慎删除了文档中所有空格、标点,大写变成小写。比如,句子"I reset the computer, It still didn't boot!"变成了"ireset the computertstilldidntboot"。你发现,只要能正确分离各个单词,加标点和调整大小写都不成问题。大部分单词在字典里都找得到,有些字符串如专有名词则找不到。

给定一个字典(一组单词),设计一个算法,找出拆分一连串单词的最佳方式。这里“最佳”的定义是,解析后无法辨识的字符序列越少越好。举个例子,字符串"jesslookedjustliketimherbrother"的最佳解析结果为"JESS looked just like TIM her brother",总共有7个字符无法辨别,全部显示为大写,以示区别。

程序员面试金典

https://blog.csdn.net/qingyuanluofeng/article/details/54586108

32

给定一个正整数和负整数组成的N*M矩阵,编写代码找出元素总和最大的子矩阵。

程序员面试金典

https://blog.csdn.net/qingyuanluofeng/article/details/54633084

33

Maximum Subarray

Find the contiguous subarray within an array (containing at least one number) which has the largest sum.

For example, given the array [-2,1,-3,4,-1,2,1,-5,4],

the contiguous subarray [4,-1,2,1] has the largest sum = 6.

click to show more practice.

More practice:

If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/54970473

34

Unique Paths

A robot is located at the top-left corner of a m x n grid (marked 'Start' in the diagram below).

The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked 'Finish' in the diagram below).

How many possible unique paths are there?

Above is a 3 x 7 grid. How many possible unique paths are there?

Note: m and n will be at most 100.

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/54980914

35

Unique Paths II

Follow up for "Unique Paths":

Now consider if some obstacles are added to the grids. How many unique paths would there be?

An obstacle and empty space is marked as 1 and 0 respectively in the grid.

For example,

There is one obstacle in the middle of a 3x3 grid as illustrated below.

[

  [0,0,0],

  [0,1,0],

  [0,0,0]

]

The total number of unique paths is 2.

Note: m and n will be at most 100.

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/54981080

36

Minimum Path Sum

Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.

Note: You can only move either down or right at any point in time.

分析:动态规划。这个是要求从网格上左上角到右下角的所经过的路径上所有元素和的最小值。移动时,只能向下或者向右

进行。

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/54981266

37

Climbing Stairs

You are climbing a stair case. It takes n steps to reach to the top.

Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

Note: Given n will be a positive integer.

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/54986627

38

Decode Ways

A message containing letters from A-Z is being encoded to numbers using the following mapping:

'A' -> 1

'B' -> 2

...

'Z' -> 26

Given an encoded message containing digits, determine the total number of ways to decode it.

For example,

Given encoded message "12", it could be decoded as "AB" (1 2) or "L" (12).

The number of ways decoding "12" is 2.

分析:题目对字符A到Z进行1到26的转换,现在给定数字,要求反推得原先的字母组成的不同字符串的个数

A:1,G:7,H:8,I:9,J:10,...,Z:26

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/55057157

39

Triangle

Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.

For example, given the following triangle

[

     [2],

    [3,4],

   [6,5,7],

  [4,1,8,3]

]

The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).

Note:

Bonus point if you are able to do this using only O(n) extra space, where n is the total number of rows in the triangle.

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/55224546

40

Best Time to Buy and Sell Stock III

Say you have an array for which the ith element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete at most two transactions.

Note:

You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

分析:至少要交易两次,求取至少交易两次下的最大值。

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/55254631

41

Word Break

Given a non-empty string s and a dictionary wordDict containing a list of non-empty words,

determine if s can be segmented into a space-separated sequence of one or more dictionary words.

You may assume the dictionary does not contain duplicate words.

For example, given

s = "leetcode",

dict = ["leet", "code"].

Return true because "leetcode" can be segmented as "leet code".

分析:给定一个非空字符串,确定是否可以将字符串分割为多个字符串之后,且每个字符串都在字典中。

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/55667730

42

Word Break II

Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, add spaces in s to construct a sentence where each word is a valid dictionary word. You may assume the dictionary does not contain duplicate words.

Return all such possible sentences.

For example, given

s = "catsanddog",

dict = ["cat", "cats", "and", "sand", "dog"].

A solution is ["cats and dog", "cat sand dog"].

分析:给定一个非空字符串,确定是否可以将字符串分割为多个字符串之后,且每个字符串都在字典中。

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/55668466

43

Best Time to Buy and Sell Stock IV

Say you have an array for which the ith element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete at most k transactions.

Note:

You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

分析:此题必须计算至多k次交易的时候获取的最大利润。

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/56291342

44

House Robber

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed,

the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and

it will automatically contact the police if two adjacent houses were broken into on the same night.

Given a list of non-negative integers representing the amount of money of each house,

determine the maximum amount of money you can rob tonight without alerting the police.

分析:一个盗贼,不能连续偷两个相邻的家,问今天最多能获取的财富是多少。

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/56297170

45

House Robber II

 

Note: This is an extension of House Robber.

After robbing those houses on that street, the thief has found himself a new place for his thievery so that he will not get too much attention. This time, all houses at this place are arranged in a circle. That means the first house is the neighbor of the last one. Meanwhile, the security system for these houses remain the same as for those in the previous street.

Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.

分析:此题修改题目为环,确实可以增加获取的财富

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/56489594

46

Maximal Square

Given a 2D binary matrix filled with 0's and 1's, find the largest square containing only 1's and return its area.

For example, given the following matrix:

1 0 1 0 0

1 0 1 1 1

1 1 1 1 1

1 0 0 1 0

Return 4.

分析:给定一个二维矩阵,只包含0和1.找到值包含1的最大面积。

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/56668234

47

Longest Increasing Subsequence

Given an unsorted array of integers, find the length of longest increasing subsequence.

For example,

Given [10, 9, 2, 5, 3, 7, 101, 18],

The longest increasing subsequence is [2, 3, 7, 101], therefore the length is 4. Note that there may be more than one LIS combination, it is only necessary for you to return the length.

Your algorithm should run in O(n2) complexity.

Follow up: Could you improve it to O(n log n) time complexity?

分析:此题是求最长递增子序列。

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/57419124

48

Best Time to Buy and Sell Stock with Cooldown

Say you have an array for which the ith element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times) with the following restrictions:

You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

After you sell your stock, you cannot buy stock on next day. (ie, cooldown 1 day)

Example:

prices = [1, 2, 3, 0, 2]

maxProfit = 3

transactions = [buy, sell, cooldown, buy, sell]

分析:此题仍然是购买股票问题。但是抛售股票的下一天不允许买入。

求可以获得的最大利润

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/58221795

49

Coin Change

You are given coins of different denominations and a total amount of money amount.

Write a function to compute the fewest number of coins that you need to make up that amount.

If that amount of money cannot be made up by any combination of the coins, return -1.

Example 1:

coins = [1, 2, 5], amount = 11

return 3 (11 = 5 + 5 + 1)

Example 2:

coins = [2], amount = 3

return -1.

Note:

You may assume that you have an infinite number of each kind of coin.

分析:这明显是给定硬币数组,判断给定的钱能否用最少的硬币兑换。

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/58651973

50

Longest Increasing Path in a Matrix

Given an integer matrix, find the length of the longest increasing path.

From each cell, you can either move to four directions: left, right, up or down. You may NOT move diagonally or move outside of the boundary (i.e. wrap-around is not allowed).

Example 1:

nums = [

  [9,9,4],

  [6,6,8],

  [2,1,1]

]

Return 4

The longest increasing path is [1, 2, 6, 9].

Example 2:

nums = [

  [3,4,5],

  [3,2,6],

  [2,2,1]

]

Return 4

The longest increasing path is [3, 4, 5, 6]. Moving diagonally is not allowed.

分析:题目给定一个矩阵,需要寻找到矩阵中的最长递增序列。

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/59057435

51

Integer Break

Given a positive integer n, break it into the sum of at least two positive integers

and maximize the product of those integers. Return the maximum product you can get.

For example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4).

Note: You may assume that n is not less than 2 and not larger than 58.

分析:将一个正整数分解为至少两个整数,然后使得分解后整数的乘积最大。

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/59134848

52

Russian Doll Envelopes

You have a number of envelopes with widths and heights given as a pair of integers (w, h). One envelope can fit into another if and only if both the width and height of one envelope is greater than the width and height of the other envelope.

What is the maximum number of envelopes can you Russian doll? (put one inside other)

Example:

Given envelopes = [[5,4],[6,4],[6,7],[2,3]], the maximum number of envelopes you can Russian doll is 3 ([2,3] => [5,4] => [6,7]).

分析:envelope:信封。此题是将信封不断装入另一个较大的信封中。

较小的信封必须宽度和高度都小于较大信封的宽度,高度才可以被放入。

问最多可以放入多少个信封。

这是程序员面试金典的一道题目。

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/59486890

53

Count Numbers with Unique Digits

Given a non-negative integer n, count all numbers with unique digits, x, where 0 ≤ x < 10n.

Example:

Given n = 2, return 91. (The answer should be the total numbers in the range of 0 ≤ x < 100,

excluding [11,22,33,44,55,66,77,88,99])

分析:给定一个非负整数n,输出0到10^n有多少非唯一的数字组成的数的个数,该整数

是n位数。

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/59517971

54

Max Sum of Rectangle No Larger Than K

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/59598735

55

Ransom Note

We are playing the Guess Game. The game is as follows:

I pick a number from 1 to n. You have to guess which number I picked.

Every time you guess wrong, I'll tell you whether the number I picked is higher or lower.

However, when you guess a particular number x, and you guess wrong, you pay $x. You win the game when you guess the number I picked.

Example:

n = 10, I pick 8.

First round:  You guess 5, I tell you that it's higher. You pay $5.

Second round: You guess 7, I tell you that it's higher. You pay $7.

Third round:  You guess 9, I tell you that it's lower. You pay $9.

Game over. 8 is the number I picked.

You end up paying $5 + $7 + $9 = $21.

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/60132523

56

Largest Divisible Subset

Given a set of distinct positive integers, find the largest subset such that every pair (Si, Sj) of elements in this subset satisfies: Si % Sj = 0 or Sj % Si = 0.

If there are multiple solutions, return any subset is fine.

Example 1:

nums: [1,2,3]

Result: [1,2] (of course, [1,3] will also be ok)

Example 2:

nums: [1,2,4,8]

Result: [1,2,4,8]

分析:给定一些列恶补相同的正整数,找到最大的子集,使其满足任意而元素(Si,Sj)

都有Si % Sj = 0 或者相反

满足这样的多个数,存在最大的数是前面一个数的倍数。次最大的数是前面的倍数,..

也就是说所有比最大的数小的数都能被该最大数整除

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/60135291

57

如何求数组连续最大和

Python程序员面试算法宝典

https://blog.csdn.net/qingyuanluofeng/article/details/92760652

58

如何获取最好的矩阵链相乘方法

给定一个矩阵序列,找到最有效的方式将这些矩阵相乘在一起。给定表示矩阵链

的数组p,使得第i个矩阵Ai的维数为p[i-1]*p[i]。编写一个函数

MatrixChainOrder(),该函数应该返回乘法运算所需的最小乘数。

输入: p=(40, 20, 30, 10, 30)

输出: 26000

有4个大小为40 * 20, 20 * 30, 30 * 10和10 * 30的矩阵。

假设这四个矩阵为A, B, C, D。该函数的执行方法可以使得

执行乘法运算的次数最少。

Python程序员面试算法宝典

https://blog.csdn.net/qingyuanluofeng/article/details/93546894

59

如何求两个字符串的最长公共子串

找出两个字符串的最长公共子串,例如字符串"abccade"与字符串

"dgcadde"的最长公共子串为"cad"

python程序员面试算法宝典

https://blog.csdn.net/qingyuanluofeng/article/details/94298998

60

如何求最长递增子序列的长度

假设L=是n个不同的实数的序列,L的递增子序列是这样的一个子序列

Lin=,其中k1

求最大的m值。

Python程序员面试算法宝典

https://blog.csdn.net/qingyuanluofeng/article/details/94967031

61

如何求字符串的编辑距离

编辑距离又称为Levenshtein距离,是指两个字符串之间由一个转成另一个所需的

最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符、

插入一个字符、删除一个字符。请设计并实现一个算法来计算两个字符串

的编辑距离,并计算其复杂度。

在某些应用场景下,替换操作的代价比较高,假设替换操作的代价

是插入和删除的两倍,算法该如何调整。

Python程序员面试算法宝典

https://blog.csdn.net/qingyuanluofeng/article/details/95035766

62

如何在二维数组中寻找最短路线

寻找一条左上角(arr[0][0])到右下角(arr[m-1][n-1])的路线,使得沿途经过

的数组中的整数的和最小。

Python程序员面试算法宝典

https://blog.csdn.net/qingyuanluofeng/article/details/95040611

63

最大连续和

给出一个长度为n的序列A1,A2,...,An,求最大连续和。换句话说,要求找到1<=i <= j<=n,使得Ai+Ai+1+..+Aj尽量大

b[i] = { b[i-1] + a,b[i-1] > 0

       {a,b[i-1] <0

输入:

8

1 -2 3  10 -4 7 2 -5

输出:

18(3 10 -4 7 2)

算法竞赛入门经典

https://blog.csdn.net/qingyuanluofeng/article/details/47747103

64

恰好型01背包

与01背包问题的唯一不同在于:初始化状态时,除了dp[0]=0之外,其余dp[j]全为INT_MAX,而且需要判断状态是否可以得到

问题:采药。不同草药,采每一株需要一些时间,每一株有自己价值,如何让采到的价值最大。

输入:第一行有两个整数T(1<=T<=1000)和M(1<=M<=100),T代表总共能够用来采药的时间,M代表山洞里的草药数目。

接下来的M行,每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值

输出:在规定时间内,可以猜到的草药的最大总价值

输入:

70(T采药总时间) 3(M,M行,每行是采药时间和价值)

71 100

69 1

1 2

输出:

3

算法竞赛入门经典

https://blog.csdn.net/qingyuanluofeng/article/details/47775925

65

完全01背包

完全01背包:

完全背包中每个物体可以被选择无限次,非恰好,状态dp[i][j]恰好可以由可能已经放入物品i的状态dp[i][j-goods[i].iWeight]转移而来,因此将遍历顺序该为顺序

问题:采药。不同草药,采每一株需要一些时间,每一株有自己价值,如何让采到的价值最大。

输入:第一行有两个整数T(1<=T<=1000)和M(1<=M<=100),T代表总共能够用来采药的时间,M代表山洞里的草药数目。接下来的M行,每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值

输出:在规定时间内,可以猜到的草药的最大总价值

输入:

70(T采药总时间) 3(M,M行,每行是采药时间和价值)

71 100

69 1

1 2

输出:

140(2*70)

算法竞赛入门经典

https://blog.csdn.net/qingyuanluofeng/article/details/47775951

66

钢条切割

钢条切割:

动态规划与分治的相同点:组合子问题求解原问题

不同点:分治的子问题不重叠,做了重复工作,动态规划保存解到表中

动态规划的特点:

1最优子结构:问题的最优解由相关子问题的最优解组合而成,子问题可以独立求解

公司出售一段长度为i英寸的钢条价格为Pi(i=1,2,...),钢条长度为整英寸

长度i   1     2     3     4     5     6     7     8     9       10

价格Pi   1     5     8     9     10   17   17   20       24   30

给定长度为n的钢条,求使得的最大收益Rn

输入的第一行是钢条的长度n

 

输入:

4

输出:

10

算法导论

https://blog.csdn.net/qingyuanluofeng/article/details/50844819

67

钢条切割_动态规划的两种解法

钢条切割:

动态规划与分治的相同点:组合子问题求解原问题

                不同点:分治的子问题不重叠,做了重复工作,动态规划保存解到表中

动态规划的特点:

1最优子结构:问题的最优解由相关子问题的最优解组合而成,子问题可以独立求解

动态规划的实现方式:1带备忘的自顶向下,2自底向上

1带备忘的自顶向下:递归中保存子问题的解,需要子问题的解时,首先检查是否已经保存过此解,如果是直接返回保存的值

2自底向上:定义子问题的规模,将子问题按照规模排序,按从小到大的顺序求解,求解子问题时,该子问题所依赖的更小的子问题已经保存

两个方法的区别:

自顶向下递归

自底向上无需递归,设置初始值,用两层循环: 1<= i <= n, 1<= j <= i , 用R[i] = max(R[i] , P[j] + R[i - j] )

公司出售一段长度为i英寸的钢条价格为Pi(i=1,2,...),钢条长度为整英寸

长度i   1     2     3     4     5     6     7     8     9       10

价格Pi   1     5     8     9     10   17   17   20       24   30

给定长度为n的钢条,求使得的最大收益Rn

输入的第一行是钢条的长度n

输入:

4

输出:

10

算法导论

https://blog.csdn.net/qingyuanluofeng/article/details/50846382

68

矩阵链乘法

给定一个n个矩阵的序列,矩阵Ai的规模为Pi-1*Pi(1<=i<=n),计算乘积A1*A2*...*An

设Ai,j表示AiAi+1...Aj乘积的结果矩阵

设m[i,j]表示计算矩阵Ai,j乘法次数最小值

那么我们需要求的结果就是m[1,n]

设AiAi+1...Aj的最优括号方案的分割点在矩阵Ak和Ak+1之间,其中i<=k

那么m[i,j]等于计算Ai,k和Ak+1,j的代价加上两者相乘的代价的最小值

设矩阵Ai的大小为Pi-1*Pi,那么Ai,k与Ak+1,j相乘的代价是Pi-1PiPj

其中k属于[i,j-1]

当m[i,i]时,表示只有一个矩阵,所以相乘的次数为0

m[i,j]= { 0 , i = j

              { min i<=k

我们用s[i,j]保存AiAi+1...Aj的最优分割点k

采用自底向上的方法,应该按照长度顺序求解矩阵链括号化问题

输入第一行的第一个数n为矩阵的个数,第二行有n+1个数,分别为n个矩阵各自的行数+最后第n个矩阵的列数

输出最小乘法次数

输入:

6

30 35 15 5 10 20 25

输出:

15125

((A1(A2A3))((A4A5)A6))

算法导论

https://blog.csdn.net/qingyuanluofeng/article/details/50848689

69

最优二叉搜索树

最优二叉搜索树:给定一个n个不同关键字的已经排序的序列K=(因此k1

对每个关键字ki,都有一个概率pi表示其搜索频率。有些要搜索的值可能不在K中,因此我们还有n+1个“伪关键字”do,d1,d2,...,dn表示不

在K中的值。d0表示所有小于k1的值,dn表示所有大于kn的值,对i=1,2,...,n-1,伪关键字di表示所有在ki和ki+1之间的值。对于每个伪关

键字di,也都有一个概率pi表示对应的搜索频率。下图显示了n=5个关键字的集合构造的两颗二叉搜索树。每个关键字ki是一个内部结点,

而每个关键字di是一个叶结点。每次搜索要么成功(找到某个关键字ki),要么失败(找到某个伪关键字di),因此有如下公式:

i属于[1,n] pi的和 + i属于[0,n] qi的和 = 1

关键字ki的两个伪关键字为di-1,di

                            k2

              k1                                       k4

       d0          d1                 k3                         k5

                                   d2          d3          d4          d5
输入关键字个数n,接下来有两行,第一行是n个数字,表示p1~pn,第二行是n+1个数字,表示q0~qn

输出最优二叉搜索树的期望搜索代价

输入:

5

0.15 0.1 0.05 0.1 0.2

0.05 0.1 0.05 0.05 0.05 0.1

输出:

2.75

算法导论

https://blog.csdn.net/qingyuanluofeng/article/details/50927159

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

参考:
[1]计算机考研--机试指南,王道论坛 组编
[2]剑指offer
[3]算法设计与分析
[4]编程之美
[5]程序员面试金典
[6]leecode
[7]Python
程序员面试算法宝典
[8]刘汝佳算法竞赛入门经典
[9]算法导论
[10]编程珠玑

 

 

 

你可能感兴趣的:(算法,64式)