思路一:就是搜索問題,
在數據中查找滿足某種條件的組合
,根據組合問題
則可以採用遞歸+回溯
的深度優先搜索算法
/**
* 回溯法:
* 相當於一種暴力法:考慮所有的分解方案,找出最小的解
* 超出時間限制
*/
public int numSquares(int n) {
return numSquaresHelper(n,new HashMap<>());
}
//暴力回溯
private int numSquaresHelper(int n) {
if (n == 0) {
return 0;
}
int count = Integer.MAX_VALUE;
//依次減去一個平方數
for (int i = 1; i * i <= n; i++) {
//選最小的
count = Math.min(count, numSquaresHelper(n - i * i) + 1);
}
return count;
}
上面可能存在
時間超時的問題
,同時也存在重複計算的問題,可以採用備忘錄
的模式,在遞歸的過程中,將一些中間結果f(n)
保存,再次求f(n)
值的時候可以直接取而不用重新計算
/**
* 回溯法改進:保存之前計算過得值
* 當然上邊的會造成很多超時,很多解會重複的計算,之前也遇到了很多這種情況
* 我們需要memoization技術,也就是把過程中的解利用HashMap全部保存起來
* @param n
*/
private int numSquaresHelper(int n, HashMap<Integer,Integer>map) {
if(n==0) return 0;
//使用HashMap保存
if(map.containsKey(n)) return map.get(n);
int count = Integer.MAX_VALUE;
for(int i=1;i*i<=n;i++){
int size = numSquaresHelper(n-i*i,map)+1;
count = Math.min(count,size);
}
map.put(n,count);
return count;
}
思路二:上面的遞歸是自頂向下的方法,存在大量的重複計算,思考是否可以採用
動態規劃
,那麼就需要思考狀態轉移方程,以及狀態定義和狀態初始值
,dp[i]=Math.min(dp[i-j*j]+1,dp[i])
/**
* 更進一步採用動態規劃
* @param n
* dp[i] 構成 數字i的完全平方數的個數,
* dp[i] = Math.min(dp[i-j*j])+1 其中j處於1和sqrt(i)之間
*/
public int numSquares1(int n) {
if(n==0) return 0;
int[] dp = new int[n+1];
dp[0]=0;
dp[1]=1;
for(int i=2;i<n+1;i++){
dp[i]=Integer.MAX_VALUE;
for(int j=1;j*j<i;j++){
dp[i]=Math.min(dp[i],dp[i-j*j]+1);
}
}
return dp[n];
}
思路三:
上面都是深度優先搜索,一條路先走到底,接着嘗試走其他路
,可以採用廣度優先搜索BFS
,多條路一起走,DFS 是一直做減法,然後一直減一直減,直到減到 0 算作找到一個解。屬於一個解一個解的尋找。BFS 的話,我們可以一層一層的算。第一層依次減去一個平方數得到第二層,第二層依次減去一個平方數得到第三層。直到某一層出現了 0,此時的層數就是我們要找到平方數和的最小個數。
/**
* 採用BFS
* @param n
* @return
* 相對於之前的DFS,當然可以使用BFS
* DFS 是一直做減法,然後一直減一直減,直到減到 0 算作找到一個解。屬於一個解一個解的尋找。
BFS 的話,我們可以一層一層的算。第一層依次減去一個平方數得到第二層,第二層依次減去一個平方數得到第三層。直到某一層出現了 0,此時的層數就是我們要找到平方數和的最小個數。
*/
public int numSquares2(int n){
Queue<Integer> queue = new LinkedList<>();
HashSet<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();
//依次減去1,4,9......生成下一層節點
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;
}