最近刷leetcode時遇到了好多DFS的題目,一開始的迷茫,只能看着答案一步一步的分析,而現在,對於DFS這類題目有了自己的一些見解。
首先DFS是什麼呢?
D—depth,F—first,S—serach,形象一點的講法就是一條道路走到黑,直到走到終點或者前面沒有路,可以理解爲一根筋。
DFS算法大同小異,只要抓住其核心思想,那麼關於DFS的題目在你看來就像是一個模子刻出來的。
那麼其核心思想是什麼呢?
dfs(這一步){
if(終點或者前面沒有路){// 1
return ...
}
if(未達終點且可以繼續走下去){// 2
//那麼就走下一步
dfs(下一步);
}
}
對於不同的題目,唯一不同的就是代碼“2”處,不同的題目有不同的判斷標準一決定是否能繼續往下走。
知道了核心算法,那麼就實戰練習一下吧!
案例1
LeetCode:combinations
Given two integers n and k, return all possible combinations of k numbers out of 1 ... n.
(從數集(1,2,....,n-1,n)取K個不重複的數)
For example,
If n = 4 and k = 2, a solution is:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
我們以題目出現的情況分析:
我們先從(1,2,3,4)中取一個數T,之後再從剩下的數集C中取下一個數,爲了不重複副,我們對於C必須是T下邊以後的數集。
我們看一下流程:
1.假設第一個數取1後,從(2,3,4)取一個數
2.如果第一個數取2,就從(3,4)取一個數
3.如果第一個數取3,就從(4)取一個數。
我們帶入DFS核心思想的那個流程代碼:
dfs(取一個數T){
if(取的數已經兩個了或者不能再取了(越界了)){// 1
//do something
return ...
}
if(還能繼續取數){// 2
//那麼就走下一步
dfs(從剩下的數C中取數);
}
}
DFS代碼如下:
public class Solution {
public ArrayList<ArrayList<Integer>> combine(int n, int k) {
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
ArrayList<Integer> round = new ArrayList<>();
doCombine(1, n, k, result, round);
return result;
}
// k表示取多少個數添加到list中,start表示從哪裏開始取
private void doCombine(int start, int n, int k,
ArrayList<ArrayList<Integer>> result, ArrayList<Integer> round) {
if (k == 0) { // 1.
result.add(new ArrayList<>(round));
return;
}
if (start > n) return; // 1.
round.add(start); // 2.
doCombine(start + 1, n, k - 1, result, round); // 2.
// 如果不取呢?
round.remove(round.size() - 1); // 2.
doCombine(start + 1, n, k, result, round); // 2.
}
}
說明如下:
start:表示取得樹在數集中的下標
n:表示數集(1,2,….,n-1,n)
k:表示剩下還需要從數集中取k個數。
代碼中標註1的DFS核心思想流程中對應的代碼1,而對於2,這道題我們根據題目得出下一步驟的做法:
1、如果取start對應的數,將start加入round,那麼dfs(剩下的數取一個,k-1)
2、如果不取start對應的數,將剛剛加入round的start刪除,那麼dfs(剩下的數取一個,k不變)。
怎麼樣,是不是很簡單?如果你學習過設計模式,就知道這不就是模板模式嗎?
案例2
題目:N—Queues,N皇后問題,這應該是比較經典的關於DFS算法的題目了。
The n-queens puzzle is the problem of placing n queens on an n×n chessboard such that no two queens attack each other.
Given an integer n, return all distinct solutions to the n-queens puzzle.
Each solution contains a distinct board configuration of the n-queens’ placement, where’Q’and’.’both indicate a queen and an empty space respectively.
For example,
There exist two distinct solutions to the 4-queens puzzle:
我們先看一看如何解決?
1.從格子中取第一行,再從【a,h】列中選擇一個可以放置的位置
2.從格子中取第2行,再從【a,h】列中選擇一個可以放置的位置
從【a,h】列中選擇一個可以放置的位置——這句話如何理解?
for(int i=a;i<h;i++){
if{位置i可以放置}{
//那就放置
}else{
//那就選擇下一個位置放置
}
}
那麼來看一下N—Queue的代碼:
public class Solution {
public ArrayList<String[]> solveNQueens(int n) {
// 先初始化
ArrayList<String[]> result = new ArrayList<>();
String[] round = new String[n];
StringBuffer sb = new StringBuffer();
for (int i = 0; i < n; i++)
sb.append(".");
Arrays.fill(round, sb.toString());
// 開始執行
doSolveNQueens(0, n, n, result, round);
return result;
}
public void doSolveNQueens(int nowRows, int rows, int cols,
ArrayList<String[]> result, String[] round) {
if (nowRows == rows) { // 1.處
String[] copyOf = Arrays.copyOf(round, round.length);
result.add(copyOf);
}
// 查詢這一行那一列可以添加
for (int j = 0; j < cols; j++) {
if (canAdd(nowRows, rows,j,cols, round)) { // 2.處
//這一步的操作
char[] charArray = round[nowRows].toCharArray();
charArray[j] = 'Q';
String string = String.valueOf(charArray);
round[nowRows] = string;
//執行下一步
doSolveNQueens(nowRows+1, rows, cols, result, round);
//將Q設置回“.”,回覆原狀
charArray = round[nowRows].toCharArray();
charArray[j] = '.';
string = String.valueOf(charArray);
round[nowRows] = string;
}
}
}
//判斷是否可以放置的代碼
private boolean canAdd(int nowRows, int rows,int nowCols ,int cols, String[] round) {
// 判斷這cols列是否可以放置
for (int i = 0; i <= nowRows; i++) {
if (round[i].charAt(nowCols) == 'Q')
return false;
}
// 判斷反斜行是否可以
for (int i = nowRows - 1, j = nowCols - 1; i >= 0 && j >= 0; i--, j--) {
if (round[i].charAt(j) == 'Q')
return false;
}
// 判斷正斜行是否可以
for (int i = nowRows - 1, j = nowCols + 1; i >= 0 && j<cols; i--, j++) {
if (round[i].charAt(j) == 'Q')
return false;
}
return true;
}
}
參數說明:
nowRows:此時的行數
rows:總的行數
nowCols:此時的列
cols:總列
重點關注代碼1、2處。
1處代碼還是老樣子,這是DFS算法中改變最小的地方,這裏不解釋了。
2處代碼,這是DFS算法中改變最大的地方。本案例我們需要通過下面的canAdd函數判斷是否可以繼續進行下一步。
帶DFS核心流程:
dfs(這一行放置一個Q){
if(所有的Q已經放置){// 1
//do something
return ...
}
for(從a列到h列選擇一列){
if(此列是否能放置Q){// 2
//那麼就走下一步
dfs(從下一行放置Q);
}
}
}
案例3
Given a set of candidate numbers ( C ) and a target number ( T ), find
all unique combinations in C where the candidate numbers sums to T .
The same repeated number may be chosen from C unlimited number of
times. Note: All numbers (including target) will be positive integers.
Elements in a combination (a 1, a 2, … , a k) must be in
non-descending order. (ie, a 1 ≤ a 2 ≤ … ≤ a k). The solution set must
not contain duplicate combinations.For example, given candidate set2,3,6,7and target7, A solution set
is: [7]
[2, 2, 3]
題目的意思就是算24,只不過只能使用加法,但是元素可以重複使用。
使用DFS來解決的話,我們的想法如下:
1.從C【2,3,6,7】中取2加,此時T=5,只要從【2,3,6,7】取數使得sum爲5集合即可結果集合{2}
2.從C【2,3,6,7】中取2加,此時T=3,只要從【2,3,6,7】取數使得sum爲3集合即可結果集合{2,2}
3.從C【2,3,6,7】中取2加,此時T=1,只要從【2,3,6,7】取數使得sum爲1集合即可
結果集合{2,2,2}
4.從C【2,3,6,7】不能取1,此時去除最新加入的2,結果集合{2,2},此時T=3
5.從C【2,3,6,7】中取3加,此時T=0完成任務了,結果集合{2,2,3}
dfs(要加入的數,剩下的T){
if(T==0,表示獲得解){// 1.
//do something
return ...
}
//從數集合C中取一個數
if(加入以後,T還是>=0){ // 2.
//那就將此取得的數加入
}
//執行下一步
dfs(要加入的數,T = T-加入的數);
//如果不加入這個數
dfs(要加入的數,T還是不變);
}
}
下面是解法:
public class Solution {
public ArrayList<ArrayList<Integer>> combinationSum(int[] candidates, int target) {
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
ArrayList<Integer> round = new ArrayList<>();
Arrays.sort(candidates);
doCombinationSum(0,target, candidates, round, result);
return result;
}
public void doCombinationSum(int index, int target, int[] candidates, ArrayList<Integer> round, ArrayList<ArrayList<Integer>> result) {
if (target == 0) { // 1.
result.add(new ArrayList<>(round));
return;
}
for (int i = index; i < candidates.length; i++) {//選擇一個數
if (target - candidates[i] < 0) return;//2.
round.add(candidates[i]); //2.
doCombinationSum(i, target - candidates[i], candidates, round, result);//2.
round.remove(round.size() - 1); //如果不加入此數
}
}
}
好了,通過這三個案例,希望大家對DFS有一些瞭解,起碼不會害怕筆試的時候出現。(建議大家刷leetcode,裏面的一些高級算法是各個大廠比較會考的,而劍指offer在我看來,有時間再刷把,裏面的題目很少涉及DFS,DP,Greedy以及回溯等算法)。