0 前言:
递归本质是把复杂问题分解成多个简单小问题。但是如果小问题中有相互重叠部分,时间效率会很差。所以应该是以递归的思想用循环的方法实现(从上往下分析问题,从下往上求解问题)。
动态规划:求解最优解时,分解大问题为小问题,然后求每个小问题的最优解,把子问题最优解存储下来(一维或二维数组)。
动态规划方法仔细安排求解顺序,对每个子问题只求解一次,并将结果保留下来.如果随后再次需要此子问题的解,只需要查找保存的结果,而不必重新计算.因此,动态规划是付出额外的内存空间来节省计算时间,是典型的时空权衡.
- 10 斐波那契数列
- 14 剪绳子
- 42 求连续子数组的最大和
- 47 礼物的最大价值
- 48 最长不含重复字符的子字符串
- 60 n个骰子的点数
- 动态规划解决TSP问题(代码可运行)
10、斐波那契数列
现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。1,1,2,3,5,8......除了第一项和第二项为1外,对于第N项,有F(N) = F(N - 1) + F(N - 2)。
我们先看一下暴力求解,其时间复杂度为O(N^2):递归问题,进入一个方法,先写出一个终止条件,然后根据题目,找出递推关系,进行递归。
public static int f1(int n) {
if(n < 1){
return 0;
}
if(n == 1 || n == 2){
return 1;
}
return f1(n - 1) + f1(n - 2);
}
当然我们可以优化成时间复杂度为O(N)
class Solution {
public:
int Fibonacci(int n) {
if(n<2)
return n;
//基于递归的循环、从下向上。
int a=0,b=1,c=0;
for(int i=2;i<=n;i++){
c=a+b;
a=b;//第一个更新
b=c;//第二个更新
}
return c;
}
};
14、剪绳子
题目:给定一根长度为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.
为了避免重复计算子问题,我们采用自下而上、从小到大的方式来求解,把先求到的子问题的解储存起来,之后要用到的时候直接进行查表。
- 动态规划(O(n^2)时间+O(n)空间)
#include <stdio.h>
#include <vector>
#include <iostream>
using namespace std;
int cutRope(int length){
if(length<2)return 0;
if(length==2)return 1;
if(length==3)return 2;
//创建vector,保留每个长度下最大乘积,从下向上,
vector<int> result{0,1,2,3};//0123基本量
int max=0;
for(int i=4;i<=length;i++)
{
for(int j=1;j<=(i/2);j++)
{
int temp=result[j]*result[i-j];//这里的f(j)与f(i-j)都已算出过。
if(temp>max){
max=temp;//更新max
}
}
result.push_back(max);//更新result,相当于建立一维的f(i)最优值
}
return result[length];
}
int main()
{
cout<<"Please enter the rope length"<<endl;
int n;
while(cin>>n){
cout<<cutRope(n)<<endl;
}
return 0;
}
- 贪心算法(O(1))
根据数学计算,当n>=5时,2(n-2)>n,3(n-3)>n,这就是说,将绳子剪成2和(n-2)或者剪成3和(n-3)时,乘积大于不剪的乘积,因此需要把绳子剪成2或者3。并且3(n-3)>=2(n-2),也就是说,当n>=5时,应该剪尽量多的3,可以使最后的乘积最大。对于长度是n的绳子,我们可以剪出n/3个3,剩余长度是1或者2,如果余数是1,就可以把1和最后一个3合并成4,那么4剪出两个2得到的乘积是4,比1*3大,因此这种情况下,需要将3的个数减少1,变成两个2;如果余数是2,那么无需做修改。
可以得到最大的乘积是:3^timesOf3 * 2^timesOf2
int maxProductAfterCutting_solution2(int length)
{
if(length < 2)
return 0;
if(length == 2)
return 1;
if(length == 3)
return 2;
// 尽可能多地减去长度为3的绳子段
int timesOf3 = length / 3;
// 当绳子最后剩下的长度为4的时候,不能再剪去长度为3的绳子段。
// 此时更好的方法是把绳子剪成长度为2的两段,因为2*2 > 3*1。
if(length - timesOf3 * 3 == 1)
timesOf3 -= 1;
int timesOf2 = (length - timesOf3 * 3) / 2;
return (int) (pow(3, timesOf3)) * (int) (pow(2, timesOf2));
}
42、求连续子数组的最大和
题目:输入一个整型数组,数组里有正数也有负数。数组中一个或连续的多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为O(n)。例如输入的数组为{1,-2,3,10,-4,7,2,-5},和最大的子数组为{3,10,-4,7,2},因此输出为该子数组的和18。、
动态规划
1、状态方程 : max( dp[ i ] ) = getMax( max( dp[ i -1 ] ) + arr[ i ] ,arr[ i ] )
2、上面式子的意义是:我们从头开始遍历数组,遍历到数组元素 arr[ i ] 时,连续的最大的和 可能为 max( dp[ i -1 ] ) + arr[ i ] ,也可能为 arr[ i ] ,做比较即可得出哪个更大,取最大值。时间复杂度为 n。其中,dp[i]表示以第i个数字结尾的子数组的最大和。
class Solution {
public:
int FindGreatestSumOfSubArray(vector<int> array) {
if(array.empty())return 0;
int maxSum=-10000,currSum=0;//currSum是函数f(i),表示以i结尾的子数组的最大和。
for(int i=0;i<array.size();i++){
//更新函数f(i)
if(currSum<=0){
currSum=array[i];
}
else{
currSum+=array[i];
}
//比对记录并保存最大值。
if(currSum>maxSum)maxSum=currSum;
}
return maxSum;
}
};
47、礼物的最大价值
在一个m * n的棋盘的每一个格都放有一个礼物,每个礼物都有一定价值(大于0)。从左上角开始拿礼物,每次向右或向下移动一格,直到右下角结束。给定一个棋盘,求拿到礼物的最大价值。例如,对于如下棋盘
1 10 3 8
12 2 9 6
5 7 4 11
3 7 16 5
礼物的最大价值为1+12+5+7+7+16+5=53。
- 动态规划(O(n^2)时间+O(n)空间)
定义一个函数f(i,j)f(i,j)表示达到坐标(i,j)(i,j)的格子时能够拿到礼物总和的最大值
#include <vector>
#include <iostream>
#include <string>
#include <stdlib.h>
#include <algorithm>
#include <string.h>
using namespace std;
int maxGift(const int *val,int rows,int cols)
{
if(val==nullptr||rows<=0||cols<=0)return 0;
//创建一维数组保存礼物最大值,该数组会一个一个更新
int* values=new int[cols];
for(int i=0;i<=rows;i++){
for(int j=0;j<cols;j++){
int up=0,left=0;//全局变量
if(i!=0)up=values[j];//当前(即为上面的)
if(j!=0)left=values[j-1];//前一个(左一个)
values[j]=max(left,up)+val[i*cols+rows];
}
}
int x=values[cols-1];
delete []values;
return x;
}
int main()
{
int *val = new int[16];
val[0] = 1;val[1] = 10;
val[2] = 3;val[3] = 100;
val[4] = 12;val[5] = 2;
val[6] = 9;val[7] = 6;
val[8] = 5;val[9] = 7;
val[10] = 4;val[11] = 11;
val[12] = 3;val[13] = 7;
val[14] = 16;val[15] = 5;
cout << maxGift(val,4,4) << endl;
return 0;
}
48:最长不含重复字符的子字符串
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。假设字符串中只包含′a′′a′~′z′′z′的字符。例如,在字符串"arabcacfr""arabcacfr"中,最长的不含重复字符的子字符串是"rabc""rabc"或者"acfr""acfr",长度为4.
- 动态规划()
定义函数f(i)表示第i个字符为结尾的不包含重复字符的子字符串的最大长度。
1)若当前字符第一次出现,则最长非重复子字符串长度f(i) = f(i-1)+1。
2)若当前字符不是第一次出现,则首先计算当前字符与它上次出现位置之间的距离d。若d大于f(i-1),即说明前一个非重复子字符串中没有包含当前字符,则可以添加当前字符到前一个非重复子字符串中,所以,f(i) = f(i-1)+1。若d小于或等于f(i-1),即说明前一个非重复子字符串中已经包含当前字符,则不可以添加当前字符,所以,f(i) = d。
#include <stdio.h>
#include <vector>
#include <string>
#include <iostream>
using namespace std;
int func(const string &str)
{
if(str.empty())return 0;
int* position=new int[26];//每个字母在字符串的位置
for(int j=0;j<26;j++)position[j]=-1;
int maxlength=0,currlength=0;
for(int i=0;i<str.size();i++){
int preIndex=position[str[i]-'a'];//上一次出现位置
if(preIndex<0||i-preIndex>currlength){//没出现过或者d>f(i-1),f(i)=f(i-1)+1
currlength++;
}
else{//出现过且d<f(i-1),f(i)=d
currlength=i-preIndex;
}
position[str[i]-'a']=i;//更新该字母在字符串中位置
//每一个字符都要进行比对,更新最大长度。
if(maxlength<currlength)maxlength=currlength;
}
delete[] position;
return maxlength;
}
int main()
{
cout<<"Please enter a string"<<endl;
string str;
while(cin>>str&&str!="q"){
cout<<func(str)<<endl;
cout<<"Please enter q to quit"<<endl;
}
return 0;
}