最近刷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以及回溯等算法)。