题目

给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:a==ba!=b。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。

只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回 true,否则返回 false

代码

    private class UnionFind {
        private int[] parent;

        public UnionFind(int n) {
            parent = new int[n];
            for (int i = 0; i < n; i++) {
                parent[i] = i;
            }
        }

        // 在这里用的是隔代路径压缩,较彻底路径压缩(递归实现)而言,虽压缩不完全但性能较高
        public int find(int x) {
            while (x != parent[x]) {
                parent[x] = parent[parent[x]];
                x = parent[x];
            }
            return x;
        }

        public void union(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);
            parent[rootX] = rootY;
        }

        public boolean isConnected(int x, int y) {
            return find(x) == find(y);
        }
    }

    public boolean equationsPossible(String[] equations) {
        UnionFind unionFind = new UnionFind(26);
        for (String equation : equations) {
            char[] charArr = equation.toCharArray();
            if (charArr[1] == '=') {
                int index1 = charArr[0] - 'a';
                int index2 = charArr[3] - 'a';
                unionFind.union(index1, index2);
            }
        }
        for (String equation : equations) {
            char[] charArr = equation.toCharArray();
            if (charArr[1] == '!') {
                int index1 = charArr[0] - 'a';
                int index2 = charArr[3] - 'a';
                if (unionFind.isConnected(index1, index2)) {
                    return false;
                }
            }
        }
        return true;
    }

题外话

今天做到这个题学到了一个新知识点——并查集,就做个记录总结下。

CSDN博主的一篇文章将并查集讲述的生动形象,可以前往阅览。

相关概念

  • 并查集 是一种树型的数据结构,用来处理一些不交集的 合并查询 问题。
  • 并查集森林 是一种将每一个集合以树表示的数据结构,其中每一个节点保存着到它父节点的引用。

代码实现

其中主要就是 findunion 两个方法。

find 方法用来寻找两个节点的根节点,如果两个节点的根节点一致,两个节点就是连通的。在寻找时可以进行路径压缩来优化查找效率。举个例子,假设原来的树内节点构成了一条线,则查找效率为 O(n),而如果在每次查找后将当前节点连接到父节点的父结点上(隔代路径压缩),或者直接将查找节点连接到根节点(彻底路径压缩)。那么经过多次的查找,当前树的深度就会变得很低,可以方便的找到当前节点所在树的根节点。

  • 隔代路径压缩

      public int find(int x) {
          while (x != parent[x]) {
              parent[x] = parent[parent[x]];
              x = parent[x];
          }
          return x;
      }
  • 彻底路径压缩

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

union 方法是用来连结两个原来不相关的树,实现也较为简单,找到两个节点所在树的根节点,将其中一个根节点指向另外一个根节点即可。

并查集的具体代码如下:

    private class UnionFind {
        private int[] parent;

        public UnionFind(int n) {
            parent = new int[n];
            for (int i = 0; i < n; i++) {
                parent[i] = i;
            }
        }

        // 在这里用的是隔代路径压缩,较彻底路径压缩(递归实现)而言,虽压缩不完全但性能较高
        public int find(int x) {
            while (x != parent[x]) {
                parent[x] = parent[parent[x]];
                x = parent[x];
            }
            return x;
        }

        public void union(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);
            parent[rootX] = rootY;
        }

        public boolean isConnected(int x, int y) {
            return find(x) == find(y);
        }
    }