首页 > 代码库 > Chapter five Depth First Search(深度优先搜索)

Chapter five Depth First Search(深度优先搜索)

组合搜索问题 Combination

问题模型:求出所有满足条件的“组合”。判断条件:组合中的元素是顺序无关的。时间复杂度:与 2^n 相关。

1.Chapter one 第2题 subsets(子集)

2.Chapter one 第3题 subsets-ii(子集II)

3.combination-sum(数字组合)

给出一组候选数字(C)和目标数字(T),找到C中所有的组合,使找出的数字和为T。C中的数字可以无限制重复被选取。所有的数字(包括目标数字)均为正整数。元素组合(a1, a2, … , ak)必须是非降序(ie, a1 ≤ a2 ≤ … ≤ ak)。解集不能包含重复的组合。

例如,给出候选数组[2,3,6,7]和目标数字7,解集为:[[7],[2,2,3]]

技术分享
public class Solution {
    /**
     * @param candidates: A list of integers
     * @param target:An integer
     * @return: A list of lists of integers
     */
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        // write your code here
        List<List<Integer>> results = new ArrayList<List<Integer>>();
        if (candidates == null || candidates.length == 0) {
            return results;
        }
        Arrays.sort(candidates);
        List<Integer> combination = new ArrayList<Integer>();
        dfs(candidates, 0, target, combination, results);
        return results;
    }
    private void dfs(int[] candidates,
                     int index,
                     int target,
                     List<Integer> combination,
                     List<List<Integer>> results) {
        if (target == 0) {
            results.add(new ArrayList<Integer>(combination));
            return;
        }
        for (int i = index; i < candidates.length; i++) {
            if (i != index && candidates[i] == candidates[i - 1]) {
                continue;
            }
            if (candidates[i] > target) {
                break;
            }
            combination.add(candidates[i]);
            dfs(candidates, i, target - candidates[i], combination, results);
            combination.remove(combination.size() - 1);
        }
    }
}
View Code

4.combination-sum-ii(数字组合II)

给出一组候选数字(C)和目标数字(T),找出C中所有的组合,使组合中数字的和为T。C中每个数字在每个组合中只能使用一次。所有的数字(包括目标数字)均为正整数。元素组合(a1, a2, … , ak)必须是非降序(ie, a1 ≤ a2 ≤ … ≤ ak)。解集不能包含重复的组合。

例如,给出候选数字集合[10,1,6,7,2,1,5] 和目标数字 8  ,解集为:[[1,7],[1,2,5],[2,6],[1,1,6]]

技术分享
public class Solution {
    /**
     * @param num: Given the candidate numbers
     * @param target: Given the target number
     * @return: All the combinations that sum to target
     */
    public List<List<Integer>> combinationSum2(int[] num, int target) {
        // write your code here
        List<List<Integer>> results = new ArrayList<List<Integer>>();
        if (num == null || num.length == 0) {
            return results;
        }
        Arrays.sort(num);
        List<Integer> combination = new ArrayList<Integer>();
        dfs(num, 0, target, combination, results);
        return results;
    }
    private void dfs(int[] num,
                     int index,
                     int target,
                     List<Integer> combination,
                     List<List<Integer>> results) {
        if (target == 0) {
            results.add(new ArrayList<Integer>(combination));
            return;
        }
        for (int i = index; i < num.length; i++) {
            if (i != index && num[i] == num[i - 1]) {
                continue;
            }
            if (num[i] > target) {
                break;
            }
            combination.add(num[i]);
            dfs(num, i + 1, target - num[i], combination, results);
            combination.remove(combination.size() - 1);
        }
    }
}
View Code9

第3题和第4题的区别只在于C中的每个数字可以使用几次,故第3题搜索从index开始:dfs(candidates, i, target - candidates[i], combination, results);第4题从index+1开始:dfs(num, i + 1, target - num[i], combination, results)。两题的共同点在解集不能包含重复的组合,故使用“选代表法”去重:if (i != index && candidates[i] == candidates[i - 1]) {continue;}

5.palindrome-partitioning(分割回文串)

给定一个字符串s,将s分割成一些子串,使每个子串都是回文串。返回s所有可能的回文串分割方案。

给出 s = "aab",返回[["aa", "b"],["a", "a", "b"]]

DFS通常解法:

技术分享
public class Solution {
    /**
     * @param s: A string
     * @return: A list of lists of string
     */
    public List<List<String>> partition(String s) {
        // write your code here
        List<List<String>> results = new ArrayList<List<String>>();
        if (s == null || s.length() == 0) {
            return results;
        }
        List<String> partition = new ArrayList<String>();
        dfs(s, 0, partition, results);
        return results;
    }
    private void dfs(String s,
                     int startIndex,
                     List<String> partition,
                     List<List<String>> results) {
        if (startIndex == s.length()) {
            results.add(new ArrayList<String>(partition));
            return;
        }
        for (int i = startIndex; i < s.length(); i++) {
            String subString = s.substring(startIndex, i + 1);
            if (!isPalindrome(subString)) {
                continue;
            }
            partition.add(subString);
            dfs(s, i + 1, partition, results);
            partition.remove(partition.size() - 1);
        }
    }
    private boolean isPalindrome(String s) {
        for (int i = 0, j = s.length() - 1; i < j; i++, j--) {
            if (s.charAt(i) != s.charAt(j)) {
                return false;
            }
        }
        return true;
    }
}
View Code

注意:从startIndex(初始为0)位置开始截取(startIndex, i+ 1)长度的子字符串判断是否为回文串,如果不是就i++,如果是就进行下一层dfs。结束条件为startIndex=s.length()-1

优化回文串判断的解法:

技术分享
public class Solution {
    List<List<String>> results;
    boolean[][] isPalindrome;
    /**
     * @param s: A string
     * @return: A list of lists of string
     */
    public List<List<String>> partition(String s) {
        results = new ArrayList<>();
        if (s == null || s.length() == 0) {
            return results;
        }
        getIsPalindrome(s);
        helper(s, 0, new ArrayList<Integer>());
        return results;
    }
    private void getIsPalindrome(String s) {
        int n = s.length();
        isPalindrome = new boolean[n][n];
        for (int i = 0; i < n; i++) {
            isPalindrome[i][i] = true;
        }
        for (int i = 0; i < n - 1; i++) {
            isPalindrome[i][i + 1] = (s.charAt(i) == s.charAt(i + 1));
        }
        for (int i = n - 3; i >= 0; i--) {
            for (int j = i + 2; j < n; j++) {
                isPalindrome[i][j] = isPalindrome[i + 1][j - 1] && s.charAt(i) == s.charAt(j);
            }
        }
    }
    private void helper(String s,
                        int startIndex,
                        List<Integer> partition) {
        if (startIndex == s.length()) {
            addResult(s, partition);
            return;
        }
        for (int i = startIndex; i < s.length(); i++) {
            if (!isPalindrome[startIndex][i]) {
                continue;
            }
            partition.add(i);
            helper(s, i + 1, partition);
            partition.remove(partition.size() - 1);
        }
    }
    private void addResult(String s, List<Integer> partition) {
        List<String> result = new ArrayList<>();
        int startIndex = 0;
        for (int i = 0; i < partition.size(); i++) {
            result.add(s.substring(startIndex, partition.get(i) + 1));
            startIndex = partition.get(i) + 1;
        }
        results.add(result);
    }
}
View Code

注意:动态规划也可以判断回文字符串,回文串的子串也是回文串,比如p[i,j](表示以i开始以j结束的子串)是回文字符串,那么p[i+1,j-1]也是回文字符串。

技术分享
int n = s.length();
boolean[][] isPalindrome = new boolean[n][n];
for (int i = 0; i < n; i++) {
    isPalindrome[i][i] = true;
}
for (int i = 0; i < n - 1; i++) {//判断相邻两个字符是否是回文串
    isPalindrome[i][i + 1] = (s.charAt(i) == s.charAt(i + 1));
}
for (int i = n - 3; i >= 0; i--) {//判断其余字符
    for (int j = i + 2; j < n; j++) {
        isPalindrome[i][j] = isPalindrome[i + 1][j - 1] && s.charAt(i) == s.charAt(j);
    }
}
View Code

6.palindrome-partitioning-ii(分割回文串II)

 

给定一个字符串s,将s分割成一些子串,使每个子串都是回文。返回s符合要求的的最少分割次数。

 

 

排列搜索问题 Permutation

问题模型:求出所有满足条件的“ 排列”。判断条件:组合中的元素是顺序“ 相关”的。时间复杂度:与 n! 相关。

7.Chapter one 第4题 permutations(全排列)

8.Chapter one 第5题 permutations-ii(带重复元素的排列)

9.n-queens(N皇后)

n皇后问题是将n个皇后放置在n*n的棋盘上,皇后彼此之间不能相互攻击(任意两个皇后都不能放在同一行、同一列或同一斜线上)。给定一个整数n,返回所有不同的n皇后问题的解决方案。每个解决方案包含一个明确的n皇后放置布局,其中“Q”和“.”分别表示一个女王和一个空位置。

技术分享
class Solution {
    /**
     * Get all distinct N-Queen solutions
     * @param n: The number of queens
     * @return: All distinct solutions
     * For example, A string ‘...Q‘ shows a queen on forth position
     */
    ArrayList<ArrayList<String>> solveNQueens(int n) {
        // write your code here
        ArrayList<ArrayList<String>> results = new ArrayList<ArrayList<String>>();
        if (n <= 0) {
            return results;
        }
        search(results, new ArrayList<Integer>(), n);
        return results;
    }
    //寻找皇后放置的合适位置(cols存储每一行的列索引)
    private void search(ArrayList<ArrayList<String>> results,
                        ArrayList<Integer> cols,
                        int n) {
        if (cols.size() == n) {
            results.add(drawChessBoard(cols));
            return;
        }
        for (int colIndex = 0; colIndex < n; colIndex++) {
            if (!isValid(cols, colIndex)) {
                continue;
            }
            cols.add(colIndex);
            search(results, cols, n);
            cols.remove(cols.size() - 1);
        }
    }
    //绘制棋盘
    private ArrayList<String> drawChessBoard(ArrayList<Integer> cols) {
        ArrayList<String> chessboard = new ArrayList<String>();
        for (int i = 0; i < cols.size(); i++) {
            StringBuilder sb = new StringBuilder();
            for (int j = 0; j < cols.size(); j++) {
                sb.append(j == cols.get(i) ? "Q" : ".");
            }
            chessboard.add(sb.toString());
        }
        return chessboard;
    }
    //判断位置是否有效
    private boolean isValid(ArrayList<Integer> cols, int column) {
        int row = cols.size();
        for (int rowIndex = 0; rowIndex < cols.size(); rowIndex++) {
            if (cols.get(rowIndex) == column) {
                return false;
            }
            if (rowIndex + cols.get(rowIndex) == row + column) {
                return false;
            }
            if (rowIndex - cols.get(rowIndex) == row - column) {
                return false;
            }
        }
        return true;
    }
};
View Code

注意:cols存储的是每一行放置皇后的列索引。search()函数寻找每一行中皇后放置的合适位置,列索引存储在cols中;drawChessBoard()函数绘制棋盘,皇后的位置绘制"Q",其余位置绘制".:,要使用StringBuilder; isValid()函数判断该位置是否有效。

10.word-ladder-ii(单词接龙II)【hard】(Chapter four 第20题 word-ladder)

给出两个单词(start和end)和一个字典,找出所有从start到end的最短转换序列。每次只能改变一个字母。变换过程中的中间单词必须在字典中出现。所有单词具有相同的长度。所有单词都只包含小写字母。

 

11.string-permutation(字符串置换)

给定两个字符串,请设计一个方法来判定其中一个字符串是否为另一个字符串的置换。置换的意思是,通过改变顺序可以使得两个字符串相等。

技术分享
public class Solution {
    /**
     * @param A a string
     * @param B a string
     * @return a boolean
     */
    public boolean stringPermutation(String A, String B) {
        // Write your code here
        int[] cnt = new int[1000];
        for (int i = 0; i < A.length(); i++) {
            cnt[(int) A.charAt(i)]++;
        }
        for (int j = 0; j < B.length(); j++) {
            cnt[(int) B.charAt(j)]--;
        }
        for (int i = 0; i < cnt.length; i++) {
            if (cnt[i] != 0) {
                return false;
            }
        }
        return true;
    }
}
View Code

注意:使用一个数组,遍历A使得数组中相应位置的元素加1, 遍历B使得数组中相应位置的元素减1, 若最终数组中存在不为0的数则返回false。

12.permutation-index(排列序号)

给出一个不含重复数字的排列,求这些数字的所有排列按字典序排序后该排列的编号。其中,编号从1开始。

 

13.permutation-index-ii(排列序号II)

给出一个可能包含重复数字的排列,求这些数字的所有排列按字典序排序后该排列在其中的编号。编号从1开始。

 

14.next-permutation(下一个排列)

给定一个整数数组来表示排列,找出其之后的一个排列。排列中可能包含重复的整数。

注意:[1,2,7,4,3,1]的下一个排列是[1,3,1,2,4,7] 通过观察原数组可以发现,如果1.从末尾往前看,数字逐渐变大,到了2时才减小。再2.从后往前找第一个比2大的数字是3,3.交换2和3,再4.把此时3后面的所有数字转置一下即可。[1,2,7,4,3,1]->[1,2,7,4,3,1]->[1,3,7,4,2,1]->[1,3,1,2,4,7]

15.next-permutation-ii(下一个排列II)

给定一个若干整数的排列,给出按正数大小进行字典序从小到大排序后的下一个排列。如果没有下一个排列,则输出字典序最小的序列。

 

16.previous-permutation(上一个排列)

给定一个整数数组来表示排列,找出其上一个排列。排列中可能包含重复的整数。

 

17.string-permutation-ii(字符串的不同排列)

给出一个字符串,找到它的所有排列,注意同一个字符串不要打印两次。

 

18.word-break(单词划分)

给出一个字符串s和一个词典,判断字符串s是否可以被空格切分成一个或多个出现在字典中的单词。

 

19.word-break-ii(单词划分II)【hard】

给定一个字符串s和一个单词字典dict,在s中添加空格来构造一个句子,其中每个单词都是有效的字典单词。返回所有这样的句子。

Chapter five Depth First Search(深度优先搜索)