这是我参与新手入门的第3篇文章。java
实现方法来自《Java8 in Action》,思路是和其余递归解决方法一致,但不一样的地方在concat方法编程
public static List<List<Integer>> subsets(List<Integer> list){
if(list.isEmpty()) {
List<List<Integer>> ans = new ArrayList<>();
ans.add(Collections.emptyList());
return ans;
}
Integer first = list.get(0);
List<Integer> rest = list.subList(1, list.size());
List<List<Integer>> subans = subsets(rest);
List<List<Integer>> subans2 = insertAll(first,subans);
return concat(subans,subans2);
}
public static List<List<Integer>> insertAll(Integer first,List<List<Integer>> lists){
List<List<Integer>> result = new ArrayList<List<Integer>>();
for (List<Integer> list : lists) {
List<Integer> copyList = new ArrayList<Integer>();
copyList.add(first);
copyList.addAll(list);
result.add(copyList);
}
return result;
}
public static List<List<Integer>> concat(List<List<Integer>> a,List<List<Integer>> b)
{
List<List<Integer>> r = new ArrayList<List<Integer>>(a);
r.addAll(b);
return r;
}
复制代码
乍一看,concat方法也没啥,可是这种写法涉及到 解题思想的转变
。markdown
在Java8中,最主要的改变就是函数式编程,它接受零个或多个参数,生成一个或多个结果,而且不会有任何反作用,这里的反作用能够理解为对传入参数/其余数据源作了修改。并发
这里稍稍扩展下,为何函数式编程不修改数据源,在并发编程中,每每是对某一数据源进行多方修改,因此要用锁,而锁的维护是很难的,不少程序的bug都出在这里。app
函数式编程就提出不修改数据源,这样,出bug的可能性就不存在了。函数式编程默认变量是不可变的,若是要改变变量,须要把变量copy而后修改,就像上面的concat方法同样。函数式编程
按照以往的思路concat方法可能会这么写:函数
public static List<List<Integer>> concat(List<List<Integer>> a,List<List<Integer>> b){
//List<List<Integer>> r = new ArrayList<List<Integer>>(a);
a.addAll(b);
return a;
}
复制代码
总体思路仍是递归那一套,使用递归必然带来性能问题,甚至StackOverflowError。性能
因此,咱们一般是写迭代而不是递归,可是Java8提倡用递归取代迭代,由于迭代每每会修改数据源,而函数式编程不修改。优化
那么递归的性能问题怎么解决?ui
先来讲下什么叫作尾调用。
尾调用就是在方法的最后调用。
这就是尾调。
public int getResult(int a,int b) {
return add(a,b);
}
复制代码
而下面这两个方法都不是尾调。
public int getResult(int a,int b) {
return add(a,b)+5;
}
public int getResult(int a,int b) {
int c = add(a,b);
return c;
}
复制代码
我们再来回顾下递归性能问题产生的缘由:
函数调用会在内存中生成调用帧,用以保存位置和内部变量,直到程序彻底结束。递归一次就生成一个栈帧
,若是输入量很大,直接就StackOverflowError了。
图片来自阮一峰博客
拿阶乘举个例子:
日常的递归方法这么写:
public static int factorial(int n) {
if(n==1)
return n;
return n*factorial(n-1);
}
复制代码
调用过程是这样的:
把方法改为尾调用是这样的:
public static int factorial(int a,int n) {
if(n==1)
return a;
return factorial(a*n,n-1);
}
复制代码
调用过程是这样的:
尾调用,由于是 方法的最后一步调用,不须要保留以前的位置和状态,直接用内层函数的调用记录替换了外层调用记录
。