做了一陣時間的leetcode,多多少少已經做了150左右的題量了。做多了對題目也有自己的心得。從以前看題目的毫無頭緒到現在的隱約抓住了一些規律性的東西。本篇是關於個人對leetcode上面典型DFS遞歸和深搜題目的總結整理,其中解題模式大同小異。本文會隨着刷題的過程逐漸更新。對於本篇文章的主題,如果要抽象出來一個公共思想,應該是如下的樣子:
def DFS(solution_Set, buildingAnswer, step)
if Avaliable(buildingAnswer) : # 如果當前構造的解是可用的解,則添加到最終解之中
solution_Set.add(buildingAnswer)
return
for bs in BuildingSpace: # 遍歷構造空間,是當前構造解可添加處理操作的空間
if feasible(bs): # 如果當前遍歷的操作對於當前階段是可行的,則對當前構造解施加操作
Process(buildingAnswer, fs)
DFS(solution_Set, buildingAnswer, step + 1) # 在當前的處理上進入下一種處理,進一步搜索解
Restore(buildingAnswer, fs) # 從下一個狀態搜索中返回,無論下一層是否是什么狀態。 恢復本階段的狀態,搜索本階段另外可施加的狀態。
以下的題目都是屬於一種類型的,只要無腦搜索配合適當的剪枝就可以了。
給定一個字符串,生成其中字符的所有的排列
public ArrayList<String> Permutation(String str) {
ArrayList<String> ret = new ArrayList<>();
Set<String> hel= new HashSet<>();
StringBuilder sb = new StringBuilder(str);
f(hel, sb, 0);
ret = new ArrayList<>(hel);
Collections.sort(ret);
return ret;
}
private void f(Set<String> hel, StringBuilder sb, int step){
if(step == sb.length()-1){
hel.add(sb.toString());
return;
}
for(int i = step; i < sb.length(); i++){
swap(sb,step,i);
f(hel, sb, step+1);
swap(sb,step, i);
//while(i+1<sb.length() && sb.charAt(i+1) == sb.charAt(i))i++;
}
}
private void swap(StringBuilder sb, int a, int b){
char tc = sb.charAt(a);
sb.setCharAt(a, sb.charAt(b));
sb.setCharAt(b, tc);
}
解析:該題注意,可能存在重復字符而且需要按照字典序輸出。這時就要感謝Java方便的集合庫了。唯一感覺代碼中不好的地方就是使用了這個Set,肯定有方法避免重復字符串的生成。其中f函數是典型的深度優先遍歷樹的從根到葉子的框架式代碼。
解法類似的題目有
46 Permutations -- 記得這題是某一年百度的筆試面試題。
22. Generate Parentheses
分析思路: 對於括號來說,有左括號和右括號(廢話)。對於DFS搜索來說,如果不加限制的搜索,就會生成(2^N)的結果。但是題目中要求的是可用的括號組合,即"(())"是正確的,")())"是錯誤的。因此,只要在dfs遞歸處加上if限制就行了,起到剪枝作用。套用上面的DFS框架就可以快速的寫出代碼。
public List<String> generateParenthesis(int n) {
List<String> ret = new ArrayList<>();
StringBuilder sb = new StringBuilder();
dfs(ret,sb, 0,0, n);
return ret;
}
private void dfs(List<String> ret, StringBuilder sb, int ln, int rn, int n){
if(ln + rn == 2*n){
ret.add(new String(sb));
return;
}
if(ln < n){
sb.append("(");
dfs(ret, sb, ln+1, rn, n);
sb.deleteCharAt(sb.length()-1);
}
if(rn < ln){
sb.append(")");
dfs(ret, sb, ln, rn+1, n);
sb.deleteCharAt(sb.length()-1);
}
}
105. Construct Binary Tree from Preorder and Inorder Traversal
這是一個給定中序、先序,生成樹的題目。
分析思路: 題目很簡單,即和手動計算的思路一樣。首先從先序序列中找到第一個,根據先序遍歷的性質,它就是第一個根,然后拿着這個根到中序序列中能把中序序列划分成兩個子序列,然后再遞歸的到先序中找下一個就是第二個根,然后再去中序中找。
public class Solution {
private int findpos(int [] a, int k){
for(int i = 0; i < a.length; i++){
if(a[i] == k)return i;
}
return -1;//wrong
}
TreeNode f(int[] preorder, int [] inorder, int l, int r){
TreeNode root = new TreeNode(preorder[rootind]);
int k = findpos(inorder, preorder[rootind]);
rootind++;
if(k != l) {
root.left = f(preorder, inorder,l, k-1);
}
if(k != r) {
root.right = f(preorder, inorder,k+1, r);
}
return root;
}
int rootind;
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder.length == 0) return null;
Integer rootind = 0;
return f(preorder,inorder, 0, preorder.length-1);
}
}