【算法题-分类型总结】回溯相关的算法题

1、算法介绍

1)三要素

路径、选择列表、结束条件

2)算法框架

void backtracking(){

    if(终止条件) {

       记录结果;

       return;    

    }

    for(集合){

       做选择;

       递归操作;

       撤销选择;

    }      

}

 

3)算法分类

排列、组合、子集、分割、棋盘

排组子分棋

2、排列

1)字符串去重全排列

package com.liujinhui;

 

import java.util.*;

import java.util.stream.Collectors;

 

public class BackTrack {

    public static Set<String> res = new HashSet<>();

    public static void main(String[] args) {

        Permutation("ab").forEach(System.out::println);

    }

    public static ArrayList<String> Permutation(String str) {

        char[] arr = str.toCharArray();

        LinkedList<Character> track = new LinkedList<>();

        backTrack(arr, track);

        return new ArrayList<>(res.stream().collect(Collectors.toList()));

    }

    //元素去重的全排列

    public static void backTrack(char[] arr, LinkedList<Character> track) {

        //终止条件

        if (track.size() == arr.length) {

            StringBuilder sb = new StringBuilder();

            for (Character character : track) {

                sb.append(character);

            }

            res.add(sb.toString());

            return;

        }

        for (int i = 0; i < arr.length; i++) {

            //做选择

            if (track.contains(arr[i])){

                continue;

            }

            track.add(arr[i]);

            //递归

            backTrack(arr, track);

            //撤销选择

            track.removeLast();

        }

    }

}

 

2)允许重复的字符全排列

import java.util.*;

import java.util.stream.Collectors;

public class Solution {

    public static Set<String> res = new HashSet<>();

    public static LinkedList<Character> track = new LinkedList<>();

    public static ArrayList<String> Permutation(String str) {

        char[] arr = str.toCharArray();

        boolean[] flag = new boolean[arr.length];

        backTrack(arr, flag);

        return new ArrayList<>(res.stream().collect(Collectors.toList()));

    }

 

    public static void backTrack(char[] arr, boolean[] flag) {

        // 结束条件

        if (track.size() == arr.length) {

            StringBuilder sb = new StringBuilder();

            track.stream().forEach(x -> sb.append(x));

            res.add(sb.toString());

            return;

        }

        //循环

        for (int i = 0; i < arr.length; i++) {

            //做选择

            if (i > 0 && arr[i] == arr[i-1] && flag[i-1] == false) {

                continue;

            }

            if (flag[i] == false) {

                flag[i] = true;

                track.add(arr[i]);

                //递归

                backTrack(arr, flag);

                //撤销选择-回溯

                flag[i] = false;

                track.removeLast();

            }

        }

    }

 

}

 

3)有重复项数字的排列

public ArrayList<ArrayList<Integer>> permuteUnique(int[] num) {

ArrayList<Integer> path = new ArrayList<>();

HashSet<ArrayList<Integer>> resSet = new HashSet<>();

boolean[] visited = new boolean[num.length];

dfs(0,path,resSet,num,visited);

return new ArrayList<>(resSet);

}

 

private void dfs(int level, ArrayList<Integer> path, HashSet<ArrayList<Integer>> resSet, int[] num, boolean[] visited) {

if (level==num.length) {

resSet.add(new ArrayList<>(path));

return;

}

for (int i = 0; i <num.length ; i++) {

if (!visited[i]){

//给当前层上个锁 这个数不能再用了

visited[i] = true;

path.add(num[i]);//★

dfs(level+1,path,resSet,num,visited);

//★ level是计数器          

//*删除一定注意这里 删除易错 每次删最后那个

path.remove(path.size()-1); visited[i] = false;           

}     

}

}

 

4)无重复项数字的排列

思路:按字典序排列

import java.util.*;

public class Solution {

    ArrayList<ArrayList<Integer>> res=new ArrayList<ArrayList<Integer>>();

    public ArrayList<ArrayList<Integer>> permute(int[] nums) {

        if (nums == null || nums.length < 1) return res;

        Arrays.sort(nums);

        ArrayList<Integer> list = new ArrayList<Integer>();

        solve(list, nums);

        return res;

    }

 

    private void solve(ArrayList<Integer> list, int[] nums) {

        if (list.size() == nums.length) {

            res.add(new ArrayList<Integer>(list));

            return;

        }

        for (int i = 0; i < nums.length; i++) {

            if (!list.contains(nums[i])) {   //如果已经包含就不再加入---相当于剪纸啊?

                list.add(nums[i]);

                solve(list, nums);

                list.remove(list.size() - 1);

            }

        }

    }

}

 

2、组合

1)多个集合的组合

import java.util.*;

 

public class Nio {

    public static int sum = 0;

    public static LinkedList<String> res = new LinkedList<>();

    public static LinkedList<Character> track = new LinkedList<>();

    public static void main(String[] args) {

        String[] split = split("{a,b}c{d,e}");

        Arrays.stream(split).forEach(System.out::println);

    }

 

    /**

     * 第二题,输出"{a,b}c{d,e}"的组合

     * @param str

     * @return

     */

    public static String[] split (String str) {

        // "{a,b}c{d,e}"

        char[] arrs = str.toCharArray();

        LinkedList<String> list = new LinkedList<>();

        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < arrs.length; i++) {

            if (arrs[i] == '}') {

                list.add(sb.toString());

                sb = new StringBuilder();

            } else if (arrs[i] == ',' || arrs[i] == '{') {

                continue;

            } else if (i > 1 && i + 1 < arrs.length && arrs[i-1] == '}' && arrs[i+1] == '{'){

                sb.append(arrs[i]);

                list.add(sb.toString());

                sb = new StringBuilder();

            } else {

                sb.append(arrs[i]);

            }

        }

        //[[a,b],[c],[de]]

        backtracking(list, 0);

        String[] arrays = new String[res.size()];

        for (int i = 0; i < res.size(); i++) {

            arrays[i] = res.get(i);

        }

        return arrays;

    }

 

    public static void backtracking(LinkedList<String> list, int num) {

        //结束条件

        if (num == list.size()) {

            StringBuilder sb = new StringBuilder();

            for (Character character : track) {

                sb.append(character);

            }

            res.add(sb.toString());

            // track = new LinkedList<>(); track会随着remove陆续清空

            return;

        }

        char[] array = list.get(num).toCharArray();

        //循环

        for (int i = 0; i < array.length; i++) {

            //选择

            track.add(array[i]);

            //递归

            backtracking(list, num + 1);

            track.removeLast(); //track会随着remove陆续清空

        }

    }

}

 

2n对括号的合法组合

public List<String> generateParenthesis(int n) {

    ArrayList<String> result = new ArrayList<>(10);

    backtrack("", 0, 0, n, result);

    return result;

}

 

private void backtrack(String string, int open, int close, int n, List<String> result) {

    if (string.length() == n << 1) {

        result.add(string);

        return;

    }

    if (open < n) {

        backtrack(string+"(", open+1, close, n, result);

    }

    if (close < open) {

        backtrack(string+")", open, close+1, n, result);

    }

}

 

3)数组和为目标值的组合

思路:先排序

import java.util.*;

public class Solution {

    public ArrayList<ArrayList<Integer>> combinationSum2(int[] num, int target) {

        ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();

        ArrayList<Integer> arr = new ArrayList<Integer>();

        if(num == null || num.length==0 || target<0)return res;

        Arrays.sort(num);

        dfs(num,target,res,arr,0);

        return res;

    }

    void dfs(int[] num,int target,ArrayList<ArrayList<Integer>> res,ArrayList<Integer> arr,int start){

        if(target==0){

            res.add(new ArrayList<Integer>(arr));

            return;

        }

        if(start >= num.length)return;

        for(int i=start;i<num.length;i++){

            if(i > start && num[i] == num[i-1])continue;

            if(num[i] <= target){

                arr.add(num[i]);

                dfs(num,target-num[i],res,arr,i+1);

                arr.remove(arr.size()-1);

            }

        }

        return;

    }

}

 

4)电话号码的字母组合

class Solution {

    public List<String> letterCombinations(String digits) {

        List<String> res = new ArrayList<>();

        if(digits.length() == 0) {

            return res;

        }

        Map<Character, String> map = new HashMap<>();

        map.put('2', "abc");

        map.put('3', "def");

        map.put('4', "ghi");

        map.put('5', "jkl");

        map.put('6', "mno");

        map.put('7', "pqrs");

        map.put('8', "tuv");

        map.put('9', "wxyz");

        backTracking(digits, res, map, new StringBuilder(), 0);

        return res;

    }

 

    public void backTracking(String digits, List<String> res, Map<Character, String> map, StringBuilder sb, int index) {

        if(digits.length() == index) {

            res.add(sb.toString());

        } else { // 需要用else,不然下标会越界

            char chr = digits.charAt(index);

            String str = map.get(chr);

            for(int i = 0; i < str.length(); i++) {

                sb.append(str.charAt(i));

                backTracking(digits, res, map, sb, index + 1);

                sb.deleteCharAt(index); // sb.deleteCharAt(index);重要的函数

            }

        }

 

    }

}

 

5)根到叶的路径和等于定值

/**

 * Definition for a binary tree node.

 * public class TreeNode {

 *     int val;

 *     TreeNode left;

 *     TreeNode right;

 *     TreeNode() {}

 *     TreeNode(int val) { this.val = val; }

 *     TreeNode(int val, TreeNode left, TreeNode right) {

 *         this.val = val;

 *         this.left = left;

 *         this.right = right;

 *     }

 * }

 */

class Solution {

    private List<List<Integer>> res;

    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {

        res = new ArrayList<>();

        List<Integer> ans = new ArrayList<>();

        dfs(root, ans, targetSum, 0);

        return res;

    }

    public void dfs(TreeNode node, List<Integer> ans, int targetSum, int num) {

        if(node == null) {

            return;

        }

        if(node.left == null && node.right == null && node.val + num == targetSum) {

            ans.add(node.val);

            res.add(new ArrayList(ans));

            ans.remove(ans.size() - 1);

            return;

        }

        ans.add(node.val);

        dfs(node.left, ans, targetSum, num + node.val);

        dfs(node.right, ans, targetSum, num + node.val);

        //每个节点最后都要移除

        ans.remove(ans.size() - 1);

    }

}

 

 

 

3、子集

1)集合的所有子集

import java.util.*;

import java.util.stream.Collectors;

public class Solution {

    public static ArrayList<ArrayList<Integer>> res  = new ArrayList<>();

    public static LinkedList<Integer> track = new LinkedList<>();

 

    public static ArrayList<ArrayList<Integer>> subsets(int[] S) {

        if (S.length == 0) {

            res.add(new ArrayList<>());

            return res;

        }

        backtracking(S, 0);

        return res;

    }

 

    public static void backtracking(int[] S, int startIndex) {

        //每次得到的结果都是子集,都加入res

        res.add(new ArrayList<>(track.stream().collect(Collectors.toList())));

        //结束条件

        if (startIndex == S.length) {

            return;

        }

        //循环

        for (int i = startIndex; i < S.length; i++) {

            //满足条件做选择

            track.add(S[i]);

            //递归

            backtracking(S, i + 1);

            //撤销选择

            track.removeLast();

        }

    }

}

 

2)递增子序列

import java.util.*;

class Solution {

    public List<List<Integer>> res = new ArrayList<>();

    public LinkedList<Integer> track = new LinkedList<>();

 

    public List<List<Integer>> findSubsequences(int[] nums) {

        if (nums.length == 0) {

            return new ArrayList<>();

        }

        backtracking(nums, 0);

        return res;

    }

 

    public void backtracking(int[] nums, int startIndex) {

        //最后执行完才结束,每次都要添加

        if (track.size() > 1) {

            res.add(new ArrayList<>(track));

            //res.add(new ArrayList<>(track));传进去的是空

        }

        boolean[] used = new boolean[201];

        //循环

        for (int i = startIndex; i < nums.length; i++) {

            //不满足条件,继续

            if ((!track.isEmpty() && nums[i] < track.get(track.size() - 1)) || used[nums[i] + 100]) {

                continue;

            }

            //做选择

            track.add(nums[i]);

            used[nums[i] + 100] = true;

            //递归

            backtracking(nums, i + 1);

            //回溯

            track.remove(track.size() - 1);

        }

    }

}

 

4、分割

1)分割回文串

class Solution {

    List<List<String>> lists = new ArrayList<>();

    Deque<String> deque = new LinkedList<>();

 

    public List<List<String>> partition(String s) {

        backTracking(s, 0);

        return lists;

    }

 

    private void backTracking(String s, int startIndex) {

        //如果起始位置大于s的大小,说明找到了一组分割方案

        if (startIndex >= s.length()) {

            lists.add(new ArrayList(deque));

            return;

        }

        for (int i = startIndex; i < s.length(); i++) {

            //如果是回文子串,则记录

            if (isPalindrome(s, startIndex, i)) {

                String str = s.substring(startIndex, i + 1);

                deque.addLast(str);

            } else {

                continue;

            }

            //起始位置后移,保证不重复

            backTracking(s, i + 1);

            deque.removeLast();

        }

    }

    //判断是否是回文串

    private boolean isPalindrome(String s, int startIndex, int end) {

        for (int i = startIndex, j = end; i < j; i++, j--) {

            if (s.charAt(i) != s.charAt(j)) {

                return false;

            }

        }

        return true;

    }

}

 

2)字符串转为ip地址

import java.util.*;

 

 

public class Solution {

    /**

     *

     * @param s string字符串

     * @return string字符串ArrayList

     */

    ArrayList<String> res = new ArrayList<>();

    public ArrayList<String> restoreIpAddresses (String s) {

        // write code here

        if(s.length() == 0)

            return res;

        //表示当前字符串s,可以从第0个位置开始插入'.' ,还有3个'.'可以插入

        backTrack(s, 0, 3);

        return res;

    }

 

 

    public void backTrack(String s, int start, int cnt){

        if(cnt == 0){

            String[] splits = s.split("\\.");

            //没有插入4个合法的小数点

            if(splits.length < 4)

                return;

            //判断每一位是否合法

            for(String str:splits){

                if(str.length() > 1 && str.charAt(0) == '0') return;  //最前面的数字不能为0

                if(Integer.valueOf(str) > 255) return;  //每一位都不能大于255

            }

            res.add(s);

            return;

        }

 

        if(start >= s.length()) return;  //没有插完全部的点 就已经超出字符串的范围了

        int len = s.length();

        //每次将一个字符作为一位

        backTrack(s.substring(0,start+1)+'.'+s.substring(start+1,len), start+2, cnt-1);

        //每次将两位字符作为一位

        if(start < len-2)

            backTrack(s.substring(0,start+2)+'.'+s.substring(start+2,len), start+3, cnt-1);

        //每次将三位字符作为一位

        if(start < len-3)

            backTrack(s.substring(0,start+3)+'.'+s.substring(start+3,len), start+4, cnt-1);

    }

}

 

5、棋盘

1N皇后问题

方法1:回溯

public class Solution {

    int answer;

    public int Nqueen (int n) {

        int to=n+1;

        //ans[1]为第一个皇后所在位置, 依此类推

        int[] ans=new int[to];

        //加了ans[0]占位,第一个皇后就可以不用单独遍历

        ans[0]=Integer.MIN_VALUE;

        dfs(ans,1,to);

        return answer;

    }

    public void dfs(int[] now,int n,int to){

        if(n==to){

            answer++;

            return;

        }

        //第n个皇后依次判断能够放在0到to-1的位置i上

        for(int i=0;i<to-1;i++){

            boolean flag=true;

            //依次判断第0到第n-1个皇后j的位置与n是否冲突

            for(int j=0;j<n;j++){

                int sub=n-j;

                int indexJ=now[j];

                //列差==行差则在对角线上

                if(indexJ+sub==i||indexJ-sub==i||i==indexJ){

                    //有冲突则判断下一个位置i+1

                    flag=false;

                    break;

                }

            }

            if(flag){

                now[n]=i;

                dfs(now,n+1,to);

            }

        }

    }

}

 

方法2:其他

class Solution {

    List<List<String>> res = new ArrayList<>();

 

    public List<List<String>> solveNQueens(int n) {

        char[][] chessboard = new char[n][n];

        for (char[] c : chessboard) {

            Arrays.fill(c, '.');

        }

        backTrack(n, 0, chessboard);

        return res;

    }

 

 

    public void backTrack(int n, int row, char[][] chessboard) {

        if (row == n) {

            res.add(Array2List(chessboard));

            return;

        }

 

        for (int col = 0;col < n; ++col) {

            if (isValid (row, col, n, chessboard)) {

                chessboard[row][col] = 'Q';

                backTrack(n, row+1, chessboard);

                chessboard[row][col] = '.';

            }

        }

 

    }

 

 

    public List Array2List(char[][] chessboard) {

        List<String> list = new ArrayList<>();

 

        for (char[] c : chessboard) {

            list.add(String.copyValueOf(c));

        }

        return list;

    }

 

 

    public boolean isValid(int row, int col, int n, char[][] chessboard) {

        // 检查列

        for (int i=0; i<row; ++i) { // 相当于剪枝

            if (chessboard[i][col] == 'Q') {

                return false;

            }

        }

 

        // 检查45度对角线

        for (int i=row-1, j=col-1; i>=0 && j>=0; i--, j--) {

            if (chessboard[i][j] == 'Q') {

                return false;

            }

        }

 

        // 检查135度对角线

        for (int i=row-1, j=col+1; i>=0 && j<=n-1; i--, j++) {

            if (chessboard[i][j] == 'Q') {

                return false;

            }

        }

        return true;

    }

}

 

2)数独是否合法

class Solution {

    public void solveSudoku(char[][] board) {

        solveSudokuHelper(board);

    }

 

    private boolean solveSudokuHelper(char[][] board){

        //「一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,

        // 一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!」

        for (int i = 0; i < 9; i++){ // 遍历行

            for (int j = 0; j < 9; j++){ // 遍历列

                if (board[i][j] != '.'){ // 跳过原始数字

                    continue;

                }

                for (char k = '1'; k <= '9'; k++){ // (i, j) 这个位置放k是否合适

                    if (isValidSudoku(i, j, k, board)){

                        board[i][j] = k;

                        if (solveSudokuHelper(board)){ // 如果找到合适一组立刻返回

                            return true;

                        }

                        board[i][j] = '.';

                    }

                }

                // 9个数都试完了,都不行,那么就返回false

                return false;

                // 因为如果一行一列确定下来了,这里尝试了9个数都不行,说明这个棋盘找不到解决数独问题的解!

                // 那么会直接返回, 「这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去!」

            }

        }

        // 遍历完没有返回false,说明找到了合适棋盘位置了

        return true;

    }

 

    /**

     * 判断棋盘是否合法有如下三个维度:

     *     同行是否重复

     *     同列是否重复

     *     9宫格里是否重复

     */

    private boolean isValidSudoku(int row, int col, char val, char[][] board){

        // 同行是否重复

        for (int i = 0; i < 9; i++){

            if (board[row][i] == val){

                return false;

            }

        }

        // 同列是否重复

        for (int j = 0; j < 9; j++){

            if (board[j][col] == val){

                return false;

            }

        }

        // 9宫格里是否重复

        int startRow = (row / 3) * 3;

        int startCol = (col / 3) * 3;

        for (int i = startRow; i < startRow + 3; i++){

            for (int j = startCol; j < startCol + 3; j++){

                if (board[i][j] == val){

                    return false;

                }

            }

        }

        return true;

    }

}

posted @ 2022-02-14 20:55  哥们要飞  阅读(32)  评论(0编辑  收藏  举报