回溯专项

137 阅读2分钟

单词搜索

题目

image.png

版本1 正确

    boolean [][] visited;
    public boolean exist(char[][] board, String word) {

        // 单词搜索
        // 先按行遍历, 寻找到和word第一个字符相同的位置, 然后作为起点, 开始回溯
        // 对board中, 所有和word第一个字符相同的位置, 进行一次回溯, 任何一个位置为true, 则结果为true
        visited = new boolean[board.length][board[0].length];

        for (int i = 0; i < board.length; i ++) {
            for (int j = 0; j < board[0].length; j ++) {
                if (board[i][j] == word.charAt(0)) {
                    // 回溯一次
                    boolean flag = backtrack(board, word, new int []{i, j}, 0);
                    if (flag) {
                        return flag;
                    }
                }
            }
        }

        return Boolean.FALSE;


    }

    public boolean backtrack(char [][] board, String word, int [] position, int k) {
        // 判断word的第k个字符是否和board当前位置的字符相同
        if (word.charAt(k) != board[position[0]][position[1]]) {
            return Boolean.FALSE;
        }

        if (k == word.length() - 1) {
            return Boolean.TRUE;
        }

        // 注意是标记当前位置为true, 来不走回头路
        visited[position[0]][position[1]] = Boolean.TRUE;
        // 回溯
        int [][] choices = {{0 , 1}, {0, -1}, {1, 0}, {-1, 0}};
        for (int i = 0; i < choices.length; i ++) {
            int [] choice = choices[i];
            int targetRow = choice[0] + position[0];
            int targetCol = choice[1] + position[1];
            if (targetRow < 0 || targetRow >= board.length || targetCol < 0 || targetCol >= board[0].length || visited[targetRow][targetCol]) {
                continue;
            }

            // 做出选择
            boolean flag = backtrack(board, word, new int[] {targetRow, targetCol}, k + 1);
            if (flag) {
                return flag;
            }
        }
        // 撤销当前位置的选择
        visited[position[0]][position[1]] = Boolean.FALSE;

        return Boolean.FALSE;
    }

正确的原因

(1) 注意在递归函数的开头判断是否能继续递归

(2) 然后在做出选择之前, 标记当前位置是访问过的, 每次都是标记当前位置, 而不是标记选择的位置

目标和

题目

image.png

版本1 正确

    int ans;
    public int findTargetSumWays(int[] nums, int target) {

        // 数组中每个数字, 都可以选择是赋予正数还是负数, 目的是等于target
        // 统计有多少种方案

        backtrack(nums, target, 0L, 0);
        return ans;

    }

    public void backtrack(int [] nums, int target, long tempSum, int start) {
        if (start == nums.length) {
            if (tempSum == target) {
                ans ++;
            }
            return;
        }



        for (int i = 0; i < 2; i ++) {
            if (i == 0) {
                // 添加正数
                backtrack(nums, target, tempSum + nums[start], start + 1);
            }
            if (i == 1) {
                // 添加负数
                backtrack(nums, target, tempSum - nums[start], start + 1);
            }
        }
    }

正确的原因

(1) 利用回溯, 枚举每个数字选择正号还是负号的情况

(2) 需要注意 下述写法是错误的, 是因为这样就统计不到长度刚好是数组所有元素的数字组成的和了, 并且题目要求的就是统计长度为数组长度的和. 也就是假设数组为[1, 1, 1]在start = 2的时候, 递归下一层, 此时start为3, 就不会统计ans了

    if (start == nums.length) {
        return;
    }

    if (tempSum == target) {
        ans ++;
    }