力扣技巧-并查集

最小生成树

【题目】给定一个图的点集,边集和权重,返回构建最小生成树的代价。

输入:N = 2, conn = [[1, 2, 37], [2, 1, 17], [1, 2, 68]]

输出:17

class Solution {

  private long cost = 0;

  // 这里直接申请了足够多的内存

  private int[] F = null;

  // 并查集初始化

  // 注意点的编号是从1~n

  private void Init(int n) {

    F = new int[n+1];

    for (int i = 0; i <= n; i++) {

      F[i] = i;

    }

    cost = 0;

  }

  private int Find(int x) {

    if (x == F[x]) {

      return x;

    }

    F[x] = Find(F[x]);

    return F[x];

  }

  // 在合并的时候,需要加上代价

  private void Union(int x, int y, int pay) {

    if (Find(x) != Find(y)) cost += pay;

    F[Find(x)] = Find(y);

  }

  // 一共有n个点,编号从1~n

  // conn表示输入的边的集合

  // 每一项是一个三元组[点a, 点b, 需要费用c]

  public long Kruskal(int n, int m, int[][] conn) {

    Init(n);

    // 边集的排序

    Arrays.sort(conn, 0, m, new Comparator<int[]>() {

      public int compare(int[] a, int[] b) {

        return a[2] - b[2];

      }

    });

    // 顺次将边集添加到集合中

    for (int i = 0; i < m; i++) {

      Union(conn[i][0], conn[i][1], conn[i][2]);

    }

    return cost;

  }

}

例 2:帮派的数目

【题目】江湖上有 N 个人,编号从 [1 ~ N],现在只能告诉你,其中两人是一个帮派的,请你输出帮派的数目。

输入:N = 4, [[1, 2], [2,3]]

输出:2

解释:一共有 4 个人,[1,2, 3] 成为一个帮派,[4] 独自成为一个帮派,那么一共有 2 个帮派。

int count = 0;

int[] F = null;

void Init(int n) {

  F = new int[n + 1];

  for (int i = 0; i <= n; i++) {

    F[i] = i;

  }

  count = n;

}

int Find(int x) {

  if (x == F[x]) {

    return x;

  }

  F[x] = Find(F[x]);

  return F[x];

}

void Union(int x, int y) {

  if (Find(x) != Find(y))

    count--;

  F[Find(x)] = Find(y);

}

int findGangNumber(int n, int[][] conn) {

  Init(n);

  int m = conn.length;

  for (int i = 0; i < m; i++) {

    Union(conn[i][0], conn[i][1]);

  }

  // 帮派里面帮主的个数

  return count;

}

延伸:如果将这里的每个点都当成一个“图”结构中的一个点,将两两成对的输入当成“图”结构中的边。那么问题就变成了求解图的连通域个数。

200. 岛屿数量
class Solution {

    private int[][] directions = new int[][]{
        {0, 1}, {1, 0}
    };
    private int[] F = null;
    private int count = 0;

    private void Init(int n) {
        F = new int[n];
        for (int i = 0; i < n; i ++) {
            F[i] = i;
        }
        count = n;
    }

    private int Find(int x) {
        if (x == F[x]) {
            return x;
        }
        F[x] = Find(F[x]);
        return F[x];
    }

    private void Union(int x, int y) {
        if (Find(x) != Find(y)) {
            count --;
        }
        F[Find(x)] = Find(y);
    }

    public int numIslands(char[][] grid) {

        final int m = grid.length;
        final int n = grid[0].length;

        Init(m * n);
        int blackNumber = 0;

        for (int i = 0; i < m; i ++) {
            for (int j = 0; j < n; j ++) {
                if ('1' == grid[i][j]) {
                    for (int d = 0; d < directions.length; d ++) {
                        int x = i + directions[d][0];
                        int y = j + directions[d][1];
                        if (x < 0 || x >= m || y < 0 || y >= n || '0' == grid[x][y]) {
                            continue;
                        }
                        Union(i * n + j, x * n + y);
                    }
                } else {
                    blackNumber ++;
                }
            }
        }

        return count - blackNumber;
    }
}
547. 省份数量
class Solution {

    private int[] F = null;
    private int count = 0;

    private void Init(int n) {
        F = new int[n];
        for (int i = 0; i < n; i ++) {
            F[i] = i;
        }
        count = n;
    }

    private int Find(int x) {
        if (x == F[x]) {
            return x;
        }
        F[x] = Find(F[x]);
        return F[x];
    }

    private void Union(int x, int y) {
        if (Find(x) != Find(y)) {
            count --;
        }
        F[Find(x)] = Find(y);
    }

    public int findCircleNum(int[][] isConnected) {

        final int n = isConnected.length;
        Init(n);

        for (int i = 0; i < n; i ++) {
            for (int j = i + 1; j < n; j ++) {
                if (1 == isConnected[i][j]) {
                    Union(i, j);
                }
            }
        }

        return count;
    }
}
839. 相似字符串组
class Solution {

    int count = 0;
    int[] F = null;

    private void Init(int n) {
        F = new int[n];
        for (int i = 0; i < n; i ++) {
            F[i] = i;
        }
        count = n;
    }

    private int Find(int x) {
        if (x == F[x]) {
            return x;
        }
        F[x] = Find(F[x]);
        return F[x];
    }

    private void Union(int x, int y) {
        if (Find(x) != Find(y)) {
            count --;
        }
        F[Find(x)] = Find(y);
    }

    private boolean check(String a, String b) {

        final int n = a.length();
        int[] aArr = new int[256];
        int[] bArr = new int[256];

        for (int i = 0; i < n; i ++) {
            aArr[a.charAt(i)] ++;
            bArr[b.charAt(i)] ++;
        }

        for (int i = 0; i < n; i ++) {
            if (aArr[i] != bArr[i]) {
                return false;
            }
        }

        int number = 0;
        for (int i = 0; i < n; i ++) {
            if (a.charAt(i) != b.charAt(i)) {
                number ++;
            }
            if (number > 2) {
                return false;
            }
        }

        return true;
    }

    public int numSimilarGroups(String[] strs) {
        final int n = strs.length;
        Init(n);
        for (int i = 0; i < n; i ++) {
            for (int j = i + 1; j < n; j ++) {
                if (check(strs[i], strs[j])) {
                    Union(i, j);
                }
            }
        }

        return count;
    }
}

虚拟点与虚拟边

上网的最小费用

【题目】园区里面有很多大楼,编号从 1~N。第 i 大楼可以自己花钱买路由器上网,费用为 cost[i-1],也可以从别的大楼拉一根网线来上网,比如大楼 a 和大楼 b 之间拉网线的费用为 c,表示为一条边 [a, b, c]。输入为每个大楼自己买路由器和拉网线的费用,请问,让所有大楼都能够上网的最小费用是多少?上网具有联通性,只要与能够上网的大楼连通,即可上网。

输入:cost = [1, 2, 3], edges = [[1,2,100], [2,3,3]]

输出:6

题目参考:https://kaiwu.lagou.com/course/courseInfo.htm?courseId=685#/detail/pc?id=6696

class Solution {

  private int[] F = null;

  private int totalCost = 0;

  // 注意,编号是从1 ~ n

  private void Init(int n) {

    F = new int[n + 1];

    for (int i = 0; i <= n; i++) {

      F[i] = i;

    }

    totalCost = 0;

  }

  private int Find(int x) {

    if (x == F[x]) {

      return x;

    }

    F[x] = Find(F[x]);

    return F[x];

  }

  private void Union(int x, int y, int pay) {

    if (Find(x) != Find(y)) {

      totalCost += pay;

    }

    F[Find(x)] = Find(y);

  }

  // N 表示结点数目

  // cost[i-1]表示结点i自己买路由器的代价

  // es[x] = [a, b, c]表示大楼a,b之间拉网线的费用

  // 输出所有大楼通网的最小费用

  public int minCostToSupplyWater(int N, int[] cost, int[][] es) {

    // 初始化并查集

    Init(N);

    // 每个结点都要自己买路由器,那么我们可以认为这样

    // 0号楼已经有网络了,可以用0费用上网

    // i号楼与0号楼拉网线,需要的费用是cost[i-1]

    // 那么这里就多了N条边

    int[][] conn = new int[es.length + N][3];

    for (int i = 0; i < es.length; i++) {

      conn[i][0] = es[i][0];

      conn[i][1] = es[i][1];

      conn[i][2] = es[i][2];

    }

    int to = es.length;

    for (int i = 1; i <= N; i++) {

      conn[to][0] = 0;

      conn[to][1] = i;

      conn[to][2] = cost[i - 1];

      to++;

    }

    // 接下来采用Krukal最小生成树算法

    Arrays.sort(conn, new Comparator<int[]>() {

      public int compare(int[] a, int[] b) {

        return a[2] - b[2];

      }

    });

    for (int i = 0; i < conn.length; i++) {

      Union(conn[i][0], conn[i][1], conn[i][2]);

    }

    return totalCost;

  }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值