【算法】-- 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}

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

递归解法的问题:

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

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

【算法】-- LintCode经典算法题理解动态规划_第1张图片

 2、转移方程

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

对于任意x,有:

【算法】-- LintCode经典算法题理解动态规划_第2张图片

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

那么可以将方程的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]已经得到结果,可以直接使用(这里体现了动态规划的一个特点即动态的存储计算过的数据)。

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

【算法】-- LintCode经典算法题理解动态规划_第3张图片

每一步尝试三种硬币,一共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=a[j]&&f[i-a[j]]!=MAX)
				{
					if(f[i-a[j]]+1

二、Unique Paths

题目:

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

【算法】-- LintCode经典算法题理解动态规划_第4张图片

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),有方程:

【算法】-- LintCode经典算法题理解动态规划_第5张图片

 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

三、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=i){
				f[i]=true;
				break;
			}
		}
	}
	printf("%d",f[num-1]);

你可能感兴趣的:(算法设计与分析,算法,动态规划,面试,leetcode,c语言)