這是我參與新手入門的第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);
}
複製代碼
調用過程是這樣的:
尾調用,由於是 方法的最後一步調用,不須要保留以前的位置和狀態,直接用內層函數的調用記錄替換了外層調用記錄
。