【算法】-- LintCode经典算法题理解动态规划

动态规划有多重要?

  • 科技公司面试必考算法
  • 根据面试经验,一半失败的面试都与动态规划有关

动态规划题目特点

1、计数题

        -有多少种方式走到右下角

        -有多少种方法选出k个数使得和是Sum

2、求最大最小值

        -从左上角走到右下角路径的最大数字和

        -最长上升子序列长度

3、求存在性

        -取石子游戏,先手是否必胜

        -能不能选出k个数使得和是Sum

动态规划的特点

1、将原问题分解为相似的子问题

2、所有的子问题都只需解一次(即存储解过的问题)

3、使用空间换时间

4、自底向上求解

目录

一、Coin Change题

1、确定状态

最后一步

子问题

 2、转移方程

3、初始条件和边界情况

 4、计算顺序

5、代码

二、Unique Paths

1、子问题

2、转移方程

 3、初始条件和边界情况

初始条件

边界情况

4、计算顺序

5、代码

三、Jump Game

1、确定状态

2、转移方程

3、初始条件和边界情况

初始条件

边界情况

4、计算顺序

5、代码


一、Coin Change题

科大讯飞笔试题

题目:

有三种硬币,分别面值2元、5元和7元,每种硬币都有足够多,现在买一本书需要27元,问如何用最少的硬币组合正好付清,不需要对方找钱?

你是否同意下面的想法?

题目要求用最少的硬币,也就是尽量用大的硬币,最后用一种硬币付清即可;

如:7+7+7=21,21+2+2+2=27;

用了6枚硬币。

但正确答案是:7+5+5+5+5=27,共5枚硬币。

而正确的做法是使用动态规划解,如何使用动态规划?动态规划的组成部分有哪些?

1、确定状态

确定状态即使用一个数组,数组的每个元素f[i]或f[i][j]代表什么。

确定状态需要做两件事:最后一步、子问题。

最后一步

先忽略解题的最优策略是什么,但最优策略肯定是K枚硬币a1...ak加起来等于27;

所以必存在一枚最后的硬币:ak;

去掉这枚硬币,前面的硬币加起来就是27-ak。

由以上说法可以得出两个关键点。

1、不关心前面的k-1枚硬币是如何拼出27-ak的,但是确定前面的硬币拼出了27-ak。

2、拼出的27-ak硬币数一定最少,否则就不是最优策略了。

子问题

所以可以将原问题“最少用多少枚硬币拼出27”转换为子问题“最少用多少枚硬币可以拼出27-ak”。

为了简化,可以设状态f(x)=最少用多少枚硬币拼出x。

现在还不知道最后那枚硬币ak是多少?

ak只可能是2、5或7;

如果ak=2,f(27)=f(27-2)+1        (f表示的是个数,所以加上最后一枚硬币是+1)

如果ak=5,f(27)=f(27-5)+1

如果ak=7,f(27)=f(27-7)+1

需要求最少的硬币数,所以可得出以下公式:

f(27)=min{f(27-2)+1,f(27-5)+1,f(27-7)+1}

看到上面的公式,一定有人问:为什么不用递归?

递归解法的问题:

由图可得:递归解法做了很多重复计算,效率低下。

那么如何避免重复计算的问题?将计算结果保存下来,并改变计算顺序。

 2、转移方程

设状态f[x]=最少用多少枚硬币拼出x

对于任意x,有:

现在思考一下,如果要求使用最多的硬币拼呢?

那么可以将方程的min改为max

 如果写出了转移方程,那么就成功了一半。

仅仅写出转移方程还是不够,在编写程序前还需做两件事。

3、初始条件和边界情况

在得出方程后衍生两个问题:如果x-2、x-5、x-7小于0怎么办?什么时候停下?

如果不能拼出Y,就定义f[Y]=正无穷,此处的正无穷是尽可能大的数。

初始条件:f[0]=0,因为方程不能得出x=0的正确答案,需要手动定义。

 4、计算顺序

从初始后开始计算;

初始条件:f[0]=0;

然后计算f[1]....f[27]

当计算到f[x]时,前面的f[x-2]、f[x-5]、f[x-7]已经得到结果,可以直接使用(这里体现了动态规划的一个特点即动态的存储计算过的数据)。

使用动态规划解此题过程如下:

每一步尝试三种硬币,一共27步。

如果有n种硬币,拼m块硬币

动态规划解此题的时间复杂度:n*m

而递归时间复杂度是指数级别,要>>n*m

5、代码


    int a[MAX],m;//a存放硬币种类,M为求解的总硬币数
	int n;
	int i,j;
	int f[m+1];
	f[0]=0;
	for(i=1;i<=m;i++){
		f[i]=MAX;
		for(j=0;j<n;j++){
			if(i>=a[j]&&f[i-a[j]]!=MAX)
				{
					if(f[i-a[j]]+1<f[i])
						f[i]=f[i-a[j]]+1;
				}
		}
	}
	if(f[m]==MAX){
		return -1;
	}
	printf("%d",f[m]);		
}

二、Unique Paths

题目:

给定m行n列网格,有一个机器人从左上角(0,0)出发,每一步可以向下或向右走一步,问有多少种不同的方式走到右下角?

1、子问题

如果机器人有X种方式从左上角走到(m-2,n-1),有Y种方式从左上角走到(m-1,n-2),则机器人有X+Y种方式走到(m-1,n-1)。

可以由原题“有多少种方式从左上角走到(m-1,n-1)”转化为子问题“机器人有多少种方式从左上角到(m-2,n-1)和(m-1,n-2)”

2、转移方程

对于任意一个格子(i,j),有方程:

 3、初始条件和边界情况

初始条件

f[0][[0]=1,因为机器人只有一种方式到左上角

边界情况

i=0或j=0(即第一行和第一列),前一步只能有一个方向过来,使f[0][j]=1,f[i][0]=1。

4、计算顺序

f[0][0]=1

计算第0行:f[0][0]、f[0][1]....f[0][n-1]

....

计算第m-1行:f[m-1][0]、f[m-1][1]......f[m-1][n-1]

答案为:f[m-1][n-1]

为什么要按行计算?

因为对于任意一个格子,如果按行计算,那么它的上边和左边的格子都已经计算过了。

时间复杂度:O(MN)

5、代码

    int n,m;
	int f[m][n];
	int i,j;
	for(i=0;i<m;i++){
		for(j=0;j<n;j++){
			if(i==0||j==0)//第一行和第一列是边界情况,因为第一行的格子只能由左方向来,第一列只能从上来 
				f[i][j]=1;
			else
				f[i][j]=f[i-1][j]+f[i][j-1];
		}
	}
	printf("%d",f[m-1][n-1]); 

三、Jump Game

网易笔试题

题目:
有n块石头分别在0,1,.....,n-1位置,有一只青蛙在石头0,想跳到石头n-1,如果青蛙在第i块石头上,它最多可以向右条距离ai,问青蛙能否跳到石头n-1?

例:

输入:a[2,3,1,1,4]

输出:True

输入:a=[3,2,1,0,4]

输出:False

此题可用动态规划也可用其他算法如贪心。

1、确定状态

假设存在一个石头i可以跳到石头n-1,则可将原问题“能不能跳到石头n-1”转换为子问题“能不能跳到石头i”。

设状态f[j]表示能不能跳到石头i。

2、转移方程

思考如何判断是否能跳到石头i?

需要考虑以下步骤:

1、枚举i前面的石头j

2、判断前面的石头能否跳到石头j(如果前面的都跳不到j,那j能跳到i也没用了)

3、如果j有效,则判断j+a[j]>=i,否则跳不到i

3、初始条件和边界情况

初始条件

f[0]=True,因为青蛙一开始就在石头0

边界情况

无边界情况,不会越界。

4、计算顺序

初始化f[0]=True

计算f[1],f[2]....f[n-1]

答案是f[n-1]

时间复杂度:O(N^2)

5、代码

    int a[num];
	int i,j;
	bool f[num];
	f[0]=true;
	for(i=1;i<num;i++){
		f[i]=false;
		for(j=0;j<i;j++){
			if(f[j]&&j+a[j]>=i){
				f[i]=true;
				break;
			}
		}
	}
	printf("%d",f[num-1]);

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

四月天行健

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

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

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

打赏作者

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

抵扣说明:

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

余额充值