【LeetCode刷题总结】 BFS双端队列(BFS变异题)

BFS的模板其实已经比较熟练,那么在一些题目中,还会用到BFS双端队列模板,这时候就要看什么情况当前层会遍历结束,进入到下一层。

1.LeetCode6081 到达角落需要移除障碍物的最小数目

不难想到,移除的障碍数就是BFS遍历的层数。
在BFS的时候,如果当前位置不是障碍,那么当前位置不会进入下一层。代码中用双端队列(所谓双端队列,你可以从队头或队尾插入/删除一个元素)处理,对于不是障碍的位置加入到队列头部,当前层遍历的索引i–,障碍才会进入到下一层遍历中。
其实,只是对BFS的模板稍微改了一下,用到了双端队列。

class Solution {
    final int[] direction = new int[]{1,0,-1,0,1};

    public int minimumObstacles(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        Deque<int[]> q = new LinkedList<>();
        q.offer(new int[]{0, 0});
        boolean[][] visited = new boolean[m][n];
        visited[0][0] = true;
        int level = 0;
        while(!q.isEmpty())
        {
            int size = q.size();
            for(int i=0; i<size; i++)
            {
                int[] cur = q.poll();
                int x = cur[0], y = cur[1];
                if(x == m-1 && y == n-1)
                {
                    return level;
                }
                for(int k=0; k<direction.length-1; k++)
                {
                    int nextX = x + direction[k];
                    int nextY = y + direction[k+1];
                    if(nextX < 0 || nextX == m || nextY < 0 || nextY == n || visited[nextX][nextY]) continue;
                    visited[nextX][nextY] = true;
                    if(grid[nextX][nextY] == 0)
                    {
                    	//不是障碍, 加入到双端队列头部, 当前层遍历索引值i--
                        i--;
                        q.addFirst(new int[]{nextX, nextY});
                    }
                    else
                    {
                        q.addLast(new int[]{nextX, nextY});
                    }
                }
            }
            level++;
        }
        return level;
    }
}

2.LeetCode1368 使网格图至少有一条有效路径的最小代价

还是借用第一题的代码模板, 最少修改次数就是level值,进入到下一层的条件为:当前位置的方向与当前位置到下一个位置的方向不一样,比如,当前位置的方向为右(1),那么BFS遍历向下,这时候实际是下一层了,因为要修改一次当前位置的方向。
但是仅仅修改了条件,第2个用例,我们的结果是1,正确的结果应该是0。

grid = [[1,1,3],[3,2,2],[1,1,4]]
在这里插入图片描述

debug代码发现,在下一个位置加入到队列时,标记下一个位置为已访问。此时加入队列分为两种情况,一种是修改当前位置方向值,一种是不需修改。对于不修改的情况,可能需要继续遍历当前层所有位置才能满足,但如果此前该位置被标记为已访问过,那么下一个位置只能被归到下一层。
(第一题在套用BFS模板时,没有这个问题,是因为如果下一个位置为障碍,那么如果需要通过它访问到终点的位置,level必然加1,在入队时标记其为已访问是没问题的,这与正常的BFS模板也是一致的。)
对于第2题这种比较特殊的情况,需要将标记已访问过该位置的语句延迟到BFS遍历到该节点时。这样即使重复把一个位置加入到队列,但它们属于不同的层,如果当前层就BFS结束返回,下一层不会访问到。m和n都小于等于100,因此,这种看似与模板不一致的“错误”写法,既解决了题目的应用问题,也不会导致时间和内存的超出。

class Solution {
    final int[] directions = new int[]{1,0,-1,0,1};

    public int minCost(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        Deque<int[]> q = new LinkedList<>();
        q.offer(new int[]{0, 0});
        boolean[][] visited = new boolean[m][n];
        int level = 0;
        while(!q.isEmpty())
        {
            int size = q.size();
            for(int i=0; i<size; i++)
            {
                int[] cur = q.poll();
                int x = cur[0], y = cur[1];
                //将访问标记延迟到遍历该节点时,而不是加入队列时候。实际上,这种写法存在效率问题,但对于解决这道题,这样写更合理。
                visited[x][y] = true;
                if(x == m-1 && y == n-1)
                {
                    return level;
                }
                for(int k=0; k<directions.length-1; k++)
                {
                    int direct = getDirection(directions[k], directions[k+1]);
                    int nextX = x + directions[k];
                    int nextY = y + directions[k+1];
                    if(nextX < 0 || nextX == m || nextY < 0 || nextY == n || visited[nextX][nextY]) continue;
                    
                    if(grid[x][y] == direct)
                    {
                        i--;
                        q.addFirst(new int[]{nextX, nextY});
                    }
                    else
                    {
                        q.addLast(new int[]{nextX, nextY});
                    }
                }
            }
            level++;
        }
        return level;
    }

    private int getDirection(int x, int y)
    {
        if(x == 0)
        {
            if(y == 1) return 1;
            else return 2;
        }
        else
        {
            if(x == 1) return 3;
            else return 4;
        }
    }
}

3. LCP 56. 信物传送

这题与第2题就是一样了,调下参数即可。

class Solution {
    final int[] directions = new int[]{1,0,-1,0,1};
    final char[] signs = new char[]{'v', '<', '^', '>'};

    public int conveyorBelt(String[] grid, int[] start, int[] end) {
        int m = grid.length;
        int n = grid[0].length();
        Deque<int[]> q = new LinkedList<>();
        q.offer(new int[]{start[0], start[1]});
        boolean[][] visited = new boolean[m][n];
        int level = 0;
        while(!q.isEmpty())
        {
            int size = q.size();
            for(int i=0; i<size; i++)
            {
                int[] cur = q.poll();
                int x = cur[0], y = cur[1];
                visited[x][y] = true;
                if(x == end[0] && y == end[1])
                {
                    return level;
                }
                for(int k=0; k<directions.length-1; k++)
                {
                    int nextX = x + directions[k];
                    int nextY = y + directions[k+1];
                    if(nextX < 0 || nextX == m || nextY < 0 || nextY == n || visited[nextX][nextY]) continue;
                    
                    if(grid[x].charAt(y) == signs[k])
                    {
                        i--;
                        q.addFirst(new int[]{nextX, nextY});
                    }
                    else
                    {
                        q.addLast(new int[]{nextX, nextY});
                    }
                }
            }
            level++;
        }
        return level;
    }
}

关于BFS双端队列先写到这儿。BFS的变异题还有多次使用BFS,掌握了BFS模板,这些题只是脑筋急转弯的思维题,有空继续总结。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wsws100

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值