欧搏客户端:[leetcode] 并查集(Ⅱ)

admin 1个月前 (05-31) 科技 4 0

{最长延续序}列

‘《问题》’[128]:《链接》。

《解题思绪》

节点自己的值作为节点的标号,两节点相邻,即允许合并(x, y)条件x == y+1

由于数组中可能会泛起值为 -1 的节点,《因此不能把》 root[x] == -1 作为根节点的特征,以是接纳 root[x] == x 作为判断是否为根节点的条件。默认较小的节点作为连通分量的根。

此外,使用 map<int, int> Counter 纪录节点所在连通分量的节点个数(也是merge 的返回值)。

class SolutI.N
{
public:
    unordered_map<int, int> counter;
    unordered_map<int, int> root;
    int longestCoNSecutive(vector<int> &nums)
    {
        int len = nums.size();
        // uSE map to discard the duplicate values
        for (int x : nums)
            root[x] = x, counter[x] = 1;
        int result = len == 0 ? 0 : 1;
        for (int x : nums)
        {
            if (root.count(x + 1) == 1)
                result = max(result, merge(x, x + 1));
        }
        return result;
    }
    int find(int x)
    {
        return root[x] == x ? x : (root[x] = find(root[x]));
    }
    int merge(int x, int y)
    {
        x = find(x);
        y = find(y);
        if (x != y)
        {
            root[y] = x;
            counter[x] += counter[y];
        }
        return counter[x];
    }
};

连通网络的操作次数

‘《问题》’[1319]:Link.

《解题思绪》

思量使用并查集。

{思量到特殊}情况,要使 N 个点连通,至少需要 N-1 条边, 否则返回[ -1 即可。

通过并查集,可以计算出多余的边的数目(多余的边是指使得图成环的边),只要 findroot(x) == findroot(y) 说明边 (x,y) 使得图成环。

遍历所有边,<在并查集>中执行合并 merge 操作(多余的边忽略不合并,只举行计数)。设 components 为合并后后 root 数组中 -1 的个数(也就是连通分量的个数),要想所有的连通分支都连起来,需要 components - 1 个边,以是要求「多余的边」的数目必须大于即是 components - 1

一个简朴的例子如下:

0--1         0--1                0--1
| /    =>    |          =>       |  | 
2  3         2  3                2  3
             components = 2
             duplicateEdge = 1

{代码实现}

class Solution
{
public:
    vector<int> root;
    int result = 0;
    int makeConnected(int n, vector<vector<int>> &connections)
    {
        int E = connections.size();
        // corner cases
        if (n == 0 || n == 1)
            return 0;
        if (E < n - 1)
            return -1;
        root.resize(n), root.assIGn(n, -1);
        // merge
        for (auto &v : connections)
        {
            int a = v[0], b = v[1];
            merge(a, b);
        }
        int components = count(root.begin(), root.end(), -1);
        if (counter >= (components - 1))
            return components - 1;
        // should not be here
        return -1;
    }
    int find(int x)
    {
        return root[x] == -1 ? x : (root[x] = find(root[x]));
    }
    // the number of duplicate edges
    int counter = 0;
    void merge(int x, int y)
    {
        x = find(x), y = find(y);
        if (x != y)
            root[y] = x;
        else
        {
            // there is a duplicate edge
            counter++;
        }
    }
};

等式方程的可知足性

‘《问题》’[990]:Link.

《解题思绪》

思量并查集。遍历所有的包罗 == 的等式,显然,相等的 2 个变量就合并。对于不等式 x!=y ,必须知足 findroot(x) != findroot(y) 才不会泛起逻辑上的错误。也就是说,不相等的 2 个变量一定在差别的连通分支当中。

#define getidx(x) ((x) - 'a')
class Solution
{
public:
    vector<int> root;
    bool equationsPossible(vector<string> &equations)
    {
        root.resize('z' - 'a' + 1, -1);
        vector<int> notequal;
        int len = equations.size();
        for (int i = 0; i < len; i++)
        {
            auto &s = equations[i];
            if (s[1] == '!')
            {
                notequal.emplace_back(i);
                continue;
            }
            int a = getidx(s[0]), b = getidx(s[3]);
            merge(a, b);
        }
        for (int i : notequal)
        {
            auto &s = equations[i];
            int a = getidx(s[0]), b = getidx(s[3]);
            if (find(a) == find(b))
                return false;
        }
        return true;
    }
    int find(int x)
    {
        return (root[x] == -1) ? x : (root[x] = find(root[x]));
    }
    void merge(int x, int y)
    {
        x = find(x), y = find(y);
        if (x != y)
            root[y] = x;
    }
};

只管削减恶意软件的流传 II

‘《问题》’[928]:这题有点难。

《解题思绪》

参考 题解1 和 题解2 。

首先,对原来的并查集结构添“加一点改善”,行使 vector<int> size[N] 纪录某个连通分量中节点的数目,注重当且仅当 x 是该连通分量的根节点时,size[x] 才示意该连通分量的节点数目这是由于在 merge 中,只对根节点的 size 举行了处置。

vector<int> root;
vector<int> size;
int find(int x)
{
    return root[x] == -1 ? (x) : (root[x] = find(root[x]));
}
void merge(int x, int y)
{
    x = find(x), y = find(y);
    if (x != y)
        root[y] = x, size[x] += size[y];	// pay attention here
}
// get the size of the connected component where node x is in
int getComponentSize(int x)
{
    return size[find(x)];
}

然后,确立一个基本图,该图是原图 graph 去除所有〖熏染节点〗 initial 的效果,并把这个基本图转换为【上述改善后的】并查集。把这个基本图中的节点暂且称为 clean nodes 或者 non-infected nodes .

从直觉上来说,{我们应该在} initial 中找到谁人标号最小,〖熏染最多〗 non-infected nodes 的节点,然则这样是否相符预期?

显然是不相符的,来看个例子,设 initial nodes = [a,b,c] ,《并》设 2 个没有被熏染的连通分量为 N1, N2 ,且这 2 个连通分量的点数知足 size(N1) > size(N2),原图 graph 结构如下:

a--N1--c

b--N2

凭据‘《问题》’的意思,需要找到的是使得最终熏染数目 M(initial) 最小的节点。

若是我们根据上述所谓的「直觉」:“在 initial 中找到谁人〖熏染最多〗 non-infected nodes 的节点”,应该去除的是节点 a ,然则由于 c 的存在,N1 依旧会被熏染,这样 M(initial) = size(N1) + size(N2)。(也就是说,【某】个连通分量相邻的〖熏染节点〗多于 1 个,该连通分量最终是一定被熏染的,<由于我们只>能去除一个〖熏染节点〗。)

实际上,这种情况下准确谜底是去除 b ,由于除 b 后:M(initial) = size(N1) ,该效果才是最小的。

以是,我们要找的是:在 initial 中找到谁人〖熏染最多〗 non-infected nodes 的节点 ans,但这些 non-infected nodes 节点只能被 ans 熏染,不能被其他的 initial 节点熏染(即只能被熏染一次)。

{代码实现}

class Solution
{
public:
    vector<int> root;
    vector<int> size;
    int MinMalwareSpreAD(vector<vector<int>> &graph, vector<int> &initial)
    {
        int N = graph.size();
        root.resize(N, -1);
        size.resize(N, 1);

        // use hash table to mark infected nodes
        vector<bool> init(N, false);
        for (int x : initial)
            init[x] = true;
        // change the non-infected graph into diSJoint union set
        for (int i = 0; i < N; i++)
        {
            if (init[i])
                continue;
            for (int j = 0; j < i; j++)
            {
                if (init[j])
                    continue;
                if (graph[i][j] == 1)
                    merge(i, j);
            }
        }
        // table[x] = {...}
        // the set {...} means the non-infected components which initial node x will infect
        // counter[x] = k
        // k means that the non-infected component x will be infected by initial nodes for k times
        vector<int> counter(N, 0);
        unordered_map<int, unordered_set<int>> table;
        for (int u : initial)
        {
            unordered_set<int> infected;
            for (int v = 0; v < graph[u].size(); v++)
            {
                if (!init[v] && graph[u][v] == 1)
                    infected.insert(find(v));
            }
            table[u] = infected;
            for (int x : infected)
                counter[x]++;
        }

        // find the node we want
        int ans = N + 1, maxInfected = -1;
        for (int u : initial)
        {
            int sum = 0;
            for (int x : table[u])
                if (counter[x] == 1)	// must be infected only once
                    sum += getComponentSize(x);
            if (sum > maxInfected || (sum == maxInfected && u < ans))
            {
                ans = u;
                maxInfected = sum;
            }
        }
        return ans;
    }

    int find(int x)
    {
        return root[x] == -1 ? (x) : (root[x] = find(root[x]));
    }

    void merge(int x, int y)
    {
        x = find(x), y = find(y);
        if (x != y)
            root[y] = x, size[x] += size[y];
    }

    int getComponentSize(int x)
    {
        return size[find(x)];
    }
};

只管削减恶意软件的流传

‘《问题》’[924]:做了上面那题之后简朴一点。

《解题思绪》

依然是使用上题中 只管削减恶意软件的流传 II 改善后的并查集结构。

对整个原图处置,转换为并查集。然后,模拟处置。即 \(\forall x \in initial\) ,使用聚集 \(Newset = initial - \{x\}\) 去模拟熏染原图,获得最终的〖熏染节点〗数 t,选取〖熏染节点〗数 t 最小且标号值最小的 \(x\) 作为返回效果。

{代码实现}

class Solution
{
public:
    vector<int> root, size;
    int minMalwareSpread(vector<vector<int>> &graph, vector<int> &initial)
    {
        int N = graph.size();
        root.resize(N, -1);
        size.resize(N, 1);

        for (int i = 0; i < N; i++)
            for (int j = 0; j < i; j++)
                if (graph[i][j] == 1)
                    merge(i, j);

        int ans = N + 1, minval = N + 1;
        // assume that discard the node x in the initial set
        // get the injected value
        for (int x : initial)
        {
            int t = getInjected(x, initial);
            if (t < minval || (t == minval && ans > x))
            {
                minval = t;
                ans = x;
            }
        }
        return ans;
    }
    // use set initial - {x} to inject the graph
    int getInjected(int x, vector<int> &initial)
    {
        unordered_set<int> s;
        for (int k : initial)
        {
            if (k == x)
                continue;
            s.insert(find(k));
        }
        int sum = 0;
        for (int t : s)
            sum += size[find(t)];
        return sum;
    }
    int find(int x)
    {
        return root[x] == -1 ? (x) : (root[x] = find(root[x]));
    }
    void merge(int x, int y)
    {
        x = find(x), y = find(y);
        if (x != y)
            root[y] = x, size[x] += size[y];
    }
};

被围绕的区域

‘《问题》’[130]:本题难度一样平常。

《解题思绪》

本题最特殊的节点是界限上的 O 以及内部与界限 O 相邻的节点。

首先,通过界限的 O 入手,从它最先举行 DFS 搜索,把所有这些的特殊节点标记为 Y 。然后,在 BoArd 中剩下的 O 就是通俗的节点(一定是不与界限 O 相邻且被 X 所围绕的),可以把它们所有换成 X 。最后,把所有的 Y 还原为 O

对于搜索方式,既可以是 DFS 也可以是 BFS

{代码实现}

class Solution
{
public:
    const vector<vector<int>> direction = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
    int row, col;
    void solve(vector<vector<char>> &board)
    {
        row = board.size();
        if (row == 0)
            return;
        col = board[0].size();
        #define Func bfs
        for (int j = 0; j < col; j++)
        {
            if (board[0][j] == 'O')
                func(0, j, board);
            if (board[row - 1][j] == 'O')
                func(row - 1, j, board);
        }

        for (int i = 0; i < row; i++)
        {
            if (board[i][0] == 'O')
                func(i, 0, board);
            if (board[i][col - 1] == 'O')
                func(i, col - 1, board);
        }

        for (int i = 0; i < row; i++)
        {
            for (int j = 0; j < col; j++)
            {
                if (board[i][j] == 'O')
                    board[i][j] = 'X';
                if (board[i][j] == 'Y')
                    board[i][j] = 'O';
            }
        }
    }

    void dfs(int i, int j, vector<vector<char>> &board)
    {
        board[i][j] = 'Y';
        for (auto &v : direction)
        {
            int a = i + v[0], b = j + v[1];
            if (a < 0 || b < 0 || a >= row || b >= col)
                continue;
            if (board[a][b] == 'O')
                dfs(a, b, board);
        }
    }

    void bfs(int i, int j, vector<vector<char>> &board)
    {
        typedef pAIr<int, int> node;
        queue<node> q;
        q.push(node(i, j));
        board[i][j] = 'Y';
        while (!q.empty())
        {
            node n = q.front();
            q.pop();
            for (auto &v : direction)
            {
                int a = n.first + v[0], b = n.second + v[1];
                if (!(a < 0 || b < 0 || a >= row || b >= col) && board[a][b] == 'O')
                    board[a][b] = 'Y', q.push(node(a, b));
            }
        }
    }
};
,

欧博网址开户

www.cX11yl.cn“欢迎进入欧”博网址(Allbet Gaming),【欧博网】址开放会员注册、代理开户、电脑客户端下载、「苹果」安卓下载等业务。

Sunbet声明:该文看法仅代表作者自己,与本平台无关。转载请注明:欧搏客户端:[leetcode] 并查集(Ⅱ)

网友评论

  • (*)

最新评论

站点信息

  • 文章总数:950
  • 页面总数:0
  • 分类总数:8
  • 标签总数:1967
  • 评论总数:52
  • 浏览总数:5308