平方拆分(dfs)
1. 問題描述:
將 2019 拆分為若干個兩兩不同的完全平方數之和,一共有多少種不同的方法?注意交換順序視為同一種方法,例如 132 + 252 + 352 = 2019 與 132 + 352 +252 = 2019 視為同一種方法。
【答案提交】
這是一道結果填空的題,你只需要算出結果後提交即可。本題的結果為一個整數,在提交答案時只填寫這個整數,填寫多餘的內容將無法得分。
2. 思路分析:
① 分析題目可以知道我們事先是不知道有多少個數字的平方相加得到2019的,所以最容易想到的方法是dfs搜尋全部的可能數字的平方和從而得到最終的結果,因為是2019所以平方的數字最多是45,而且是不能夠到達45的,所以我們的任務是在這45個數字中選取若干個數字,使得他們的平方和最終的結果是2019。使用dfs解決有兩種思路。
② 第一種思路:我們從數字1出發,考慮選取當前的數字與不選取當前的數字的情況,對於每一個數字都是這樣考慮所以是存在兩種平行狀態的,在遞迴的時候我們可以寫兩個dfs方法,一個是選取當前的數字,另外一個是不選取當前的數字遞迴下去就可以搜尋全部可能的方案,因為是數字的平方和與相加的順序是無關的,所以在當遞迴當前的數字i之後,下一次遞迴的數字為i + 1這樣遞迴下去平方數之和的數字才不是重複的(保證下一次遞迴的數字是比前一次遞迴的數字是大1的:相鄰的數字是不重複的),遞迴的出口是當平方和大於2019或者是遞迴的數字大於45的時候那麼就可以return了(返回到上一層),當平方和等於2019那麼就可以對方法數目加1,並且return返回到上一層。我們可以在dfs方法中傳遞一個記錄中間結果的引數檢驗一下結果是否是正確的,對於java語言可以使用List<Integer>,python可以使用List[int](列表)記錄,當平方和滿足2019的時候可以輸出中間結果。下面是選取與不選取當前數字的程式碼(選取當前數字那麼需要先在記錄的List添加當前的數字當遞迴退回到這一層的時候進行回溯:刪除掉當前加入的數字):
③ 第二種思路:其實與第一種思路是類似,我們可以在for迴圈中進行遞迴,for迴圈中遞迴的範圍為當前遞迴的平方數字~45,在for迴圈中遞迴相當於也是選取與不選取當前的數字兩種狀態,當我們退回到當前這一層的時候那麼嘗試下一個數字表示含義的就是不選取當前的數字,嘗試下一個數字,所以與第一種思路是類似的,只是在寫法上有所不同,所以在for迴圈中寫一個dfs方法即可,往下遞迴的時候平方的數字肯定是加1的,下面是for迴圈遞迴的核心程式碼:
④ 分別使用python與java語言編寫程式碼,發現java語言快得多,5秒之內就可以計算出結果但是python需要30秒左右,最終的結果為:26287
3. 程式碼如下:
java程式碼:
import java.util.ArrayList;
import java.util.List;
public class Main {
// 使用一個全域性變數記錄能夠分解的總的數目
static int res = 0;
public static int squareSplit(){
dfs(1, 0, new ArrayList<Integer>());
return res;
}
// i表示當前遞迴的平方的計數, n表示當前的平方和, rec用來記錄中間的結果
public static void dfs(int i, int n, List<Integer> rec){
// 可以不寫i >= 45這個條件因為下面的for迴圈會有一個範圍當大於了45了之後根本不會執行迴圈
if (n > 2019){
return;
}
if (n == 2019){
for (int k = 0; k < rec.size(); ++k){
System.out.print(rec.get(k) + " ");
}
System.out.println();
res++;
return;
}
for (int k = i; k < 45; ++k){
rec.add(k);
// 相當於也是選取與不選取當前的數字兩種平行狀態
dfs(k + 1, n + k * k, rec);
// 回溯
rec.remove(rec.size() - 1);
}
}
public static void main(String[] args) {
System.out.println(squareSplit());
}
}
import java.util.ArrayList;
import java.util.List;
public class Main {
// 使用一個全域性變數記錄能夠分解的總的數目
static int res = 0;
public static int squareSplit(){
dfs(1, 0, new ArrayList<Integer>());
return res;
}
// i表示當前遞迴的平方的計數, n表示當前的平方和, rec用來記錄中間的結果
public static void dfs(int i, int n, List<Integer> rec){
// 當滿足下面兩個任何一個條件的時候那麼就可以直接return返回到上一層
if (n == 2019) {
// for (int k = 0; k < rec.size(); ++k){
// System.out.print(rec.get(k) + " ");
// }
// System.out.println();
res++;
return;
}
// 因為這個不像for迴圈遞迴,for迴圈往下遞迴有一個最大範圍(i, 45)的限制,而這裡假如不選取當前的數字之後假如沒有i >= 45這個條件會導致死迴圈, 而且下面這個判斷條件必須在n == 2019之後因為有可能n == 45但是n == 2019但是由於先判斷不滿足的條件直接return導致有的情況沒有累加到結果中, 比如結果中的[3, 5, 7, 44]就是這樣一個例子假如先判斷是否大於了45之後那麼就直接返回了這樣就少計算了一個結果
if (n > 2019 || i >= 45) {
return;
}
rec.add(i);
dfs(i + 1, n + i * i, rec);
// 回溯
rec.remove(rec.size() - 1);
dfs(i + 1, n, rec);
}
public static void main(String[] args) {
System.out.println(squareSplit());
}
}
python程式碼:
from typing import List
class Solution:
res = 0
# python比java慢多了
file = open("D:/output/output1.txt", "w")
def dfs(self, i: int, n: int, rec: List[int]):
# 最多到45 ^ 2
if n == 2019:
# print(rec)
# 將輸出語句的內容寫入到檔案中
print(rec, file=self.file)
self.res += 1
return
if n > 2019: return
# for迴圈中進行遞迴
for k in range(i, 45):
rec.append(k)
# i + 1表示去重這樣取出來的平方數字都是比上一個數字要大的
self.dfs(k + 1, n + k * k, rec)
# 回溯
rec.pop()
def squareSplit(self):
# 一開始想到的是dfs
self.dfs(1, 0, list())
return self.res
if __name__ == '__main__':
print(Solution().squareSplit())
from typing import List
class Solution:
res = 0
file = open("D:/output/output2.txt", "w")
def dfs(self, i: int, n: int, rec: List[int]):
if n == 2019:
# print(rec)
# 將輸出的內容到一個txt檔案中
print(rec, file=self.file)
self.res += 1
return
# 最多到45 ^ 2
if n > 2019 or i >= 45: return
rec.append(i)
# 選取當前的數字
self.dfs(i + 1, n + i * i, rec)
# 回溯
rec.pop()
# 不選取當前的數字
self.dfs(i + 1, n, rec)
def squareSplit(self):
self.dfs(1, 0, list())
return self.res
if __name__ == '__main__':
print(Solution().squareSplit())