【周赛总结】二分+搜索,求秩问题,并查集的使用


第212场周赛。

1631. 最小体力消耗路径(二分+搜索)

在这里插入图片描述
在这里插入图片描述

很典型的题目,题目最后要求返回的答案是一个最大值,且我们很容易发现任何小于这个最大值的数都是无法被满足的,任何大于这个最大值的数都是可以被满足的。且判断在给定数值时,能否满足这个事情是比较容易的(BFS或者DFS)。因此我们应该想到用二分+搜索的方法。

另外一个思路是采用并查集,我们构建起来相邻的边,并且排序。从小到大进行连线,如同构造一个最小生成树,但是这里判断的条件是是否原点和最后的终点联通

class Solution {
    //方法一 二分+BFS
    public int minimumEffortPath(int[][] heights) {
        int max = 0;
        int min = 10000000;
        int n = heights.length;
        int m = heights[0].length;
        if (m == 1 && n == 1) return 0;
        for (int i = 0; i<n;i++){
            for (int j = 0; j<m;j++){
                min = Math.min(min, heights[i][j]);
                max = Math.max(max, heights[i][j]);
            }
        }
        int l = 0;
        int r = max-min;
        while (l<=r){
            int mid = (l+r)/2;
            if (check(heights, mid)){
                r = mid-1;
            }
            else l = mid+1;
        }
        return l;
    }
    public boolean check(int[][] heights, int min){
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{0,0});
        int n = heights.length;
        int m = heights[0].length;
        int[][] v = new int[n][m];
        v[0][0] = 1;
        int[][] dis =  new int[][]{{0,1}, {1,0}, {-1,0}, {0,-1}};
        int[][] dis =  {{0,1}, {1,0}, {-1,0}, {0,-1}};
        while (!queue.isEmpty()){
            int[] cur = queue.poll();
            if (cur[0] == n-1 && cur[1] == m-1) return true;
            for (int[] d:dis){
                int newx = cur[0]+d[0];
                int newy = cur[1]+d[1];
                if (0<=newx && newx<n && 0<=newy && newy<m && v[newx][newy] == 0 && Math.abs(heights[newx][newy]-heights[cur[0]][cur[1]])<=min){
                    v[newx][newy] = 1;
                    queue.offer(new int[]{newx, newy});
                }
            }
        }
        return false;
    }
    
======================================================================================
    // 方法二:并查集
    public int minimumEffortPath(int[][] heights) {
        int n = heights.length;
        int m = heights[0].length;
        UF uf = new UF(n*m);
        List<Edge> edges = new ArrayList<>();

        for (int i = 0; i<n;i++){
            for (int j = 0; j<m;j++){
                if (i+1<n) {
                   edges.add(new Edge(i*m+j, (i+1)*m+j, Math.abs(heights[i][j]-heights[i+1][j]))); 
                }
                if (j+1<m) edges.add(new Edge(i*m+j, i*m+j+1, Math.abs(heights[i][j]-heights[i][j+1])));
            }
        }
        int ans = 0;
        Collections.sort(edges, (a, b)->a.value-b.value);
        for (Edge e:edges){
            int a = e.v;
            int b = e.w;
            uf.union(a,b);
            ans = Math.max(ans, e.value);
            if (uf.isUnion(0, n*m-1)) break;
        }
        return ans;
    }

    class Edge{
        int v;
        int w;
        int value;
        Edge(int v, int w, int value) {
            this.v = v;
            this.w = w;
            this.value = value;
        }
    }

    class UF{
        int[] fa;
        int[] sz;
        public UF(int n){
            fa = new int[n];
            for (int i = 0; i<n;i++)fa[i] = i;
            sz = new int[n];
            for (int i = 0; i<n; i++)sz[i] = 1;
        }

        public int find(int x){
            if(fa[x] == x)return fa[x];
            fa[x] = find(fa[x]);
            return fa[x];
        }

        public boolean isUnion(int i, int j){
            return find(i) == find(j);
        }

        public void union(int x, int y){
            int xfa = find(x);
            int yfa = find(y);
            if (xfa == yfa) return;
            if(sz[xfa]>=sz[yfa]){
                sz[xfa] += sz[yfa];
                fa[yfa] = xfa;
            }else{
                sz[yfa] += sz[xfa];
                fa[xfa] = yfa;
            }
        }
    }
}

1632. 矩阵转换后的秩(求秩问题,并查集的使用)

在这里插入图片描述

在这里插入图片描述

首先我们先来简化问题,如果不考虑需要让数值相等的点的秩相同这个条件。我们如何求解这个问题。

方法是我们 首先对矩阵的元素排序,得到从小到大的队列。然后我们维护一个每一行和每一列的最大的位置,xmax表示这x一行中最大的数字在哪一列,ymax表示这y一列中最大的数字在哪一行。这样我们每次加入新的数字只需要满足val[cur] = max(ymax(y), xmax(x))+1,然后修改xmax(x) = y, ymax(y) = x

但是题目特别要求的了同一行和同列的数字相同的位置的秩相同。这样我们就需要对这些相同位置的点进行合并处理了,统一作为一个整体,每次修改都是统一的。这里我们用到了并查集。

对于二维数组的问题,一个很重要的思想就是要把二维数组铺平,变成一维数组。

class Solution {
    public int[][] matrixRankTransform(int[][] matrix) {
        int n = matrix.length;
        int m = matrix[0].length;
        int sum = n*m;
        UF uf = new UF(sum);
        int[] xmax = new int[n]; // 维护最大的行位置
        int[] ymax = new int[m]; // 维护最大的列位置
        Arrays.fill(xmax, -1);
        Arrays.fill(ymax, -1);
        
        // !!!!==========!!!
		//这里必须是Integer,因为Comparator接口要求是一个类
        Integer[] index = new Integer[sum]; 
        
        int[] val = new int[sum]; // 存储每次修改之后的秩 
        int[][] ans = new int[n][m];
        for (int i = 0; i<n;i++){
            for (int j = 0; j<m;j++){
                index[i*m+j] = i*m+j;
            }
        }
		
        Arrays.sort(index, (Integer i, Integer j) -> matrix[i/m][i%m]-matrix[j/m][j%m]);
        /*
        Arrays.sort(index, new Comparator<Integer>(){ 
        // 注意拼写,Comparator  需要重写的方法是compare 并且要求了类,因此必须Integer
            //@override
            public int compare(Integer i, Integer j){
                return matrix[i/m][i%m]-matrix[j/m][j%m];
            }
        });
        */
        for (int i = 0; i<sum; i++){
            int cur = index[i];
            int x = cur/m;
            int y = cur%m;
            int valcur = 1;

            if (xmax[x]!=-1){
                int k = xmax[x]; // 当前行的最大列
                int maxindex = x*m+k; // 坐标转换
                if (matrix[x][y] == matrix[x][k]){
                	// 这里一定要是根节点作为索引。
                    valcur = val[uf.find(maxindex)]; // 坐标相等,这里需要与根节点一样
                    uf.union(cur, maxindex); // 链接
                }
                else {
                    valcur = val[uf.find(maxindex)]+1;
                }
            }

            if (ymax[y]!=-1){
                int k = ymax[y];
                int maxindex = k*m+y;
                if (matrix[x][y] == matrix[k][y]){
                    valcur = Math.max(val[uf.find(maxindex)], valcur); // 进行比较
                    uf.union(cur, maxindex);
                }
                else {
                    valcur = Math.max(val[uf.find(maxindex)]+1, valcur);
                }
            }
            // 更新最大行和最大列的信息,并且更新当前节点的根节点的数值。
            xmax[x] = y;
            ymax[y] = x;
            val[uf.find(cur)] = valcur;
        }
        
        for (int i = 0; i<n;i++){
            for (int j = 0; j<m; j++){
                int cur = i*m+j;
                ans[i][j] = val[uf.find(cur)];
            }
        }
        return ans;

    }

    class UF{
        int[] fa;
        int[] sz;
        public UF(int n){
            fa = new int[n];
            for (int i = 0; i<n;i++)fa[i] = i;
            sz = new int[n];
            for (int i = 0; i<n; i++)sz[i] = 1;
        }

        public int find(int x){
            if(fa[x] == x)return fa[x];
            fa[x] = find(fa[x]);
            return fa[x];
        }

        public boolean isUnion(int i, int j){
            return find(i) == find(j);
        }

        public void union(int x, int y){
            int xfa = find(x);
            int yfa = find(y);
            if (xfa == yfa) return;
            if(sz[xfa]>=sz[yfa]){
                sz[xfa] += sz[yfa];
                fa[yfa] = xfa;
            }else{
                sz[yfa] += sz[xfa];
                fa[xfa] = yfa;
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值