概念TODO
UF的标准模板
算法实战
- 等式方程的可满足性(medium)
- 朋友圈(medium)
- 最长连续序列(hard)
下面将介绍以上题目的实现:
等式方程的可满足性(medium)
分析:
- List item
class Solution {
public boolean equationsPossible(String[] equations) {
int n = equations.length;
UF uf = new UF();
for (int i = 0; i < n; i++) {
if (equations[i].charAt(1) == '='){
uf.union(equations[i].charAt(0) - 'a', equations[i].charAt(3) - 'a');
}
}
for (int i = 0; i < n; i++) {
if (equations[i].charAt(1) == '=' && !uf.connected(equations[i].charAt(0) - 'a', equations[i].charAt(3) - 'a') ){
return false;
}
if (equations[i].charAt(1) == '!' && uf.connected(equations[i].charAt(0) - 'a', equations[i].charAt(3) - 'a') ){
return false;
}
}
return true;
}
class UF {
int[] id;
int[] size;
public UF() {
id = new int[26];
size = new int[26];
for (int i = 0; i < 26; i++){
id[i] = i;
size[i] = 1;
}
}
public int find(int p){
while (p != id[p]) {
id[p] = id[id[p]];
p = id[p];
}
return p;
}
public void union(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot) return;
if (size[pRoot] < size[qRoot]){
id[pRoot] = qRoot;
size[qRoot] += size[pRoot];
}else {
id[qRoot] = pRoot;
size[pRoot] += size[qRoot];
}
}
public boolean connected(int p, int q) {
return find(p) == find(q);
}
}
}
执行用时 :1 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗 :39.3 MB, 在所有 Java 提交中击败了16.67%的用户
- 547 朋友圈(medium)
班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j
个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。示例 1:
输入: [[1,1,0], [1,1,0], [0,0,1]] 输出: 2 说明:已知学生0和学生1互为朋友,他们在一个朋友圈。
第2个学生自己在一个朋友圈。所以返回2
class Solution {
//Union Find
public int findCircleNum(int[][] M) {
int n = M.length;
UF uf = new UF(n);
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) {
if (M[i][j] == 1)
uf.union(i, j);
}
return uf.count;
}
class UF {
int[] id;
int[] size;
public int count;
public UF(int n) {
id = new int[n];
size = new int[n];
count = n;
for (int i = 0; i < n; i++) {
id[i] = i;
size[i] = 1;
}
}
public int find(int p) {
while (p != id[p]) {
id[p] = id[id[p]];
p = id[p];
}
return p;
}
public void union(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot) return;
if (size[pRoot] < size[qRoot]) {
id[pRoot] = qRoot;
size[qRoot] += size[pRoot];
count--;
} else {
id[qRoot] = pRoot;
size[pRoot] += size[qRoot];
count--;
}
}
}
DFS解法:
public int findCircleNum(int[][] M) {
int m = M.length;
int count = 0;
int[] visited = new int[m]; //all 0
for (int i = 0; i < m; i++)
if (visited[i] == 0){
dfs(M, i, visited);
count++;
}
return count;
}
public void dfs(int[][] M, int i, int[] visited) {
for (int j = 0; j < M.length; j++){
if (visited[j] == 0 && M[i][j] == 1){
visited[j] = 1;
dfs(M, j, visited);
}
}
}
- 947. 移除最多的同行或同列石头(medium)
n 块石头放置在二维平面中的一些整数坐标点上。每个坐标点上最多只能有一块石头。
如果一块石头的 同行或者同列 上有其他石头存在,那么就可以移除这块石头。
给你一个长度为 n 的数组 stones ,其中 stones[i] = [xi, yi] 表示第 i 块石头的位置,返回 可以移除的石子 的最大数量。
方法:并查集
删到最后,留在图中的顶点一定位于不同的行和不同的列。因此,并查集里的元素是 描述「横坐标」和「纵坐标」的数值。因此我们需要遍历数组 stones,将每个 stone 的横坐标和纵坐标在并查集中进行合并。理解合并的语义十分重要。
「合并」的语义
「合并」的语义是:所有横坐标为 x 的石头和所有纵坐标为 y 的石头都属于同一个连通分量。
并查集里如何区分横纵坐标
然而会遇到这样一个问题:石头的位置是「有序数对(二维)」,并查集的底层是「一维数组」,我们在并查集里应该如何区分横纵坐标呢?
例如:如果一块石头的坐标为 [3, 3] ,那么所有横坐标为 3 的石头和所有纵坐标为 3 的石头都在一个连通分量中,但是我们需要在并查集里区分「横坐标」和「纵坐标」,它们在并查集里不能相等,根据题目的提示 0 <= x_i, y_i <= 10^40 <=xi ,yi <= 10^4
,可以把其中一个坐标 映射 到另一个与 [0, 10000] 不重合的区间,可以的做法是把横坐标全部减去 1000110001 或者全部加上 1000110001(视频讲解有误,以此处为准),或者按位取反([0, 10000] 里的 3232 位整数,最高位变成 11以后,一定不在 [0, 10000] 里)。
在并查集里我们需要维护连通分量的个数,新创建顶点的时候连通分量加 11;合并不在同一个连通分量中的两个并查集的时候,连通分量减 11。
class Solution {
public int removeStones(int[][] stones) {
int m = stones.length;
UF uf = new UF();
for (int i = 0; i < m; i++) {
int p = stones[i][0] + 10001; //为了区分横坐标与纵坐标数值
int q = stones[i][1];
uf.union(p, q);
}
return m - uf.getCount();
}
private class UF {
Map<Integer, Integer> parent;
int count;
public UF() {
this.parent = new HashMap();
this.count = 0;
}
public int getCount() {
return this.count;
}
public void union(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot)
return;
parent.put(pRoot, qRoot);
count--;
}
public int find(int x) {
if( !parent.containsKey(x)) {
parent.put(x, x);
count++;
}
//如果x的父节点不是自己,则找到其父节点
if (x != parent.get(x)) {
parent.put(x, find(parent.get(x)));
}
return parent.get(x);
}
}
}