问题描述
- 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
, return3
because12 = 4 + 4 + 4
; givenn = 13
, return2
because13 = 4 + 9
. - 地址
问题分析
- 该题是给定一个数字
n
,看能用最少数目的平方数加出n
,求平方数的最少数目。 - 首先明确一个概念,所有的数字都能被平方数加出来,因为平方数中含有 1
- 对于该题,有两种解法:
BFS
将问题抽象成一个树,严格而言是一个图,若当前数时curSum
,那么能加的平方数,必须满足加之后不超过n
,所以这就是当前节点对应的若干选择,也便是树的分叉。根据BFS,也是层序遍历从上到下遍历的概念,一旦某一层第一次加到n
,那么该层层数一定用到了最少数目的平方数。如何知道当前节点的层数(也就是从0加到当前节点值用了多少个平方数)呢?
将当前值与 用了多少个平方数 包装成一个类即可。也可用层序遍历那种方法但是用这种 BFS 会超时,因为存在大量的冗余计算,比如 10 可以由 2 + 8 得到,可以由 2 + 2 + 2 + 2 + 2得到,甚至可以由 10 个 1 相加得到。而对于这些10,都会进行下一步的 BFS,而我们只关心的是所用平方数最少(树深度最小)的那一个,所以设置一个
visited
数组,visited[i]
用于记录i
值是否已经访问过(是否已经入队),若访问过,便不再访问。
- 动态规划
- 因为是求最优问题,所以自然而然想到动态规划。用
dp[i]
表示 从i
加到n
所用的最少平方数数目,那么初始条件为dp[n] = 0
,从后向前填这个dp
数组,dp[0]
即为所求。关于依赖关系,dp[i] = 1 + min(dp[i + j*j]), i + j * j <= 0
。
- 因为是求最优问题,所以自然而然想到动态规划。用
- 以上 BFS和动态规划的思想都是一种“加法”思想,从 0 加到 n 最少需要多少个平方数,减法思想类似,从 n 减到 0 最少需要多少个平方数
- 也有人往贪心那里想,其实不对,比如如果用贪心 ,
12 = 9 + 1 + 1 + 1
,事实上是12 = 4 + 4 + 4
经验教训
- 一个题,能同时用 BFS 与 动态规划,这道题具有什么特点
代码实现
- BFS(加法思想)
public int numSquares(int n) {
if (n <= 0) {
return 0;
}
boolean[] visited = new boolean[n];
LinkedList<Node> queue = new LinkedList<>();
//初始化 queue 与 visited
queue.add(new Node(0, 0));
visited[0] = true;
while (! queue.isEmpty()) {
Node popNode = queue.pop();
for (int i = 1; ; ++i) {
int curValue = popNode.val + i * i;
if (curValue == n) {
//加出了n
return popNode.depth + 1;
}
if (curValue <= n && ! visited[curValue]) {
//当前值还未到 n 并且还没有被访问过(被添加进队列)
queue.add(new Node(curValue, popNode.depth + 1));
visited[curValue] = true;
}
if (curValue > n) {
break;
}
}
}
return 0;
}
//表示当前值val是经过几步加出来的
class Node {
int val;
int depth;
public Node(int val, int depth) {
this.val = val;
this.depth = depth;
}
}
- BFS(减法思想)
public int numSquares(int n) {
if (n <= 0) {
return 0;
}
boolean[] visited = new boolean[n + 1];
LinkedList<Node> queue = new LinkedList<>();
queue.add(new Node(n, 0));
visited[n] = true;
while (! queue.isEmpty()) {
Node popNode = queue.remove();
int i = 1;
while (true) {
int curValue = popNode.val - i * i;
if (curValue > 0 && ! visited[curValue]) {
queue.add(new Node(curValue, popNode.depth + 1));
visited[curValue] = true;
}
if (curValue == 0) {
return popNode.depth + 1;
}
if (curValue < 0) {
break;
}
++i;
}
}
return 0;
}
- 动态规划
public int numSquares(int n) {
if (n <= 0) {
return 0;
}
//dp[i] 表示从 i 加到 n 还需要加几个数
int[] dp = new int[n + 1];
//初始化
dp[n] = 0;
//从后向前填
for (int i = n - 1; i >= 0; i--) {
int value = Integer.MAX_VALUE;
//对于 dp[i]枚举所有可能性,取最小的一个,然后加上当前的选定的平方数贡献的 1
for (int j = 1; i + j * j <= n; j++) {
value = Math.min(value, dp[i + j*j]);
}
//
dp[i] = value + 1;
}
return dp[0];
}