回溯算法问题

基本解法

回溯问题,其实就是一个决策树遍历。

要考虑三个问题。

  • 路径:已做出的选择。
  • 选择列表:当前能做的选择。
  • 结束条件:到达决策树的叶子节点,不能选择时。

其实回溯问题可以理解为动规的暴力解法,而且是没有重叠子问题的动规。

result = []
public void backtrack(路径, 选择列表):
    if 满足结束条件
        result.add(路径);
        return;
    
    for 选择 in 选择列表
        (排除不合法选择)
        做选择
        backtrack(路径, 选择列表);
        撤销选择

全排列

给出n个数或字符,写出全部可能的排列组合。

理解为第i位上的字母和自己及其后面的位置上字母进行交换。

回溯就是在递归前做出选择,在递归后撤销之前的选择。

leetcode46题是无重复的数组,代码如下。

47题是有重复的数组,需要进行进一步剪枝。

  • 每次加List时在Lists里判断是否有相同的元素 => 慢
  • 用Set去重 => 快一点
  • 先将数组排序,用一个boolen数组记录元素是否访问过。若nums[i]=nums[i-1],且nums[i-1]没有被访问,说明它俩在树的同一层;若nums[i]=nums[i-1],且nums[i-1]被使用过,说明i在i-1的子树中。这两种情况就剪枝。

代码实现

    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> lists = new ArrayList<>();
        if (nums == null || nums.length == 0) {
            return lists;
        }
        process(nums, 0, lists,new ArrayList<Integer>());
        return lists;
    }

    public void process(int[] nums, int i, List<List<Integer>> lists,  List<Integer> list) {
        if (i == nums.length) {
            for (int j = 0; j < nums.length; j++) {
                list.add(nums[j]);
            }
            //其实可以不加这个判断
            if (!lists.contains(new ArrayList<>(list))) {
                lists.add(new ArrayList<>(list));
                list.clear();
            }
        }
        for (int j = i; j < nums.length; j++) {
            swap(nums, i, j);
            process(nums, i + 1, lists, list);
            swap(nums, i, j);
        }
    }

    public void swap(int[] nums, int a, int b) {
        int tmp = nums[a];
        nums[a] = nums[b];
        nums[b] = tmp;
    }

N皇后

N*N的棋盘,放N个皇后,让它们不能相互攻击。

注:皇后可以攻击同一行、同一列、左上左下右上右下四个方向。(理解为8个方向上的车)

  • 路径:已选择的列表,即0-index行已经放了Q
  • 选择:第index行的全部列都是可选择
  • 结束:index超过最后一行

代码实现

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

    public List<List<String>> solveNQueens(int n) {
        char[][] chars = new char[n][n];
        //Arrays.fill(chars, '.');
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                chars[i][j] = '.';
            }
        }
        process(n, 0, chars );
        return ret;
    }

    public void process(int n, int index, char[][] chars ) {
        if (index == n) {
            List<String> list = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                list.add(new String(chars[i]));
            }
            ret.add(list);
            return;
        }

        for (int col = 0; col < n; col++) {
            //皇后就是八个方向的车,即八个方向上都不能有Q
            //这里只要判断index的上半部分的方向
            if (!isVaild(n, chars, index, col)) {
                continue;
            }
            chars[index][col] = 'Q';
            process(n, index + 1, chars);
            chars[index][col] = '.';
        }
    }

    //检查列,左上,右上,行有没有Q
    public boolean isVaild(int n, char[][] chars, int row, int col) {
        for (int i = 0; i < row; i++) {
            if (chars[i][col] == 'Q') {
                return false;
            }
        }
        for (int i = 0; i < col; i++) {
            if (chars[row][i] == 'Q') {
                return false;
            }
        }
        for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
            if (chars[i][j] == 'Q') {
                return false;
            }
        }

        for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
            if (chars[i][j] == 'Q') {
                return false;
            }
        }
        return true;
    }

发布了158 篇原创文章 · 获赞 12 · 访问量 5544

猜你喜欢

转载自blog.csdn.net/qq_34761012/article/details/104750939