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秒左右,最终的结果为:52574(这里有个坑就是0也是完全平方数所以需要从0开始dfs)
3. 代码如下:
java代码:
import java.util.ArrayList;
import java.util.List;
public class Main {
// 使用一个全局变量记录能够分解的总的数目
static int res = 0;
public static int squareSplit(){
dfs(0, 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(0, 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(0, 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(0, 0, list())
return self.res
if __name__ == '__main__':
print(Solution().squareSplit())