法一(动态规划)-较快
/**
* 法一(动态规划)-较快
* 1. 思路
* (1)本题可看做完全背包问题
* (2)完全平方数就是物品(可以无限件使用),凑个正整数n就是背包,问凑满这个背包最少有多少物品
* (3)背包容量是 n,物品是从 1 - √n,价值是从 1 - √n的完全平方
* 2. 步骤
* (1)确定dp数组以及下标的含义
* dp[i]表示通过平方数组成i所需要完全平方数的最少价值
* (2)确定递推公式
* dp[i]可由dp[i - j * j]推出,dp[i - j * j] + 1便可以凑成dp[i]
* 需要选择最小的dp[i],故dp[i] = min(dp[i], dp[i - j * j] + 1)
* (3)确定dp数组如何初始化
* dp[0]初始化为0,dp[i](i>0)初始化为最大值,这样dp[i]在递推的时候才不会被初始值覆盖
* (4)确定遍历顺序
* 可以外层for遍历背包,内层for遍历物品
* 3. 复杂度
* (1)时间复杂度 0(n * √n)
* (2)空间复杂度 0(n)
*
* @param n
* @return
*/
public int numSquares(int n) {
int[] dp = new int[n + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for (int i = 1; i <= n; i++) { // 遍历背包,依次求出1, 2...直到n的解
for (int j = 1; j * j <= i; j++) { // 遍历物品,依次减去一个平方数
dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
}
}
return dp[n];
}
法二(BFS)-较快
/**
* 法二(BFS)-较快
* 1. 思路
* (1)一层一层的算,第一层依次减去一个平方数得到第二层,第二层依次减去一个平方数得到第三层...
* (2)直到某一层出现了0,此时的层数就是我们要找到平方数和的最小个数
* 2. 复杂度
* (1)时间复杂度 0(n^(h/2)),h是可能发生的最大递归次数
* (2)空间复杂度 0((√n)^h),这也是在h级可以出现的最大节点数
*
* @param n
* @return
*/
public int numSquares_2(int n) {
Queue<Integer> queue = new LinkedList<>();
Set<Integer> visited = new HashSet<>(); // 记录重复的解
int level = 0;
queue.add(n);
while (!queue.isEmpty()) {
int size = queue.size();
level++;
for (int i = 0; i < size; i++) {
int cur = queue.poll();
for (int j = 1; j * j <= cur; j++) {
int next = cur - j * j;
if (next == 0) {
return level;
}
if (!visited.contains(next)) {
queue.offer(next);
visited.add(next);
}
}
}
}
return -1;
}
法三(DFS)-最慢
/**
* 考虑所有的分解方案,找出最小的解
*
* @param n
* @param map
* @return
*/
private int dfs(int n, Map<Integer, Integer> map) {
if (map.containsKey(n)) { // 跳过重复的解
return map.get(n);
}
if (n == 0) {
return 0;
}
int count = Integer.MAX_VALUE;
for (int i = 1; i * i <= n; i++) {
count = Math.min(count, dfs(n - i * i, map) + 1);
}
map.put(n, count);
return count;
}
/**
* 法三(DFS)-最慢
* 一个一个的找,一直做减法,直到减到0算作找到一个解
*
* @param n
* @return
*/
public int numSquares_3(int n) {
return dfs(n, new HashMap<>());
}
法四(数学)-最快
/**
* 判断是否是平方数
*
* @param n
* @return
*/
private boolean isSquare(int n) {
int sqrt = (int) Math.sqrt(n);
return sqrt * sqrt == n;
}
/**
* 法四(数学)-最快
* 1. 思路
* (1)四平方和定理:任何一个整数都可以表示为不超过4个数的平方和
* (2)推论:当且仅当n=4^a*(8b+7)时,n恰好可以表示为4个数的平方和,a和b都是非负整数
* 2. 复杂度
* (1)时间复杂度 0(√n)
* (2)空间复杂度 0(1)
*
* @param n
* @return
*/
public int numSquares_4(int n) {
if (isSquare(n)) { // 考虑答案是不是1
return 1;
}
int temp = n;
while (temp % 4 == 0) { // 考虑答案是不是4,也就是判断n是不是等于4^a*(8b+7)
temp /= 4;
}
if (temp % 8 == 7) {
return 4;
}
for (int i = 1; i * i < n; i++) { // 然后考虑答案是不是2
if (isSquare(n - i * i)) { // 当前数依次减去一个平方数,判断得到的差是不是平方数
return 2;
}
}
return 3; // 以上情况都排除的话,答案就是3
}
本地测试
/**
* 279. 完全平方数
*/
lay.showTitle(279);
Solution279 sol279 = new Solution279();
System.out.println(sol279.numSquares(12));
System.out.println(sol279.numSquares_2(12));
System.out.println(sol279.numSquares_3(12));
System.out.println(sol279.numSquares_4(12));