最近刷面試題經常刷到遞歸方面的算法,一直以為都是遞歸,后來發現竟然都有具體的叫法,所以寫了這篇博客來牢記以下
1. 五大常用算法
(1) 分治算法
把一個復雜的問題分成兩個或多個相同或者相似的子問題,然后不斷地細分,直到最后的子問題可以很簡單地求解出來,原問題的解就是自問題的合並。比如常見的快速排序算法和歸並算法
分治法的核心思想就是把大的難解的問題不斷分割,分而治之。
(2) 動態規划
類似於分治法,將帶求解的問題分化成幾個小問題,每個小問題的解會影響原問題的解。
先求每個子問題的局部解,然后通過決策保留那些可能達到最優解的局部解
能夠分解成若干子問題,且子問題之間有交叉
(3) 回溯算法
類似於一個枚舉的過程,其實也是一個建樹的過程,是樹的深度優先搜索。
在搜索嘗試過程中,發現當前節點已經不能滿足求解條件時,就返回其父節點,繼續嘗試別的路徑
(4) 分支界限法
類似於回溯算法,不過不是深度優先搜索,而是廣度優先搜索,一般是用到queue(先進先出)來對每一個節點進行判斷。
(5) 貪心算法
在問題求解時,總是找到局部最優解,而不是整體上最優。
貪心算法的前提:局部最優策略能最終產生全局的最優解
2. 遞歸(個人感覺這個學會了,算法只是如何調用遞歸)
先上一個最簡單的遞歸代碼,斐波那契數列
public class Fibo { public int[] array = new int[100];{ array[0]=0; array[1]=1; array[2]=1;} public int Fibonacci(int n){ if(n==1||n==2){ return 1; }else{ return this.array[n]=Fibonacci(n-1)+Fibonacci(n-2); } } public static void main(String[] args) { // TODO Auto-generated method stub Fibo fb = new Fibo(); System.out.println(fb.Fibonacci(3)); } }
當然,也可以來個偽遞歸,就是建立一個數組,把答案先預存起來,然后直接返回結果。劍指offer上很多直接遞歸是通不過的,必須要偽遞歸才可以
再來個青蛙跳台階的代碼,題目同樣是劍指offer上的
//一只青蛙一次可以跳上1級台階,也可以跳上2級。 //求該青蛙跳上一個n級的台階總共有多少種跳法 public class JumpFloor { public int Jump(int target){ if(target==0){ return 0; }else if(target==1){ return 1; }else if(target==2){ return 2; }else{ int[] array = new int[target+1]; array[0]=0; array[1]=1; array[2]=2; for(int i=3;i<array.length;i++){ array[i]=array[i-1]+array[i-2]; } return array[target]; } } //一只青蛙一次可以跳上1級台階,也可以跳上2級,跳3個,4個......n-1個。 //求該青蛙跳上一個n級的台階總共有多少種跳法 public int JumpII(int target){ if(target==0){ return 0; }else if(target==1){ return 1; }else if(target==2){ return 2; }else{ int[] array = new int[target+1]; array[0]=0; array[1]=1; array[2]=2; int sum = 3; for(int i=3;i<array.length;i++){ array[i]=sum+1; sum = sum+array[i]; } return array[target]; } } public static void main(String[] args) { // TODO Auto-generated method stub JumpFloor jf = new JumpFloor(); System.out.println(jf.Jump(3)); System.out.println(jf.JumpII(3)); } }
這里也是用偽遞歸來實現的
3. 遞歸的本質
其實遞歸的本質就是找到前后的聯系,找到遞歸的公式
例如 F(n) = F(n-1) + F(n-2) 等等
遞歸的一般情況是
(1). if(滿足遞歸結束條件),返回值
(2). else,繼續遞歸
4. 回溯的本質,其實是在遞歸基礎上進行了改進
(1). if(不滿足繼續遞歸查找的條件,通常是界限判斷),返回
(2). if(滿足查找條件),記錄下來,繼續往下
(3). 加入這個節點,更新條件和值
(4). 遞歸左邊
(5).遞歸右邊
(6). 刪除這個節點(回溯)
來個例子,例子是中興的一道筆試題,我對其進行了改進。其實所有這類的能量補給,血條補給都是相同的解法。
import java.util.ArrayList;
//輸入 //補給站個數,[補給站距離],[補給站能量],目的地距離(總共需要能量),初始能量 //3, [5, 7, 10], [2, 3, 5], 15, 5 //5, [10, 20, 22, 23, 26], [10, 2, 5, 1, 1], 30, 10
//輸出
//所有可以到達的方案,到達不了返回-1
import java.util.Arrays; import java.util.Scanner; public class PowerGain { private ArrayList<ArrayList<Integer>> listAll = new ArrayList<ArrayList<Integer>>(); private ArrayList<Integer> list = new ArrayList<Integer>(); public ArrayList<ArrayList<Integer>> minNumber(int n, int[] distince, int[] judce, int left, int right, int destination, int power){ //System.out.println(left+" "+destination+" "+power); if( left>right || power<=0){ return listAll; } //if(power>=distince[left]){ list.add(left); //} if(power>=destination){ listAll.add(new ArrayList<Integer>(list)); //return listAll; } int newleft = left+1; //不喝繼續前行,喝了繼續前行 minNumber(n, distince, judce, newleft, n, destination-distince[left], power-distince[left]); minNumber(n, distince, judce, newleft, n, destination-distince[left], power-distince[left]+judce[left]); list.remove(list.size()-1); return listAll; } public void outPrint(ArrayList<ArrayList<Integer>> tempAll){ if(tempAll.size()==0){ System.out.println(-1); return; } for(int i=0;i<tempAll.size();i++){ ArrayList<Integer> temp = tempAll.get(i); for(int j=1;j<temp.size();j++){ System.out.print(temp.get(j)); if(j!=temp.size()-1){ System.out.print(" "); } } System.out.println(); } } public static void main(String[] args) { // TODO Auto-generated method stub Scanner input = new Scanner(System.in); String st = input.nextLine(); String[] sarray = st.split(", "); int n = Integer.parseInt(sarray[0]); int[] distince = new int[n+1]; int[] judce = new int[n+1]; int index = 1; //sarray下標 for(int i=0;i<n;i++){ if(i==0){ distince[i] = Integer.parseInt(sarray[index].substring(1)); judce[i] = Integer.parseInt(sarray[index+n].substring(1)); }else if(i==n-1){ distince[i] = Integer.parseInt(sarray[index].substring(0, sarray[index].length()-1)); judce[i] = Integer.parseInt(sarray[index+n].substring(0, sarray[index+n].length()-1)); }else{ distince[i] = Integer.parseInt(sarray[index]); judce[i] = Integer.parseInt(sarray[index+n]); } index++; } index = 2*n+1; int destination = Integer.parseInt(sarray[index]); int power = Integer.parseInt(sarray[index+1]); //長度分段 judce[n] = 0; distince[n]=destination-distince[n-1]; for(int i=n-1;i>0;i--){ distince[i]=distince[i]-distince[i-1]; } PowerGain m = new PowerGain(){}; ArrayList<ArrayList<Integer>> result = m.minNumber(n, distince, judce, 0, n, destination, power); m.outPrint(result); } }