[LeetCode] 279. Perfect Squares 解题报告

Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, ...) which sum to n.

For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9.


这一题目测方法很多,我的第一感觉就是使用动态规划来做。

方法一:

传统动态规划。

首先我们需要一个辅助数组,dp[n+1],存储针对当前数字,最小需要几个完全平方数来拼凑。

动态规划的 方程是:

dp[k]=min(dp[k-i]+dp[i],current_max_value)

针对上面的方程的解释是:对于当前数字k,给定变量i,从1遍历到k-1,获取dp[k-i]+dp[i]的最小值,作为dp[k]的值。依次类推,对于k从2遍历到n,最后返回dp[n]即可。


值得注意的几点:

  1. 辅助数组初始化的时候需要判断当前的数本身是不是完全平方数,如果是的话,dp[k]置为1。至于如何判断是否为完全平方数,遍历1-n并不是一个明智的选择,因为大部分的数组都不是完全平方数。因此,需要一个while循环,给定变量k,从1开始,dp[k]=1,k++,直到k*k>n,结束就好。
  2. 在计算dp[k-i]+dp[i]的时候,实际上如果有一个等于0,说明不可能凑成,dp[k]应该是等于0的。
  3. 在有额外存储空间的情况下,不要企图利用递归直接输出dp[n],因为递归深度过深,会造成栈溢出,后面会有一个典型的错误案例。

代码如下,复杂度大致为O(N^2):

public class Solution {
		int[] nDp;

	public int numSquares(int n) {
		nDp = new int[n + 1];
		// init nDP, if it is perfect square, set it to 1
		int nIndex = 1;
		while (true) {
			int nCur = nIndex * nIndex;
			if (nCur <= n) {
				nDp[nCur] = 1;
			} else {
				break;
			}
			nIndex++;
		}
		for (int i = 1; i < nDp.length; i++) {
			dp(i);
		}
		return nDp[n];
	}

	private int dp(int n) {
		if (nDp[n] > 0) {
			return nDp[n];
		} else {
			int nMax = Integer.MAX_VALUE;
			for (int i = 1; i <= n / 2; i++) {
				int nDpx = nDp[i];
				int nDpy = nDp[n - i];
				if (nDpx == 0 || nDpy == 0) {
					nMax = Math.max(0, nMax);
				} else {
					nMax = Math.min(nDpx + nDpy, nMax);
				}
			}
			nDp[n] = nMax;
			return nMax;
		}
	}
}

另外一点很重要的,在上面的29行的for循环中,有一个很重要的优化,把i换成了i*i(原因和上面的第一点一样),因为大部分都不是完全平方数,这样可以避免重复计算

			for (int i = 1; i*i <= n / 2; i++) {
				int nDpx = nDp[i*i];
				int nDpy = nDp[n - i*i];
				if (nDpx == 0 || nDpy == 0) {
					nMax = Math.max(0, nMax);
				} else {
					nMax = Math.min(nDpx + nDpy, nMax);
				}
			}



栈溢出错误例子(StackOverflowError):

public class Solution {
public class Solution {
	int[] nDp;

	public int numSquares(int n) {
		nDp = new int[n + 1];
		for (int i = 0; i < nDp.length; i++) {
			nDp[i] = -1;
		}
		// init nDP, if it is perfect square, set it to 1
		int nIndex = 1;
		while (true) {
			int nCur = nIndex * nIndex;
			if (nCur <= n) {
				nDp[nCur] = 1;
			} else {
				break;
			}
			nIndex++;
		}
		return dp(n);
	}

	private int dp(int n) {
		if (nDp[n] != -1) {
			return nDp[n];
		} else {
			int nMax = Integer.MAX_VALUE;
			for (int i = 1; i <= n / 2; i++) {
				int nDpx = dp(i);
				int nDpy = dp(n - i);
				if (nDpx == 0 || nDpy == 0) {
					nMax = Math.max(0, nMax);
				} else {
					nMax = Math.min(nDpx + nDpy, nMax);
				}
			}
			nDp[n] = nMax;
			return nMax;
		}
	}
}

可以看到上面代码的21行,直接调用的dp(n),也就是说n前面的大部分数都没有算(大部分都是非完全平方数),遇到了会继续递归,会造成递归层数过多,函数递归是保存在栈中的,栈溢出。这样做的话,也没有充分的使用辅助数组dp[]。



方法二:

定理法:请参考,四平方和定理 (英语:Lagrange's four-square theorem) 说明每个正整数均可表示为4个整数平方和。它是费马多边形数定理华林问题的特例,和拉格朗日三平方和定理 Legendre's three-square theorem。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值