动态规划入门 ① leetcode279 完全平方数

本文主要目的为向初学者介绍动态规划的思想。

什么是动态规划?
答:动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。(来源于百度百科)

动态规划的基本思想是什么?
答:动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。
    如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。(来源于百度百科)

看完了动态规划的基本思想,你会发现自己还是不会,接下来是一道例题:

下题为leetcode第279题:完全平方数
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

示例 1:

输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.
示例 2:

输入: n = 13
输出: 2
解释: 13 = 4 + 9.

这道题的解法有很多,我们先试着用普通递归的思路解一下: 假设f(n)为结果
易知f(1)=1;f(2)=2;f(3)=3;
当n=某一个数的平方时,易知f(n)=1;

当n=6时,f(6)的情况有2种——分别是f(1)+f(6-1)和f(4)+f(6-4),即f(1)+f(5)和f(4)+f(2)。那么f(6)的值便是这两者的最小值,即min( f(1)+f(5) , f(4)+f(2) );
情况1:f(1)的值=1,那f(5)的值等于多少呢?我们可以继续往下推:f(5)的情况有2种:f(1)+f(4)和f(4)+f(1);
情况1.1:f(1)=1,f(4)=1;那么f(5)=2;
情况1.2:f(5)=2;
最终f(5)的值为情况1.1和1.2中的最小值,即f(5)=2;
那么情况1的结构便是f(6)=3;
同理可计算出情况2的结果
…(此处省略)

接下来我们开始写代码:

class Solution {
    public int numSquares(int n) {
        if(n==1 || n==2 || n==3) return n;
		int temp = (int) Math.sqrt(n);
		if(temp*temp == n) return 1;
		
		int min = Integer.MAX_VALUE;
		for(int i=1;i*i <= n;i++){
			min = Math.min(min, numSquares(n-i*i)+1);
		}
		return min;
    }
}

这个代码是正确的,只不过时间复杂度太高(指数级别)。我们在leetcode上提交一下发现超时:
在这里插入图片描述
那应该如何改进我们的代码呢?
在普通递归中我们会发现,有很多值都被重复计算过很多次,我们可以使用记忆化搜索来改善上面的普通递归,使得这些值只被计算一次就被保存下来,大大降低时间复杂度。代码如下:

class Solution {
    static int[] arr;
    public int numSquares(int n) {
        arr = new int[n+1];//创建一个数组以保存计算出来的f(n)
		for(int i=0;i<n+1;i++)//初值为-1
			arr[i] = -1;
		return f(n);
    }
    public static int f(int n){
		if(arr[n] != -1) return arr[n];//当arr[n]已经被赋过值,便返回arr[n]。
		
		if(n==1 || n==2 || n==3) return n;
		int temp = (int) Math.sqrt(n);
		if(temp*temp == n) return 1;
		
		int min = Integer.MAX_VALUE;
		for(int i=1;i*i <= n;i++){
			min = Math.min(min, f(n-i*i)+1);
		}
		
		arr[n] = min;
		return arr[n];
	}
}

接下来,我们在leetcode上提交一下。
在这里插入图片描述
成功通过,这个记忆化搜索已经是很好的方法了,那还有没有更简洁、更快的方法呢?答案是有,那便是动态规划

上面两种递归方法都是通过“自顶向下”的思维来解决问题,而动态规划恰恰相反,它是通过“自底向上”来解决的,认清这一点很重要。 代码如下:

class Solution {
    public int numSquares(int n) {
        int[] arr = new int[n+1];//依然是设置数组来保存计算出的值
		arr[1] = 1;  //f(1)当然是1了
		for(int i=2;i<n+1;i++){//自底向上,依次计算出f(n)的值,并保存在arr[n]中
			int min = Integer.MAX_VALUE;
			
			for(int j=1;j*j <= i;j++)
				min = Math.min( min,arr[i-j*j]);
			
			arr[i] = min + 1;
		}
		return arr[n];
    }
}

试着提交一下,果然比记忆化搜索更快:
在这里插入图片描述

看完例题后,请再返回最顶部阅读一下动态规划的基本思想,相信会加深对动态规划的理解。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值