LeetCode每日一题(20200820)

原题地址

思考过程:

分为三种情况,1.如果点击的是雷,修改为‘X’然后返回;2.如果点击的是和雷相连的点,找到这个点周围的雷数量k,修改为‘k’然后返回;3.如果点击的是空白方块,修改为‘B’,然后广度优先处理这个点周围的点,并记录下处理过的点。

代码实现:

    char unknownMineC = 'M';//未挖出的地雷
    char mineC = 'X';//挖出的地雷
    char unknownC = 'E';//未挖出的空方块
    char emptyC = 'B';//挖出的空白方块
    char[] numbersC = {'1', '2', '3', '4', '5', '6', '7', '8'};

    public char[][] updateBoard(char[][] board, int[] click) {
        int x = click[0];
        int y = click[1];

        //地雷被挖出
        if (board[x][y] == unknownMineC) {
            board[x][y] = mineC;
            return board;
        }

        //获取点击的位置,有相连地雷的数量
        List<int[]> list = getLinkedPosition(board, click);
        int num = getMineNum(board, list);

        //有1个以上地雷与点击位置相连,该位置值改为地雷数,返回
        if (num > 0) {
            board[x][y] = numbersC[num - 1];
            return board;
        }

        //没有地雷与点击位置相连,该位置值改为挖出的空白方块,并处理所有与该点相连的点
        board[x][y] = emptyC;
        Set<String> history = new HashSet<>();//用于记录已经处理过的位置
        history.add(x + "-" + y);
        return handle(board, list, history);

    }

    //输入n个位置,返回这些位置中有雷的个数
    private int getMineNum(char[][] board, List<int[]> list) {
        int num = 0;
        for (int[] p : list) {
            char c = board[p[0]][p[1]];
            if (unknownMineC == c || mineC == c) {
                num++;
            }
        }
        return num;
    }

    //输入与空白方块相连的点,判断这些点的其他相连点,是否与雷相连,
    // 如果相连,点的值修改为雷个数,如果不相连,点的值修改为空白方块,且递归处理与这个点相连的其他点
    private char[][] handle(char[][] board, List<int[]> positions, Set<String> history) {
        List<int[]> emptyList = new ArrayList<>();
        for (int[] p : positions) {
            if (!history.contains(p[0] + "-" + p[1])) {
                history.add(p[0] + "-" + p[1]);

                List<int[]> list = getLinkedPosition(board, p);
                int num = getMineNum(board, list);
                if (num > 0) {
                    //有n个地雷相连,修改值为n
                    board[p[0]][p[1]] = numbersC[num - 1];
                } else {
                    //没有地雷与输入位置相连,所有相连点需要再次判断
                    board[p[0]][p[1]] = emptyC;
                    emptyList.addAll(list);
                }
            }
        }
        if (emptyList.size() == 0) {
            return board;
        } else return handle(board, emptyList, history);
    }

    //输入位置,返回与此位置相连的所有位置(3-8个)
    private List<int[]> getLinkedPosition(char[][] board, int[] p) {
        int x = p[0];
        int y = p[1];
        int maxX = board.length;
        int maxY = board[0].length;
        List<int[]> list = new ArrayList<>();
        if (x - 1 >= 0) {
            list.add(new int[]{x - 1, y});
        }
        if (y - 1 >= 0) {
            list.add(new int[]{x, y - 1});
        }
        if (x - 1 >= 0 && y - 1 >= 0) {
            list.add(new int[]{x - 1, y - 1});
        }
        if (x - 1 >= 0 && y + 1 < maxY) {
            list.add(new int[]{x - 1, y + 1});
        }
        if (x + 1 < maxX) {
            list.add(new int[]{x + 1, y});
        }
        if (y + 1 < maxY) {
            list.add(new int[]{x, y + 1});
        }
        if (x + 1 < maxX && y - 1 >= 0) {
            list.add(new int[]{x + 1, y - 1});
        }
        if (x + 1 < maxX && y + 1 < maxY) {
            list.add(new int[]{x + 1, y + 1});
        }
        return list;
    }

执行结果:

耗时太高了。。。思路应该是没有太大问题,可能在代码实现上应该优化。

 

算法复杂度分析:

时间复杂度:O(n*m),其中 n,m是行数和列数。

空间复杂度:O(n*m),其中 n,m是行数和列数。

 

查看官方解法:

官方提供了深度优先和广度优先两种解法

先看广度优先代码,代码如下:

    int[] dirX = {0, 1, 0, -1, 1, 1, -1, -1};
    int[] dirY = {1, 0, -1, 0, 1, -1, 1, -1};

    public char[][] updateBoard(char[][] board, int[] click) {
        int x = click[0], y = click[1];
        if (board[x][y] == 'M') {
            // 规则 1
            board[x][y] = 'X';
        } else{
            bfs(board, x, y);
        }
        return board;
    }

    public void bfs(char[][] board, int sx, int sy) {
        Queue<int[]> queue = new LinkedList<int[]>();
        boolean[][] vis = new boolean[board.length][board[0].length];
        queue.offer(new int[]{sx, sy});
        vis[sx][sy] = true;
        while (!queue.isEmpty()) {
            int[] pos = queue.poll();
            int cnt = 0, x = pos[0], y = pos[1];
            for (int i = 0; i < 8; ++i) {
                int tx = x + dirX[i];
                int ty = y + dirY[i];
                if (tx < 0 || tx >= board.length || ty < 0 
                                       || ty >= board[0].length) {
                    continue;
                }
                // 不用判断 M,因为如果有 M 的话游戏已经结束了
                if (board[tx][ty] == 'M') {
                    ++cnt;
                }
            }
            if (cnt > 0) {
                // 规则 3
                board[x][y] = (char) (cnt + '0');
            } else {
                // 规则 2
                board[x][y] = 'B';
                for (int i = 0; i < 8; ++i) {
                    int tx = x + dirX[i];
                    int ty = y + dirY[i];
                    // 这里不需要在存在 B 的时候继续扩展,因为 B 之前被点击的时候已经被扩展过了
                    if (tx < 0 || tx >= board.length || ty < 0 
                    || ty >= board[0].length || board[tx][ty] != 'E' 
                    || vis[tx][ty]) {
                        continue;
                    }
                    queue.offer(new int[]{tx, ty});
                    vis[tx][ty] = true;
                }
            }
        }
    }

一如既往的简洁高效,也一如既往的难理解。。。

深度优先代码:

 int[] dirX = {0, 1, 0, -1, 1, 1, -1, -1};
    int[] dirY = {1, 0, -1, 0, 1, -1, 1, -1};

    public char[][] updateBoard(char[][] board, int[] click) {
        int x = click[0], y = click[1];
        if (board[x][y] == 'M') {
            // 规则 1
            board[x][y] = 'X';
        } else {
            dfs(board, x, y);
        }
        return board;
    }

    public void dfs(char[][] board, int x, int y) {

        //找到与输入点相连的8个点(可能小于8)
        int cnt = 0;
        for (int i = 0; i < 8; ++i) {
            int tx = x + dirX[i];
            int ty = y + dirY[i];
            if (tx < 0 || tx >= board.length || ty < 0 || ty >= board[0].length) {
                continue;
            }
            // 不用判断 X,因为如果有 X 的话游戏已经结束了
            if (board[tx][ty] == 'M') {
                ++cnt;
            }
        }
        //判断这8个点中雷的数量,如果有雷,执行规则3,结束
        if (cnt > 0) {
            // 规则 3
            board[x][y] = (char) (cnt + '0');
        } else {

            //如果没有雷,递归处理每一个点
            // 规则 2
            board[x][y] = 'B';
            for (int i = 0; i < 8; ++i) {
                int tx = x + dirX[i];
                int ty = y + dirY[i];
                // 这里不需要在存在 B 的时候继续扩展,因为 B 之前被点击的时候已经被扩展过了
                if (tx < 0 || tx >= board.length || ty < 0 || ty >= board[0].length 
                || board[tx][ty] != 'E') {
                    continue;
                }
                dfs(board, tx, ty);
            }
        }
    }

 

借鉴:

对比官方提供的代码,我觉得可以借鉴的地方有如下:

1.使用两个数组(int[] dirX 和 int[] dirY)表示周围可能的8个点,在找周围点时,代码可以更优雅和高效。

2.使用char的值来判断点是否处理过,没有处理过的点值为'E',而不用另外再用个Set,增加多余的消耗。

3.做深度优先处理时,只需要传int[][]到方法中处理,不用返回int[][],传进来的那个对象,值已经被方法改变了。

借鉴后代码如下:


    char unknownMineC = 'M';//未挖出的地雷
    char mineC = 'X';//挖出的地雷
    char unknownC = 'E';//未挖出的空方块
    char emptyC = 'B';//挖出的空白方块
    char[] numbersC = {'1', '2', '3', '4', '5', '6', '7', '8'};


    int[] dirX0 = {0, 1, 0, -1, 1, 1, -1, -1};
    int[] dirY0 = {1, 0, -1, 0, 1, -1, 1, -1};

    /*
  执行用时:26 ms, 在所有 Java 提交中击败了5.94%的用户
  内存消耗:40.1 MB, 在所有 Java 提交中击败了44.39%的用户
  * */
    public char[][] updateBoard1(char[][] board, int[] click) {
        int x = click[0];
        int y = click[1];

        //地雷被挖出
        if (board[x][y] == unknownMineC) {
            board[x][y] = mineC;
            return board;
        }

        //获取点击的位置,有相连地雷的数量
        List<int[]> list = getLinkedPosition(board, click);
        int num = getMineNum(board, list);

        //有1个以上地雷与点击位置相连,该位置值改为地雷数,返回
        if (num > 0) {
            board[x][y] = numbersC[num - 1];
            return board;
        }

        //没有地雷与点击位置相连,该位置值改为挖出的空白方块,并处理所有与该点相连的点
        board[x][y] = emptyC;
        handle(board, list);
        return board;

    }

    //输入n个位置,返回这些位置中有雷的个数
    private int getMineNum(char[][] board, List<int[]> list) {
        int num = 0;
        for (int[] p : list) {
            char c = board[p[0]][p[1]];
            if (unknownMineC == c || mineC == c) {
                num++;
            }
        }
        return num;
    }

    //输入与空白方块相连的点,判断这些点的其他相连点,是否与雷相连,
    // 如果相连,点的值修改为雷个数,如果不相连,点的值修改为空白方块,且递归处理与这个点相连的其他点
    private void handle(char[][] board, List<int[]> positions) {
        List<int[]> emptyList = new ArrayList<>();
        for (int[] p : positions) {

            //使用char的值来判断是否处理过,为’E‘时才是没有处理过的
            if (board[p[0]][p[1]] == unknownC) {

                List<int[]> list = getLinkedPosition(board, p);
                int num = getMineNum(board, list);
                if (num > 0) {
                    //有n个地雷相连,修改值为n
                    board[p[0]][p[1]] = numbersC[num - 1];
                } else {
                    //没有地雷与输入位置相连,所有相连点需要再次判断
                    board[p[0]][p[1]] = emptyC;
                    emptyList.addAll(list);
                }
            }
        }
        if (emptyList.size() > 0) {
            handle(board, emptyList);
        }
    }

    //输入位置,返回与此位置相连的所有位置(3-8个)
    private List<int[]> getLinkedPosition(char[][] board, int[] p) {
        int x = p[0];
        int y = p[1];
        int maxX = board.length;
        int maxY = board[0].length;
        List<int[]> list = new ArrayList<>();
        for (int i = 0; i < 8; ++i) {
            int tx = x + dirX0[i];
            int ty = y + dirY0[i];
            if (tx < 0 || tx >= maxX || ty < 0 || ty >= maxY) {
                continue;
            }
            list.add(new int[]{tx, ty});
        }
        return list;
    }

执行结果:

有一定提升,和官方广度优先代码执行结果差不多。

总结:

同样的解题思路,实现出来的代码却可以有天壤之别。代码的简洁和高效上,还需要提高。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值