动态规划(一)

动态规划有以下三类问题:
存在性问题
最值问题
计数问题

1.最值问题:

题目:有2元、5元和7元三种面值的硬币,如果想要拼成27元,怎么拼花费的硬币数量最少

暴力递归:

/*
	return :构成num最少的硬币数量
	param:  num:需要拼成的面值

*/
public int getMinCoin(int num){
	if(num == 0){
		return 0;
	}else if(num >0 && num <2){
		/*
			这里返回int的最大值是因为要求最少的硬币数,
			所以如果在递归过程中出现了int的最大值,肯定是不满足要求的结果,也就是把不能拼成27元的情况略去了
		*/
		return Integer.MAX_VALUE;
	}else if(num >=2 && num < 5){
		return getMinCoin(num -2); 
	}else if(num >=5 &&num <7){
		return Math.min(getMinCoin(num-2),getMinCoin(num-5));
	}else {
		//num >=7
		return Math.min(getMinCoin(num-2),Math.min(getMinCoin(num-5),getMinCoin(num-7)));
	}
} 

DP

/*
	coins:可用面值硬币的集合,比如在该案例中是2,5,7
	num:需要凑齐的面值
*/
public int getMinCoin(int[] coins,int num){
	//声明一个数组来记录已经计算过的结果
	int[] result = new int[num-1];
	//记录成功凑齐的最后结果init
	result[0] = 0;
	/*
		for循环中最好使用++i
		如果循环次数过大的话++i的性能会比i++好
		原因:
			不管是i++和++i,在for循环中只是自增作用,并没有把表达式赋给某一个变量
			所以在for循环执行顺序的话是一样的
			但是i++如果细分的话会比++i多一个赋值操作
			所以会浪费一些性能
	*/
	for(int i = 1;i < num;++i){
		result[i] = Integer.MAX_VALUE;
		//遍历硬币集合
		for(int j = 0;j<coins.length;++j){
			/*
				首先需要判断当前需要凑齐的面值是否大于硬币集合中的某一个值 --->num >= coins[j]
				如果大于某一个值,则可能被该硬币凑齐
				然后需要判断已经计算过的值result[i-coins[j]]是否可以被凑齐,也就是是否为Integer.MAX_VALUE
				如果是,还需要判断result[i-coins[j]]+1 < result[i],也就是当前凑齐的方式是不是比之前所有的凑齐方式硬币数少
				如果是,则更新result数组的值(这个值在某一个i值可能被多次更新,但是最后更新的值肯定是最少的硬币数)
			*/
			if(num >= coins[j] &&result[i-coins[j]] !=Integer.MAX_VALUE && result[i-coins[j]]+1 < result[i]){
				result[i] = result[i-coins[j]]+1;
			}
		}
	}
	return result[num-1] == Integer.MAX_VALUE ? -1 : result[num-1];
}

2.计数问题:

题目:给定m行n列的网格,有一个人从左上角坐标(0,0)开始出发,问有多少种方式走到右下角(只能向下走或者向右走)

暴力递归

/*
	return :到当前坐标共有多少种方式
	param:坐标值

	思路:
		如果有x种方式走到(m-1,n),y种方式走到(m,n-1)
		则通过x+y中方式走到(m,n)		
*/
public int total(int i,int j){
	if(i<0 || j<0){
		return 0;
	}else if(i==0 && j==0){
		//这里必须是1,不然后面全都是0
		return 1;
	}else{
		return total(i-1,j)+total(i,j-1);
	}
}

DP

public int total(int m,int n){
	int[][] result = new int[m][n];
	result[0][0] = 1;
	//计算顺序:按行计算数组
	for(int i = 0;i < m,++i){
		for(int j = 0; j < n;++j){
			if(i == 0 || j == 0){
				//处理边界
				result[i][j] = 1;
			}else{
				result[i][j] = result[i-1][j] + result[i][j-1];
			}
		}	
	return result[m-1][n-1];
	}



}

3.可行性分析

题目:有n块石头分别在x轴的0,1,2,... ,n-1位置
一只青蛙在石头0处
如果青蛙在第i块石头上,它最多往右跳ai个石头
问青蛙是否能够调到石头n-1处

DP

/*
	return:是否可以跳到n-1的石头上
	param: arr数组:
			大小为n,也就是n块石头
			数组中的值为处在第i个位置时,可以最多往右跳arr[i]个石头

*/
public boolean solution(int[] arr){
	if(arr == null || arr.length == 0){
		return false;
	}
		//石头数
		int length = arr.length;
		boolean[] result = new boolean[length];
		// init 初始化第一个石头
		result[0] = true;	
		//遍历石头
		for(int i = 1;i<length;++i ){
			//初始化是否能跳到这个石头
			result[i] = false;
			//遍历这个石头之前的所有石头
			for(int j = 0;j<i;++j){
				//如果能跳到i石头之前的某个石头j,而且在j石头上时最多能向右跳过的石头大于等于i与j之间的间距
				//说明能够调到第i个石头上,并break跳出该循环
				if(result[j]  && arr[j] >= i-j){
					result[i] = true;
					break;
				}
			}

		}
		return result[length-1];
}

猜你喜欢

转载自blog.csdn.net/xkshihaoren/article/details/88068806
今日推荐