题目如岛屿问题解法一文章所介绍,这里不过多赘述,直接讲解第二种解法。
并查集解法
并查集解法的整体思路是,将二维数组中为‘1’的部分提取出来作为样本,再进行判断,如果左上方向有同样为‘1’的,则进行合并,最后看一共有几座岛屿。
不过并查集解法有一个问题,给定的二维数组中是char类型,Java中基本数据都行都会作为值进行传递,那么该怎么分辨二维数组中所有的’1’是不相同的呢?
代码
比较粗糙的做法是用一个类替代这个‘1’,声明一个和参数board等长的Dot类的二维数组,从头至尾遍历一次,将board中为1的位置在Dot数组中对应位置创建Dot对象。
将Dot作为并查集中的样本。利用对象的内存地址不同来区别出不同的1,遍历,进行union后,返回集合个数。
代码主流程中,在进行union时一共进行了三次的遍历。
第一次遍历:只遍历第0列,因为第0列左侧没有值,所以不用考虑左侧。
第二次遍历:只遍历第0行,因为第0行上面没有值,所以不用考虑上面。
第三次遍历:从第1行第1列遍历。只考虑左上方向有没有‘1’,有‘1’则进行合并即可,不用再考虑边界问题。
如果用1个for循环搞定这些,可以。不过常数时间更复杂。每次都要判断边界。
public static class Node<V> {
V val;
public Node(V value) {
this.val = value;
}
}
public static class Dot {
}
public static class UnionFind1<V> {
HashMap<V, Node<V>> nodes;
HashMap<Node<V>, Node<V>> parentMap;
HashMap<Node<V>, Integer> sizeMap;
public UnionFind1(List<V> lists) {
nodes = new HashMap<>();
parentMap = new HashMap<>();
sizeMap = new HashMap<>();
for (V val : lists) {
Node node = new Node(val);
nodes.put(val, node);
parentMap.put(node, node);
sizeMap.put(node, 1);
}
}
public Node<V> findParent(Node<V> node) {
Stack<Node> stack = new Stack<>();
while (node != parentMap.get(node)) {
node = parentMap.get(node);
stack.push(node);
}
while (!stack.isEmpty()) {
parentMap.put(stack.pop(), node);
}
return node;
}
public void union(V a, V b) {
Node aNode = findParent(nodes.get(a));
Node bNode = findParent(nodes.get(b));
if (aNode != bNode) {
int aSize = sizeMap.get(aNode);
int bSize = sizeMap.get(bNode);
Node big = aSize >= bSize ? aNode : bNode;
Node small = big == aNode ? bNode : aNode;
parentMap.put(small, big);
sizeMap.put(big, aSize + bSize);
sizeMap.remove(small);
}
}
public int getSize() {
return sizeMap.size();
}
}
public static int numIslands2(char[][] board) {
int row = board.length;
int col = board[0].length;
Dot[][] dots = new Dot[row][col];
List<Dot> dotList = new ArrayList<>();
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (board[i][j] == '1') {
dots[i][j] = new Dot();
dotList.add(dots[i][j]);
}
}
}
UnionFind1<Dot> unionFind1 = new UnionFind1<>(dotList);
for (int i = 1; i < col; i++) {
if (board[0][i - 1] == '1' && board[0][i] == '1') {
unionFind1.union(dots[0][i - 1], dots[0][i]);
}
}
for (int i = 1; i < row; i++) {
if (board[i - 1][0] == '1' && board[i][0] == '1') {
unionFind1.union(dots[i - 1][0], dots[i][0]);
}
}
for (int i = 1; i < row; i++) {
for (int j = 1; j < col; j++) {
if (board[i][j] == '1') {
if (board[i][j - 1] == '1') {
unionFind1.union(dots[i][j], dots[i][j - 1]);
}
if (board[i - 1][j] == '1') {
unionFind1.union(dots[i][j], dots[i - 1][j]);
}
}
}
}
return unionFind1.getSize();
}
优化
上面的Dot类很粗糙,为了区别 '1’声明了一个类,很臃肿,所以还可以将上面的方法进行优化。
因为传进来的是一个二维数组,优化的过程是将二维数组拉伸,变成一个一维数组。
根据二维数组的列和行,算出新数组整体的长度,并放到新数组中。
比如说下图:3行4列的数组
新数组长度 row * col = 12,放入新数组的规则是: 当前行数 * 总列数 + 当前列数,第0行放入新数组中就是数组下标 0 ~ 3,第1行0列的1,放入新数组中就是 1 * 4 + 0 = 4,以此类推。
依然是将二维数组中‘1’部分当做是样本放到并查集中。相同就合并(在新数组中),最后再查看数组中集合个数即可。
代码
public static int numIslands3(char[][] board) {
int row = board.length;
int col = board[0].length;
UnionFind2 unionFind2 = new UnionFind2(board);
for (int i = 1; i < row; i++) {
if (board[i - 1][0] == '1' && board[i][0] == '1') {
unionFind2.union(i - 1, 0, i, 0);
}
}
for (int j = 1; j < col; j++) {
if (board[0][j - 1] == '1' && board[0][j] == '1') {
unionFind2.union(0, j - 1, 0, j);
}
}
for (int i = 1; i < row; i++) {
for (int j = 1; j < col; j++) {
if (board[i][j] == '1') {
if (board[i][j - 1] == '1') {
unionFind2.union(i, j, i, j - 1);
}
if (board[i - 1][j] == '1') {
unionFind2.union(i, j, i - 1, j);
}
}
}
}
return unionFind2.getSets();
}
public static class UnionFind2 {
int[] parent;
int[] size;
int[] help;
int col;
int sets;
public UnionFind2(char[][] board) {
int row = board.length;
this.col = board[0].length;
int len = row * col;
parent = new int[len];
help = new int[len];
size = new int[len];
sets = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (board[i][j] == '1') {
int index = index(i, j);
parent[index] = index;
size[index] = 1;
sets++;
}
}
}
}
public int index(int row, int currCol) {
return row * col + currCol;
}
// index -> 计算后在parent中的下标;
public int findParent(int index) {
int num = 0;
while (index != parent[index]) {
index = parent[index];
help[num++] = index;
}
for (int i = 0; i < num; i++) {
parent[help[i]] = index;
}
return index;
}
public void union(int r1, int c1, int r2, int c2) {
int i1 = index(r1, c1);
int i2 = index(r2, c2);
int f1 = findParent(i1);
int f2 = findParent(i2);
if (f1 != f2) {
if (size[f1] >= size[f2]) {
size[f1] += size[f2];
parent[f2] = f1;
} else {
size[f2] += size[f1];
parent[f1] = f2;
}
sets--;
}
}
public int getSets() {
return sets;
}
}