Java C++题解leetcode886可能的二分法并查集染色法

目录
  • 题目要求
  • 思路一:反向点+并查集
    • 浅学并查集(Union Find)
    • Java
    • C++
  • 思路二:染色法
    • Java
    • C++
  • 总结

题目要求

思路一:反向点+并查集

  • 根据题意不喜欢就不在一个组可以想到使用并查集,本题是两个集合所以对每一个节点引入一个反向点,使两者分属于不同集合,借此记录前续节点维持的不喜欢关系;
  • 在将每个节点xxx放入组合时,同时将其反向节点x+nx+nx+n放入另一组合,然后向后遍历依次处理每个节点,同时判断相互不喜欢的两个点当前是否会被迫放入一个集合(连通),若是则无法满足题意。

下面浅学一些并查集的基本概念,然后再去实现思路——

浅学并查集(Union Find)

学习参考链接

  • 从介绍到不断优化的整个构造推导过程,图片示例与解释很清晰。

简介:

一种树型的数据结构,用于处理一些不相交集合的合并查询问题;

  • 核心思想:

    • 用一个数组表示整片森林,树的根节点唯一标识了一个集合,只要找到了某个元素的树根,就能确定它在哪个集合里;
  • 适用场景:
    • 用于需要反复查找某一元素属于哪个集合以进行集合合并的场景,用其他数据结构解决该类问题将造成巨大的时空开销。

基础操作:

通常包括三个函数

函数 功能
find(x) 查找元素xxx属于哪个集合,也就是找当前元素所在树的根节点,查找的同时进行路径压缩
union(a, b) 合并元素aaa和元素bbb所属集合,根据树高合并两棵树
isConnected(a, b) 判断aaa和元素bbb是否处于同一集合中,也就是判断二者根是否相同

Java

class Solution {
    int[] p = new int[4010]; // 并查集数组,存父级节点
    // 找当前节点的根
    int find(int x) {
        if(p[x] != x) // 非根节点
            p[x] = find(p[x]); // 继续向下找根并进行路径压缩
        return p[x];
    }
    // 连接两节点的根
    void union(int a, int b) {
        p[find(a)] = p[find(b)];
    }
    // 两节点是否连通
    boolean isConnected(int a, int b) {
        return find(a) == find(b);
    }
    public boolean possibleBipartition(int n, int[][] dislikes) {
        for (int i = 1; i <= 2 * n; i++) // 节点+反向节点
            p[i] = i; // 初始化并查集,指向自己
        for (int[] cur : dislikes) {
            int a = cur[0], b = cur[1];
            if (isConnected(a, b)) // 连通,被迫在一组
                return false;
            // 利用反向节点维护连通关系
            union(a, b + n);
            union(b, a + n);
        }
        return true;
    }
}
  • 时间复杂度:O(n+m),其中m为dislikes的长度
  • 空间复杂度:O(n)

C++

  • 注意union会和C++中的预定义函数重名
class Solution {
public:
    int p[4010]; // 并查集数组,存父级节点
    // 找当前节点的根
    int find(int x) {
        if(p[x] != x) // 非根节点
            p[x] = find(p[x]); // 继续向下找根并进行路径压缩
        return p[x];
    }
    // 连接两节点的根
    void unionn(int a, int b) {
        p[find(a)] = p[find(b)];
    }
    // 两节点是否连通
    bool isConnected(int a, int b) {
        return find(a) == find(b);
    }
    bool possibleBipartition(int n, vector<vector<int>>& dislikes) {
        for (int i = 1; i <= 2 * n; i++) // 节点+反向节点
            p[i] = i; // 初始化并查集,指向自己
        for (auto cur : dislikes) {
            int a = cur[0], b = cur[1];
            if (isConnected(a, b)) // 连通,被迫在一组
                return false;
            // 利用反向节点维护连通关系
            unionn(a, b + n);
            unionn(b, a + n);
        }
        return true;
    }
};
  • 时间复杂度:O(n+m)
  • 空间复杂度:O(n)

思路二:染色法

  • 将不喜欢数组存成一个无向图,给分属两个不同集合的点染上不同的颜色,不断更新染色并判断不喜欢关系是否能够成立;
  • 采用链式前向星存储构建无向图,有边的两者不能是同一个颜色,用1和2表示两种不同的颜色,用000表示未染色;
  • 定义一个DFS(node, clr)函数表示将节点node染成clr色

Java

class Solution {
    int N = 2010, M = 2 * 10010;
    int[] head = new int[N], edge = new int[M], next = new int[M];
    int[] color = new int[N];
    int idx = 0;;
    void add(int a, int b) {
        edge[idx] = b;
        next[idx] = head[a];
        head[a] = idx++;
    }
    boolean DFS(int node, int clr) {
        color[node] = clr;
        for (int i = head[node]; i != -1; i = next[i]) {
            int j = edge[i];
             // 不喜欢双方同色
            if (color[j] == clr)
                return false;
            if (color[j] == 0 && !DFS(j, 3 - clr))
                return false;
        }
        return true;
    }
    public boolean possibleBipartition(int n, int[][] dislikes) {
        Arrays.fill(head, -1);
        for (int[] cur : dislikes) { // 构建无向图
            int a = cur[0], b = cur[1];
            add(a, b);
            add(b, a);
        }
        for (int i = 1; i <= n; i++) {
            if (color[i] != 0) // 已经染过
                continue;
            if (!DFS(i, 1)) // 无法染色成功
                return false;
        }
        return true;
    }
}
  • 时间复杂度:O(n+m)
  • 空间复杂度:O(n+m)

C++

class Solution {
public:
    static const int N = 2010, M = 2 * 10010;
    int head[N], edge[M], next[M];
    int color[N];
    int idx = 0;;
    void add(int a, int b) {
        edge[idx] = b;
        next[idx] = head[a];
        head[a] = idx++;
    }
    bool DFS(int node, int clr) {
        color[node] = clr;
        for (int i = head[node]; i != -1; i = next[i]) {
            int j = edge[i];
             // 不喜欢双方同色
            if (color[j] == clr)
                return false;
            if (color[j] == 0 && !DFS(j, 3 - clr))
                return false;
        }
        return true;
    }
    bool possibleBipartition(int n, vector<vector<int>>& dislikes) {
        memset(head, -1, sizeof(head));
        for (auto cur : dislikes) { // 构建无向图
            int a = cur[0], b = cur[1];
            add(a, b);
            add(b, a);
        }
        for (int i = 1; i <= n; i++) {
            if (color[i] != 0) // 已经染过
                continue;
            if (!DFS(i, 1)) // 无法染色成功
                return false;
        }
        return true;
    }
};
  • 时间复杂度:O(n+m)
  • 空间复杂度:O(n+m)

总结

算法题就回避一下Rust……待我学成归来……

填了拖了好久的并查集的坑还捎带复习了一波链式前向星存图;

感觉链式前向星忘得差不多了……

以上就是Java C++题解leetcode886可能的二分法并查集染色法的详细内容,更多关于Java C++ 可能的二分法的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java C++题解leetcode915分割数组示例

    目录 题目要求 思路一:两次遍历 Java C++ Rust 思路二:一次遍历 Java C++ Rust 题目要求 题目链接 思路一:两次遍历 题目的意思也就是左半边数组的最大值小于等于右半边数组的最小值,那么就找这个分界点就好: 首先从后向前遍历,找[i,n−1]里最小的值: 然后从前向后遍历,找[0,i]里最大的值: 然后找满足max[i]<=min[i+1]的分割点i: 可以将2.3两步结合为一步完成,由于iii从前向后不断增大,所以用后面(较大)的值覆盖更新之前的值. 找到分界点的索引

  • Java C++ leetcode面试零矩阵

    目录 题目要求 思路:模拟 Java C++ Rust 总结 题目要求 思路:模拟 定义两个数组分别记录每行or每列中为0的元素: 0所在的行列清零也就意味着元素所在行or列有0则置零[废话连篇]: 所以一次遍历找出有0的行列,一次遍历根据其将相应元素置零. Java class Solution { public void setZeroes(int[][] matrix) { int n = matrix.length, m = matrix[0].length; boolean[] row

  • Java C++题解leetcode1441用栈操作构建数组示例

    目录 题目要求 思路:模拟[双指针] Java C++ Rust 题目要求 思路:模拟[双指针] 按题意模拟即可: 一个指针cur依次指向target中的每个元素,另一个指针i依次指向1∼n的数字: 对i所指向的每个数字进行Push操作,然后判断当前数字与target[cur]是否相等: 相等则判断下一个数字,同时将cur指向下一个元素: 否则需进行Pop操作. 过程中需注意cur的越界,当其越界则target构造完毕. Java class Solution { public List<Str

  • Java C++题解leetcode 1684统计一致字符串的数目示例

    目录 题目 思路:模拟 Java C++ Rust 题目 题目要求 思路:模拟 用一个哈希表记录可出现的字母,然后逐一遍历每个单词每个字母,符合条件则结果加一. Java class Solution { public int countConsistentStrings(String allowed, String[] words) { boolean[] hash = new boolean[26]; for (var a : allowed.toCharArray()) hash[a -

  • Java C++题解leetcode902最大为N的数字组合数位DP

    目录 题目要求 阅读理解 思路:数位DP Java C++ 总结 题目要求 题目链接 阅读理解 思路:数位DP Java class Solution { public int atMostNGivenDigitSet(String[] digits, int n) { // 转存digits int[] nums = new int[digits.length]; for (int i = 0; i < digits.length; i++) nums[i] = Integer.parseIn

  • Java C++题解leetcode886可能的二分法并查集染色法

    目录 题目要求 思路一:反向点+并查集 浅学并查集(Union Find) Java C++ 思路二:染色法 Java C++ 总结 题目要求 思路一:反向点+并查集 根据题意不喜欢就不在一个组可以想到使用并查集,本题是两个集合所以对每一个节点引入一个反向点,使两者分属于不同集合,借此记录前续节点维持的不喜欢关系: 在将每个节点xxx放入组合时,同时将其反向节点x+nx+nx+n放入另一组合,然后向后遍历依次处理每个节点,同时判断相互不喜欢的两个点当前是否会被迫放入一个集合(连通),若是则无法满

  • Java C++题解leetcode1598文件夹操作日志搜集器

    目录 题目要求 思路:模拟 Java C++ Rust 总结 题目要求 思路:模拟 根据日志判断目前在哪一级子文件夹即可,级数就等于返回时的步数,主文件夹级数初始为000: xl:级数+1+1+1: ./:级数不变: ../:级数−1-1−1. Java class Solution { public int minOperations(String[] logs) { int res = 0; for (String l : logs) { if (l.equals("../"))

  • Java C++题解leetcode字符串轮转KMP算法详解

    目录 题目要求 思路一:双指针(模拟) Java C++ 思路二:子串 手写KMP Java dp C++ dp 调API Java C++ 总结 题目要求 思路一:双指针(模拟) Java class Solution { public boolean isFlipedString(String s1, String s2) { if (s1.length() != s2.length()) return false; int n = s1.length(); if (n == 0) retu

  • Java C++题解 leetcode第k个数实例

    目录 题目要求 思路一:小根堆 Java C++ 思路二:多路归并[多指针] Java C++ Rust 总结 题目要求 思路一:小根堆 中文题目描述不太清晰,但其实由题目可以发现,当x满足条件时,3x.5x.7x分别也都满足条件. 将满足条件的数依次放入优先队列存放用于后续计算,由于每次要取待计算队列中最小的数x,所以定义小根堆: 弹出x,计算3x.5x.7x并入队: 用一个哈希表记录防止重复入队. 每次取数(pop)时进行计数,到第k次结束,当前队首即为答案. Java <学到了> 1L也

  • Java C++题解leetcode判定是否为字符重排

    目录 题目要求 思路一:排序 Java C++ Rust 思路二:词频统计 Java C++ Rust 总结 题目要求 思路一:排序 Java class Solution { public boolean CheckPermutation(String s1, String s2) { if(s1.length() != s2.length()) return false; char[] sort1 = s1.toCharArray(); Arrays.sort(sort1); char[]

  • Java C++题解leetcode消失的两个数字实例

    目录 题目要求 思路:数学推导 Java C++ Rust 总结 题目要求 思路:数学推导 不重复的数组序列可以根据高斯公式计算所有元素的总和: 用当前数组长度加上两个缺失的数字可以得到所有数字长度,即可应用公式. 减去当前数组和即可得到缺失数字和sumsumsum: 两个缺失的数字分别位于m=sum2m=\frac{sum}{2}m=2sum两边: 遍历当前数组中所有小于(或大于)mmm的值,找到缺失的一个: 同样利用两个“和”的差值得到: 利用sumsumsum即可得到另一个. Java c

  • Java C++题解leetcode672灯泡开关示例

    目录 题目要求 思路:找规律 Java C++ Rust 总结 题目要求 思路:找规律 找到尽可能最精简的通项表达,今日参考:京城打工人 首先,归纳每个开关会影响的灯,其中(k=0,1,2,…): 开关 反转灯编号 一 k 二 2k 三 2k+1 四 3k+1 可见灯以6盏为周期具有相同变化,所以以下只需要推导第一个周期里的6盏灯即可. 观察前6盏灯: 灯 开关 1 一.三.四 2 一.二 3 一.三 4 一.二.四 5 一.三 6 一.二 发现灯2.6和3.5分别受同样的开关影响,所以状态相同

  • Java C++ 题解leetcode1619删除某些元素后数组均值

    目录 题目要求 思路:模拟 Java C++ Rust 题目要求 思路:模拟 根据题意模拟即可: 排序然后只取中间符合条件的数加和然后计算均值: 根据给出的数组长度n为20的倍数,5%可直接取n/20: 两边各去除5%,则剩余长度为0.9n. Java class Solution { public double trimMean(int[] arr) { Arrays.sort(arr); int n = arr.length, tot = 0; for (int i = n / 20; i

  • Java C++题解leetcode817链表组件示例

    目录 题目要求 思路:模拟 Java C++ Rust 总结 题目要求 思路:模拟 Java class Solution { public int numComponents(ListNode head, int[] nums) { int res = 0; Set<Integer> set = new HashSet<>(); for (int x : nums) set.add(x); // 转存nums while (head != null) { if (set.cont

随机推荐