【重温经典】 完全平方数
方法1.1:BFS(从底向上)
- 准备一个队列
q
,装从0开始的数,将每一种当前的处理到的完全平方数和 vis
用来标记当前处理的完全平方数和是否被访问过,没有访问的话,添加到q
中等待访问- 从0开始,每一层叠加,当完全平方数和等于n的时候,这时候找到的层数
level
一定是最小的,即完全平方数的个数
public int numSquares(int n) {
Queue<Integer> q = new LinkedList<>();
Set<Integer> vis = new HashSet<>();
q.offer(0);
vis.add(0);
int level = 0;
while (!q.isEmpty()) {
int size = q.size();
level++;
while (size-- > 0) {
int cur = q.poll();
for (int i = 1; i * i <= n; i++) {
int next = cur + i * i;
if (next == n) return level;
if (next > n) break;
if (!vis.contains(next)) {
vis.add(next);
q.offer(next);
}
}
}
}
return level;
}
方法1.2:BFS(从顶向下)
- 从n开始反向思考,一直到达0结束
public int numSquares(int n) {
Queue<Integer> q = new LinkedList<>();
Set<Integer> vis = new HashSet<>();
q.offer(n);
int level = 0;
while (!q.isEmpty()) {
int size = q.size();
while (size-- > 0) {
int cur = q.poll();
if (cur == 0) return level;
for (int i = 1; i * i <= n; i++) {
int next = cur - i * i;
if (!vis.contains(next)) {
vis.add(next);
q.offer(next);
}
}
}
level++;
}
return level;
}
方法1.3:BFS(求备选的完全平方数)
- 将n的所有完全平方数先求出来,然后遍历的时候,只需要遍历这些完全平方数
public int numSquares(int n) {
List<Integer> squares = generateSquares(n);
Queue<Integer> queue = new LinkedList<>();
boolean[] marked = new boolean[n + 1];
queue.add(n);
marked[n] = true;
int level = 0;
while (!queue.isEmpty()) {
int size = queue.size();
level++;
while (size-- > 0) {
int cur = queue.poll();
for (int square : squares) {
int next = cur - square;
if (next < 0) {
break;
}
if (next == 0) {
return level;
}
if (marked[next]) {
continue;
}
marked[next] = true;
queue.add(next);
}
}
}
return -1;
}
private List<Integer> generateSquares(int n) {
List<Integer> squares = new ArrayList<>();
int base = (int) Math.sqrt(n);
for (int i = 1; i <= base; i++) {
squares.add((int) Math.pow(i, 2));
}
return squares;
}
方法2:DP
-
思路有点像「零钱兑换」的完全背包思想
-
f [ i ] f[i] f[i]表示 i i i这个数的最完美平方数的个数,如下面的逻辑(源国际站)
f[0] = 0
f[1] = f[0]+1 = 1
f[2] = f[1]+1 = 2
f[3] = f[2]+1 = 3
f[4] = Min{ f[4-1*1]+1, f[4-2*2]+1 }
= Min{ f[3]+1, f[0]+1 }
= 1
f[5] = Min{ f[5-1*1]+1, f[5-2*2]+1 }
= Min{ f[4]+1, f[1]+1 }
= 2
.
.
.
f[13] = Min{ f[13-1*1]+1, f[13-2*2]+1, f[13-3*3]+1 }
= Min{ f[12]+1, f[9]+1, f[4]+1 }
= 2
.
.
.
f[n] = Min{ f[n - i*i] + 1 }, n - i*i >=0 && i >= 1
public int numSquares(int n) {
int[] f = new int[n + 1];
int INF = Integer.MAX_VALUE >> 1;
Arrays.fill(f, INF);
f[0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j * j <= i; j++) {
f[i] = Math.min(f[i], f[i - j * j] + 1);
}
}
return f[n];
}
FollowUp:求一共有多少种可以形成n的结果集
- 问题变成求组合数的问题了,下面的代码求的是所有的组合数,未去除重复的排列
List<List<Integer>> res = new ArrayList<>();
List<Integer> squares;
public int numSquares(int n) {
this.squares = generateSquares(n);
dfs(n, new ArrayList<>());
for (List<Integer> r : res) {
System.out.println(JSON.toJSONString(r));
}
return -1;
}
private void dfs(int n, List<Integer> sub) {
if (n < 0) return;
if (n == 0) {
res.add(new ArrayList<>(sub));
return;
}
for (int x : squares) {
sub.add(x);
dfs(n - x, sub);
sub.remove(sub.size() - 1);
}
}
private List<Integer> generateSquares(int n) {
List<Integer> squares = new ArrayList<>();
int base = (int) Math.sqrt(n);
for (int i = 1; i <= base; i++) {
squares.add((int) Math.pow(i, 2));
}
return squares;
}