机器人移动范围

117 阅读6分钟

题目

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

示例 1: 输入:m = 2, n = 3, k = 1 输出:3 示例 2:

输入:m = 3, n = 1, k = 0 输出:1

提示: 1 <= n,m <= 100 0 <= k <= 20

当作回溯算法


import java.util.ArrayList;
import java.util.List;


public class Main {


    public static void main(String[] args) {

        Main main = new Main();
        main.movingCount(7, 2, 3);

    }

    int m = 0;
    int n = 0;
    int k = 0;
    List<int []> finalList = new ArrayList<>();

    public int movingCount(int m, int n, int k) {
        this.m = m;
        this.n = n;
        this.k = k;
        List<int []> pathList = new ArrayList<>();
        pathList.add(new int[] {0, 0});
        huisu(pathList, new int[] {0, 0});

        return finalList.size();

    }

    public void huisu(List<int []> pathList, int [] position) {
        // 由position的到选择列表, 同时剔除非法选择
        List<int []> optionList = getOptionList(position, pathList);

        if (optionList.size() == 0) {
            for (int [] path: pathList) {
                if (!contains(finalList, path)) {
                    finalList.add(path);
                }
            }
            return;
        }

        for (int i = 0; i < optionList.size(); i ++) {
            int [] option = optionList.get(i);

            // 记录当前路径
            List<int []> newPathList = new ArrayList<>();
            newPathList.addAll(pathList);
            newPathList.add(option);

            // 作出选择
            huisu(newPathList, option);

        }



    }

    public List<int []> getOptionList(int [] position, List<int []> pathList) {
        List<int []> optionList = new ArrayList<>();
        int row = position[0];
        int col = position[1];
        int [] rowOffset = new int [] {1, -1, 0, 0};
        int [] colOffset = new int [] {0, 0, 1, -1};

        for (int i = 0; i < 4; i++) {
            int targetRow = row + rowOffset[i];
            int targetCol = col + colOffset[i];
            int [] option = new int[]{targetRow, targetCol};
            if (targetRow < 0 || targetRow >= m || targetCol < 0 || targetCol >= n || !isLessK(targetRow, targetCol) || contains(pathList, option)) {
                continue;
            }
            optionList.add(option);
        }

        return optionList;

    }

    // 判断行 列各位数之和是否小宇k
    public boolean isLessK(int row, int col) {
        int sum = row % 10;
        while(row / 10 != 0) {
            row = row / 10;
            sum = sum + row % 10;

        }
        sum = sum + col % 10;
        while(col / 10 != 0) {
            col = col / 10;
            sum = sum + col % 10;

        }

        if (sum > k) {
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    public Boolean contains(List<int []> pathList, int [] position) {
        for (int [] path: pathList) {
            if (path[0] == position[0] && path[1] == position[1]) {
                return Boolean.TRUE;
            }
        }
        return Boolean.FALSE;
    }

}

分析

  1. 每条递归到底的路径通过一个List<int[]> 来记录.

  2. 通过遍历list来判断某个格子是否走过

  3. 最后的结果通过合并所有的list, 得到一个没有重复的list, 然后通过其大小得到所有可能走到的格子.

优点:

  1. 该算法本质就是DFS, 通过递归和回退来探索每一条可能的路径, 也设置了相应的备忘录来防止走回头路.

缺点:

  1. 采用的备忘录方式太笨重, 没必要记录每一条递归的具体路径, 因为我只是想判断该格子是否走过

  2. 统计最后结果的方式也太愚蠢, 因为题目不需要知道到底是哪些格子被走到了, 只需要一个数量

  3. 具体执行的时候, 当格子多了之后, 算法运行很慢, 会超时

改良版

import java.util.ArrayList;
import java.util.List;


public class Main {


    public static void main(String[] args) {

        Main main = new Main();
        main.movingCount(1, 2, 1);

    }

    int m = 0;
    int n = 0;
    int k = 0;
    boolean [][] visited;
    int count = 1;

    public int movingCount(int m, int n, int k) {
        this.m = m;
        this.n = n;
        this.k = k;
        this.visited = new boolean[m][n];
        visited[0][0] = Boolean.TRUE;
        huisu(new int[] {0, 0});

        return this.count;

    }

    public void huisu(int [] position) {
        // 由position的到选择列表, 同时剔除非法选择
        List<int []> optionList = getOptionList(position);

        if (optionList.size() == 0) {
            return;
        }

        for (int i = 0; i < optionList.size(); i ++) {
            int [] option = optionList.get(i);


            // 作出选择
            huisu(option);

        }



    }

    public List<int []> getOptionList(int [] position) {
        List<int []> optionList = new ArrayList<>();
        int row = position[0];
        int col = position[1];
        int [] rowOffset = new int [] {1, -1, 0, 0};
        int [] colOffset = new int [] {0, 0, 1, -1};

        for (int i = 0; i < 4; i++) {
            int targetRow = row + rowOffset[i];
            int targetCol = col + colOffset[i];
            int [] option = new int[]{targetRow, targetCol};
            if (targetRow < 0 || targetRow >= m || targetCol < 0 || targetCol >= n || !isLessK(targetRow, targetCol) || visited[targetRow][targetCol]) {
                continue;
            }
            visited[targetRow][targetCol] = Boolean.TRUE;
            count ++;
            optionList.add(option);
        }

        return optionList;

    }

    // 判断行 列各位数之和是否小宇k
    public boolean isLessK(int row, int col) {
        int sum = row % 10;
        while(row / 10 != 0) {
            row = row / 10;
            sum = sum + row % 10;

        }
        sum = sum + col % 10;
        while(col / 10 != 0) {
            col = col / 10;
            sum = sum + col % 10;

        }

        if (sum > k) {
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }



}
  1. 备忘录采用二维布尔类型的数组来记录某个格子是否走过

  2. 最后的结果只通过简单的计数完成

  3. 不会超时

BFS的写法


import java.util.LinkedList;
import java.util.Queue;


public class Main {


    public static void main(String[] args) {

        Main main = new Main();
        main.movingCount(38, 15, 9);

    }


    public int movingCount(int m, int n, int k) {
        if (k == 0) {
            return 1;
        }

        Queue<int[]> queue = new LinkedList<int[]>();
        // 记录格子
        boolean [][] visited = new boolean[m][n];
        // 添加起点
        queue.offer(new int[] {0, 0});
        visited[0][0] = Boolean.TRUE;

        int count = 1;

        int [] rowOffset = new int [] {1, -1, 0, 0};
        int [] colOffset = new int [] {0, 0, 1, -1};

        while(!queue.isEmpty()) {
            int [] position = queue.poll();
            // 扩散节点
            int row = position[0];
            int col = position[1];

            for (int i = 0; i < 4; i ++) {
                int targetRow = row + rowOffset[i];
                int targetCol = col + colOffset[i];
                if (get(targetRow) + get(targetCol) != sum(targetRow, targetCol)) {
                    System.out.println();
                }
                if (targetRow < 0 || targetRow >= m || targetCol < 0 || targetCol >= n || get(targetRow) + get(targetCol) > k || visited[targetRow][targetCol]) {
                    continue;
                }
                queue.offer(new int[]{targetRow, targetCol});
                visited[targetRow][targetCol] = Boolean.TRUE;
                count ++;

            }

        }

        return count;

    }

    private int get(int x) {
        int res = 0;
        while (x != 0) {
            res += x % 10;
            x /= 10;
        }
        return res;
    }



}

DFS的写法

import java.util.ArrayList;
import java.util.List;


public class Main {


    public static void main(String[] args) {

        Main main = new Main();
        main.movingCount(11, 8, 16);

    }

    int m = 0;
    int n = 0;
    int k = 0;
    boolean [][] visited;

    public int movingCount(int m, int n, int k) {
        this.m = m;
        this.n = n;
        this.k = k;
        visited = new boolean[m][n];
        return dfs(0, 0);


    }

    public int dfs(int row, int col) {
        if (row < 0 || row >= m || col < 0 || col >= n || get(row) + get(col) > k || visited[row][col]) {
            return 0;
        }
        visited[row][col] = Boolean.TRUE;

        return 1 + dfs(row + 1, col) + dfs(row - 1, col) + dfs(row, col + 1) + dfs(row, col - 1);

    }

    private int get(int x) {
        int res = 0;
        while (x != 0) {
            res += x % 10;
            x /= 10;
        }
        return res;
    }


}

2.24日回溯算法重写

int m;
    int n;
    int k;
    boolean [][] visited;
    int maxCount = 1;
    public int movingCount(int m, int n, int k) {
        this.m = m;
        this.n = n;
        this.k = k;
        // visited数组用于记录机器人是否到达过该方格
        this.visited = new boolean[m][n];
        visited[0][0] = Boolean.TRUE;
        digui(new int[]{0, 0});
        return maxCount;

    }

    private void digui(int [] position) {
        // 获得选择列表
        List<int []> optionList = getOptionList(position);
        if (optionList.size() == 0) {
            // 当没有选择的时候, 记录一下结果
            // maxCount = Math.max(maxCount, count);
            return;
        }

        for(int [] option : optionList) {
            // 做出选择
            visited[option[0]][option[1]] = Boolean.TRUE;
            digui(option);

        }

    }


    // 根据当前位置, 得到下一步可以走的选择列表
    private List<int []> getOptionList(int [] position) {
        List<int []> optionList = new ArrayList<>();
        int row = position[0];
        int col = position[1];
        // 移动有上下左右四种可能
        int [] rowOffset = new int[] {1, -1, 0, 0};
        int [] colOffset = new int[] {0, 0, 1, -1};
        for (int i = 0; i < 4; i ++) {
            int targetRow = row + rowOffset[i];
            int targetCol = col + colOffset[i];
            if (targetRow >=0 && targetRow < m && targetCol >= 0 && targetCol < n && judge(targetRow, targetCol, k) && !visited[targetRow][targetCol]) {
                maxCount ++;
                visited[targetRow][targetCol] = Boolean.TRUE;
                optionList.add(new int[] {targetRow, targetCol});
            }
        }
        return optionList;
    }

    private boolean judge(int row, int col, int k) {
        // 判断row和col位数之和是否大于k
        int sum = 0;
        while (row != 0) {
            sum += row % 10;
            row = row / 10;
        }
        while (col != 0) {
            sum += col % 10;
            col = col / 10;
        }
        if (sum <= k) {
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

错误的地方

  1. 没有正确的设置初始值
visited[0][0] = Boolean.TRUE; 
int maxCount = 1;
  1. 累加变量以及visited数组赋值位置的问题

即下面两行代码的位置

maxCount ++;
visited[targetRow][targetCol] = Boolean.TRUE;

如果我们是在获取选择列表的时候, 来判断哪些选择不能选的时候, 这两个代码就应该放在获取选择列表的里面, 这样保证了总体上不会获得重复的选择, 因为你如果放在进行选择的地方才赋值的话, 就会出现如下的情况, 有一个选择列表是1, 2, 3然后因为回溯是类似DFS的算法, 那么选择了1进行之后的选择, 就可能又生成了3这个选择(因为第一个选择列表还没遍历到3, 因此visited是没有记录3的), 这样就会产生重复选择(两个3), maxCount也是同理

如果我们是在选择列表那里剔除访问过的选择的话, 那么就可以把这两个代码放到选择列表那里, 因为还是上文那种情况如果从选择1出发, 到了选择3, 那么下次在第一个循环里的选择列表那里, 遇见3就会直接跳过了, 也不会出现重复选择