本文仍是轉載加編寫,關於算法的教程網上不少不少,找到這個比較適用的,算法自己就是一種思想,掌握了思想,再根據一些數學知識解決問題。java
回溯法是一種系統搜索問題解空間的方法。爲了實現回溯,須要給問題定義一個解空間。 說到底它是一種搜索算法。只是這裏的搜索是在一個叫作解空間的地方搜索。 而每每所謂的dfs,bfs都是在圖或者樹這種數據結構上的搜索。 根據定義來看,要實現回溯,須要兩點1搜索,2解空間 先看什麼是解空間。 就是形如數組的一個向量[a1,a2,....,an]。這個向量的每一個元素都是問題的部分解,只有當這個數組的每個元素都填滿(獲得所有解)的時候,才代表這個問題獲得瞭解答。 再看搜索。 最簡單的就是for循環,上面的向量有n個維度,所以就是n個for循環。 形如:面試
for(求a1位置上的解)
for(求a2位置上的解)
for(求a3位置上的解)
......
......
for(求an位置上的解)
複製代碼
可是若是n是100?n是100000?那麼如何回溯? 固然也能夠寫n個for循環,可是這樣的程序會慘不忍睹。。。並且彷佛10000個(不過每每回溯的時間複雜度太大,通常n不會這麼大)for循環也很難寫出來。。。 所以咱們須要一種全新的書寫回溯的方法。形如:算法
void backtrack(int i,int n,other parameters) {
if( i == n)
{
//get one answer
record answer;
return;
}
//下面的意思是求解空間第i個位置上的下一個解
for(next ans in position i of solution space)
{
backtrack(i+1,n,other parameters);
}
}
複製代碼
就是這麼簡單!!! 上面的模板適用於全部"解空間肯定"的回溯法的問題!!! 上面的i表明解空間的第i個位置,每每從0開始,而n則表明解空間的大小。每一次的backtrack(i,n,other)調用,表明求解空間第i個位置上的解。而當i=n時,表明解空間上的全部位置的解都已經求出。 有了上述模板,咱們就解決了搜索的問題。 所以幾乎全部回溯的問題的難度都在於如何定義解空間。segmentfault
下面經過題目,帶入模板,而後再看個人解答,來感知一下如何定義解空間。數組
即對沒有重複數字的數組a=[a1,a2,a3,...an]求全排列。 解空間定義爲s=[s1,s2,s3,....sn]與數字長度相同。s的每個元素s【i】(i >= 0&&i < n),都爲數組a中的任意元素a【j】(j >= 0&&j < n),不過要保證任意的s【i】不相等。 這裏惟一複雜的地方是須要用一個boolean【】數組來代表哪些數已經用過,這樣才能保證任意的s【i】不相等。 所以咱們看到,回溯自己是很簡單的,單純的模板套用,難的在於須要根據回溯條件來定義各類別的變量,以及最後結果的記錄。數據結構
(這個下面給出ac 代碼) 這個題很難,可是掌握瞭如何定義解空間以後再作這個題就會感受是小兒科了。 這裏的解空間s = [s1,s2,s3,....sn]中的每個元素s【i】表明格子的座標(x,y),所以從邏輯上來看,s應該是一個類類型的數組。不過,這個題求的是數目,而不是最後的確切路徑,所以解空間在這裏並無記錄。 java ac代碼:函數
class Solution {
int ans;
public int uniquePathsIII(int[][] grid) {
if(grid.length == 0)return 0;
int num = 0;
int x = 0,y = 0;
for(int i = 0;i < grid.length;i++)
for(int j = 0;j < grid[0].length;j++){
if(grid[i][j] == 1||grid[i][j] == 0)num++;
if(grid[i][j] == 1){x = i;y = j;}
}
backtrack(0,num,x,y,grid,new boolean[grid.length][grid[0].length]);
return ans;
}
void backtrack(int i,int n,int x,int y,int[][]grid,boolean[][]flag) {
if(!(x >= 0 && x < grid.length && y >= 0 && y < grid[0].length)||flag[x][y]||grid[x][y] == -1)
return;
if(i == n && grid[x][y] == 2)
{
ans++;
return;
}
flag[x][y] = true;
backtrack(i+1,n,x+1,y,grid,flag);
backtrack(i+1,n,x-1,y,grid,flag);
backtrack(i+1,n,x,y+1,grid,flag);
backtrack(i+1,n,x,y-1,grid,flag);
flag[x][y] = false;
}
}
複製代碼
上面這個題的解空間應該有N+1維纔對,可是爲了方便書寫,我只求出前n維位置的解,而後保證最後一維中位置是終點便可。spa
若是仍然以爲抽象,那麼我建議你們把回溯想象成 「填格子」 遊戲。 到leetcode上找回溯的專題,對於每個回溯法可解的問題,看看這題須要填的格子(格子就是解空間)是什麼。設計
好比n個不重複字母的全排列,不就是填充n個格子,填滿而且合法就獲得一個解。code
這裏的格子的數量是括號對數乘以2,格子上填的就是左括號或者右括號,這裏的剪枝條件是,當前右括號數量超過了左括號,或左括號數量超過了一半。固然爲了剪枝須要在函數參數中維護左右括號數這兩個變量。
思路和算法
只有在咱們知道序列仍然保持有效時才添加 '(' or ')',而不是像 方法一 那樣每次添加。咱們能夠經過跟蹤到目前爲止放置的左括號和右括號的數目來作到這一點,
若是咱們還剩一個位置,咱們能夠開始放一個左括號。 若是它不超過左括號的數量,咱們能夠放一個右括號。
class Solution {
public List<String> generateParenthesis(int n) {
List<String> ans = new ArrayList();
backtrack(ans, "", 0, 0, n);
return ans;
}
public void backtrack(List<String> ans, String cur, int open, int close, int max){
if (cur.length() == max * 2) {
ans.add(cur);
return;
}
if (open < max)
backtrack(ans, cur+"(", open+1, close, max);
if (close < open)
backtrack(ans, cur+")", open, close+1, max);
}
}
複製代碼
最後,爲何要掌握回溯法??? 由於懂了回溯法以後筆試裏的不少題就算AC不了,起碼成功運行70%到90%之間是沒問題的。 並且若是筆試題裏有的數據集設計的不夠好,那麼回溯甚至能夠比動態規劃運行的還快。 而這對於得到面試機會已經足夠了!!! 而且回溯很優美,很容易理解,由於說到底它不過就是個填格子的遊戲罷了。