LC-6260. 矩阵查询可获得的最大分数(最小堆,并查集+离线(海平面上升问题))【周赛323】

6260. 矩阵查询可获得的最大分数

难度困难7

给你一个大小为 m x n 的整数矩阵 grid 和一个大小为 k 的数组 queries

找出一个大小为 k 的数组 answer ,且满足对于每个整数 queres[i] ,你从矩阵 左上角 单元格开始,重复以下过程:

  • 如果 queries[i] 严格 大于你当前所处位置单元格,如果该单元格是第一次访问,则获得 1 分,并且你可以移动到所有 4 个方向(上、下、左、右)上任一 相邻 单元格。
  • 否则,你不能获得任何分,并且结束这一过程。

在过程结束后,answer[i] 是你可以获得的最大分数。注意,对于每个查询,你可以访问同一个单元格 多次

返回结果数组 answer

示例 1:

img

输入:grid = [[1,2,3],[2,5,7],[3,5,1]], queries = [5,6,2]
输出:[5,8,1]
解释:上图展示了每个查询中访问并获得分数的单元格。

示例 2:

img

输入:grid = [[5,2,1],[1,1,2]], queries = [3]
输出:[0]
解释:无法获得分数,因为左上角单元格的值大于等于 3 。

提示:

  • m == grid.length
  • n == grid[i].length
  • 2 <= m, n <= 1000
  • 4 <= m * n <= 105
  • k == queries.length
  • 1 <= k <= 104
  • 1 <= grid[i][j], queries[i] <= 106

暴力模拟(超时)

class Solution {
    int[][] dirt = {{0,1},{0,-1},{1,0},{-1,0}};
    boolean[][] visited;
    int n,m;
    public int[] maxPoints(int[][] grid, int[] queries) {
        n = grid.length;
        m = grid[0].length;
        int[] res = new int[queries.length];
        for(int i = 0; i < res.length; i++){
            if(grid[0][0] > queries[i]) continue;
            visited = new boolean[n][m];
            visited[0][0] = true;
            res[i] = dfs(0,0,queries[i],grid);
        }
        return res;
    }
    
    public int dfs(int i, int j, int max, int[][] grid){
        if(grid[i][j] >= max) return 0;
        int res = 1;
        for(int k = 0; k < dirt.length; k++){
            int newx = i + dirt[k][0],newy = j + dirt[k][1];
            if(newx >= 0 && newx < n && newy >= 0 && newy < m && !visited[newx][newy]){
                visited[newx][newy] = true;
                res += dfs(newx,newy,max,grid);
            }
        }
        return res;
    }
}

转化为海平面上升问题

https://leetcode.cn/problems/maximum-number-of-points-from-grid-queries/solution/by-liuliangcan-9osu/

  • 题目的询问query实际上limit,即询问:对于limit从左上角开始,能floodfill到的格子个数。
  • 再转换一下:左上角从虚空连接了高度limit的海平面,问能淹几个格子。
  • 那么把询问离线排序,从小到大处理即可。
  • 队列用小顶堆存,含义为:边界的格子最矮值先被淹。
  • 于是只有堆顶小于limit时才需要继续搜索;否则等待海平面上升。

题解来自:0x3f : https://leetcode.cn/problems/maximum-number-of-points-from-grid-queries/solution/by-endlesscheng-qeei/

方法一:并查集

一看到这种具有大小关系的查询,直接就是经典的并查集加离线算法套路,力扣上有很多类似的题目,周赛双周赛就出现了好多次这样的!

把矩阵的元素值从小到大排序,询问也从小到大排序。

用双指针遍历矩阵元素值和询问,如果矩阵元素值小于询问值,就把该格子和周围四个格子中的小于询问值的格子相连。

用并查集可以实现相连的过程,同时维护每个连通块的大小。

答案就是左上角的连通块的大小(前提是左上角小于询问值)。

如果每次询问一次,dfs 一次,很可能超时,因此可以用并查集。离线处理,把边按权值排序,把问题按大小排序。然后离线的过程就是不断向图中加边的过程。

# 网格图
# 边是什么?边权是什么?
# 抽象成图
# 答案就是包含左上角的连通块的大小
# 连通块需要满足,点权 < q
# 点权处理起来比较麻烦,转换成边权就比较方便了,可以直接用并查集合并

# 怎么转换成边权?
# 把两个点连起来,需要满足什么条件?
#   - 两个点的点权都是 < q
# 边权 = 两个点的点权的最大值

# 边权排序 询问排序
# 双指针遍历 边权 和 询问
# 每次把 < q 的边 所对应的两个点合并起来
# 答案 就是左上角(0)的连通块大小

python

class Solution:
    def maxPoints(self, grid: List[List[int]], queries: List[int]) -> List[int]:
        # 方法一:并查集
        m,n = len(grid), len(grid[0])
        mn = m*n

        # 并查集模板
        fa = list(range(mn))
        size = [1] * mn # 自成一个大小为1的连通块
        def find(x: int) -> int:
            if fa[x] != x:
                fa[x] = find(fa[x])
            return fa[x]
        def merge(f: int, to: int) -> None:
            f = find(f)
            to = find(to)
            if f != to:
                fa[f] = to
                size[to] += size[f]
                
        # 根据邻居关系进行建图处理
        edges = []
        for i, row in enumerate(grid):
            for j,x in enumerate(row): # 对每个点只与其上面和左边建立边
                if i > 0: # 建边(边权,当前点,上/左点)
                    edges.append((max(x,grid[i-1][j]),i*n+j,(i-1)*n+j)) # 当前点与上面点连边 
                if j > 0: 
                    edges.append((max(x,grid[i][j-1]),i*n+j,i*n+j-1)) # 当前点与左边点连边
        edges.sort(key=lambda p:p[0]) # 根据边权排序

        ans = [0]*len(queries)
        j = 0
        # 按照查询值的大小排序,依次进行查询
        for i,q in sorted(enumerate(queries), key = lambda p:p[1]): # 将queries值和下标合起来 根据值排序
            # 根据查询值的大小利用指针持续更新并查集
            while j < len(edges) and edges[j][0] < q:
                merge(edges[j][1],edges[j][2])
                j += 1
            if grid[0][0] < q:
                ans[i] = size[find(0)]
        return ans

java

class Solution {
    private static final int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    private int[] fa, size;

    public int[] maxPoints(int[][] grid, int[] queries) {
        int m = grid.length, n = grid[0].length, mn = m * n;

        // 并查集
        fa = new int[mn];
        for (var i = 0; i < mn; i++) fa[i] = i;
        size = new int[mn];
        Arrays.fill(size, 1);

        // 矩阵元素从小到大排序,方便离线
        var a = new int[mn][3];
        for (var i = 0; i < m; ++i)
            for (var j = 0; j < n; ++j)
                a[i * n + j] = new int[]{grid[i][j], i, j};
        Arrays.sort(a, (p, q) -> p[0] - q[0]);

        // 查询的下标按照查询值从小到大排序,方便离线
        var k = queries.length;
        var id = IntStream.range(0, k).boxed().toArray(Integer[]::new);
        Arrays.sort(id, (i, j) -> queries[i] - queries[j]);

        var ans = new int[k];
        var j = 0;
        for (var i : id) {
            var q = queries[i];
            for (; j < mn && a[j][0] < q; ++j) {
                int x = a[j][1], y = a[j][2];
                // 枚举周围四个格子,值小于 q 才可以合并
                for (var d : dirs) {
                    int x2 = x + d[0], y2 = y + d[1];
                    if (0 <= x2 && x2 < m && 0 <= y2 && y2 < n && grid[x2][y2] < q)
                        merge(x * n + y, x2 * n + y2); // 把坐标压缩成一维的编号
                }
            }
            if (grid[0][0] < q)
                ans[i] = size[find(0)]; // 左上角的连通块的大小
        }
        return ans;
    }

    // 并查集模板
    private int find(int x) {
        if (fa[x] != x) fa[x] = find(fa[x]);
        return fa[x];
    }

    private void merge(int from, int to) {
        from = find(from);
        to = find(to);
        if (from != to) {
            fa[from] = to;
            size[to] += size[from];
        }
    }
}

方法二:最小堆

仍然是离线询问,还可以从左上角出发向外搜索,用最小堆,初始把左上角的元素值及其坐标入堆。对每个询问,不断循环,如果堆顶元素值小于询问值,则弹出堆顶,继续搜索。

循环结束时,出堆的元素个数就是答案。

# 初始,把左上角和坐标(grid[0][0], 0, 0) 放到一个最小堆中
# 遍历排序后的询问,再套一个while循环
# 不断取堆顶,直到堆为空,或者 堆顶 >= q
#   循环内部:把这个位置向四种拓展,把拓展后的格子的值和坐标加到堆中
#   统计循环次数,或者 <q  的值的个数 cnt
# 循环结束之后,cnt 就是答案

python

class Solution:
    def maxPoints(self, grid: List[List[int]], queries: List[int]) -> List[int]:
        m,n = len(grid), len(grid[0])
        ans = [0]*len(queries)
        h = [(grid[0][0], 0, 0)] # 将左上角放到堆中
        grid[0][0] = 0 # 表示访问过了
        cnt = 0
        # 按照查询值的大小排序,依次进行查询
        for qi,q in sorted(enumerate(queries), key = lambda p:p[1]):
            # 不断取堆顶,直到堆为空,或者 堆顶 >= q
            while h and h[0][0] < q:
                cnt += 1
                val, i, j = heappop(h)
                # 遍历结点四周
                for x, y in (i+1,j),(i-1,j),(i,j+1),(i,j-1):
                    if 0<=x<m and 0<=y<n and grid[x][y]: # 合法且未访问
                        heappush(h, (grid[x][y], x, y))
                        grid[x][y] = 0
            ans[qi] = cnt
        return ans

java

class Solution {
    private static final int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    public int[] maxPoints(int[][] grid, int[] queries) {
        // 查询的下标按照查询值从小到大排序,方便离线
        var k = queries.length;
        var id = IntStream.range(0, k).boxed().toArray(Integer[]::new);
        Arrays.sort(id, (i, j) -> queries[i] - queries[j]);

        var ans = new int[k];
        var pq = new PriorityQueue<int[]>((a, b) -> a[0] - b[0]);
        pq.add(new int[]{grid[0][0], 0, 0});
        grid[0][0] = 0; // 充当 vis 数组的作用
        int m = grid.length, n = grid[0].length, cnt = 0;
        for (var i : id) {
            var q = queries[i];
            while (!pq.isEmpty() && pq.peek()[0] < q) {
                ++cnt;
                var p = pq.poll();
                for (var d : dirs) { // 枚举周围四个格子
                    int x = p[1] + d[0], y = p[2] + d[1];
                    if (0 <= x && x < m && 0 <= y && y < n && grid[x][y] > 0) {
                        pq.add(new int[]{grid[x][y], x, y});
                        grid[x][y] = 0; // 充当 vis 数组的作用
                    }
                }
            }
            ans[i] = cnt;
        }
        return ans;
    }
}

相关题目

1697. 检查边长度限制的路径是否存在

在线算法:

  • 在线算法就是边输入边处理,不一定要等所有输入都完成以后才会给出相应的解答。

离线算法:

  • 在问题的一开始就要知道所有的数据,然后才能输出结果。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值