1579. Remove Max Number of Edges to Keep Graph Fully Traversable

Dare2Solve

Dare2Solve

1579. Remove Max Number of Edges to Keep Graph Fully Traversable
SAMSUNG 49-Inch Odyssey G9
SAMSUNG 49-Inch Odyssey G9
Because earth is not flat

Intuition

The problem involves determining the maximum number of edges that can be removed while maintaining connectivity between all nodes in an undirected graph for both Alice and Bob. Different edge types restrict traversal permissions to Alice, Bob, or both.

Approach

  1. Union-Find Data Structure:

    • Use a Union-Find (Disjoint Set Union, DSU) data structure to manage connected components for Alice and Bob separately.
    • Each component in the Union-Find represents a set of nodes that are connected.
  2. Edge Classification:

    • Classify edges into three types based on their traversal permissions (Type 1 for Alice, Type 2 for Bob, Type 3 for both).
  3. Union Operations:

    • Perform union operations in the Union-Find data structure based on edge types:
      • For Type 3 edges, union nodes for both Alice and Bob.
      • For Type 1 and Type 2 edges, union nodes only for Alice or Bob respectively.
  4. Count Operations:

    • Count the number of edges removed while performing union operations. This count helps in determining the maximum removable edges.
  5. Validation:

    • After processing all edges, check if both Alice and Bob have exactly one connected component each (i.e., groups == 1 in Union-Find for both).
  6. Result Calculation:

    • If both Alice and Bob have exactly one connected component each, calculate and return the maximum removable edges as total_edges - count.
    • If either Alice or Bob has more than one connected component (groups > 1), return -1 indicating the graph cannot be fully traversed by both after removing edges.

Complexity

Code Explanation

Union-Find Data Structure Initialization

class UnionFind {
    constructor(n) {
        this.parent = new Array(n).fill().map((_, i) => i);
        this.groups = n;
    }

Find Operation with Path Compression

    find(i) {
        if (this.parent[i] !== i) {
            this.parent[i] = this.find(this.parent[i]);
        }
        return this.parent[i];
    }

Union Operation for Merging Sets

    union(i, j) {
        const rootI = this.find(i);
        const rootJ = this.find(j);

        if (rootI !== rootJ) {
            this.parent[rootJ] = rootI;
            this.groups--;
            return true;
        }
        return false;
    }
}

Main Function: maxNumEdgesToRemove Initialization

/**
 * @param {number} n
 * @param {number[][]} edges
 * @returns {number}
 */
var maxNumEdgesToRemove = function (n, edges) {
    const alice = new UnionFind(n);
    const bob = new UnionFind(n);
    let count = 0;

Processing Type 3 Edges

for (let [type, u, v] of edges) {
  if (type === 3 && alice.union(u, v) && bob.union(u, v)) {
    count++;
  }
}

Processing Type 1 and Type 2 Edges

for (let [type, u, v] of edges) {
  if ((type === 1 && alice.union(u, v)) || (type === 2 && bob.union(u, v))) {
    count++;
  }
}

Result Evaluation

    if (alice.groups === 1 && bob.groups === 1) {
        return edges.length - count;
    } else {
        return -1;
    }
};

Code

C++

class UnionFind {
public:
    vector<int> parent;
    int groups;

    UnionFind(int n) : parent(n), groups(n - 1) {
        for (int i = 0; i < n; ++i) {
            parent[i] = i;
        }
    }

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

    bool unionSets(int i, int j) {
        int x = find(i);
        int y = find(j);
        if (x == y) {
            return false;
        } else {
            parent[y] = x;
            --groups;
            return true;
        }
    }
};

class Solution {
public:
    int maxNumEdgesToRemove(int n, vector<vector<int>>& edges) {
        UnionFind alice(n), bob(n);
        int count = 0;

        for (const auto& edge : edges) {
            int type = edge[0], u = edge[1] - 1, v = edge[2] - 1;
            if (type == 3 && alice.unionSets(u, v) && bob.unionSets(u, v)) {
                ++count;
            }
        }
        for (const auto& edge : edges) {
            int type = edge[0], u = edge[1] - 1, v = edge[2] - 1;
            if ((type == 1 && alice.unionSets(u, v)) || (type == 2 && bob.unionSets(u, v))) {
                ++count;
            }
        }

        if (alice.groups == 0 && bob.groups == 0) {
            return edges.size() - count;
        } else {
            return -1;
        }
    }
};

Python

class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.groups = n - 1
        
    def find(self, i):
        if self.parent[i] != i:
            self.parent[i] = self.find(self.parent[i])
        return self.parent[i]
    
    def union(self, i, j):
        x = self.find(i)
        y = self.find(j)
        if x == y:
            return False
        else:
            self.parent[y] = x
            self.groups -= 1
            return True

class Solution:
    def maxNumEdgesToRemove(self, n: int, edges: List[List[int]]) -> int:
        alice = UnionFind(n)
        bob = UnionFind(n)
        count = 0
        
        for edge in edges:
            type, u, v = edge[0], edge[1] - 1, edge[2] - 1
            if type == 3 and alice.union(u, v) and bob.union(u, v):
                count += 1
        
        for edge in edges:
            type, u, v = edge[0], edge[1] - 1, edge[2] - 1
            if (type == 1 and alice.union(u, v)) or (type == 2 and bob.union(u, v)):
                count += 1
        
        if alice.groups == 0 and bob.groups == 0:
            return len(edges) - count
        else:
            return -1

Java

class UnionFind {
    int[] parent;
    int groups;

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

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

    boolean union(int i, int j) {
        int x = find(i);
        int y = find(j);
        if (x == y) {
            return false;
        } else {
            parent[y] = x;
            groups--;
            return true;
        }
    }
}

class Solution {
    public int maxNumEdgesToRemove(int n, int[][] edges) {
        UnionFind alice = new UnionFind(n);
        UnionFind bob = new UnionFind(n);
        int count = 0;

        for (int[] edge : edges) {
            int type = edge[0], u = edge[1] - 1, v = edge[2] - 1;
            if (type == 3 && alice.union(u, v) && bob.union(u, v)) {
                ++count;
            }
        }
        for (int[] edge : edges) {
            int type = edge[0], u = edge[1] - 1, v = edge[2] - 1;
            if ((type == 1 && alice.union(u, v)) || (type == 2 && bob.union(u, v))) {
                ++count;
            }
        }

        if (alice.groups == 0 && bob.groups == 0) {
            return edges.length - count;
        } else {
            return -1;
        }
    }
}

JavaScript

/**
 * @param {number} n
 * @param {number[][]} edges
 * @returns {number}
 */
var maxNumEdgesToRemove = function (n, edges) {
    const alice = new UnionFind(n), bob = new UnionFind(n);
    let count = 0;

    for (let [type, u, v] of edges) {
        if (type === 3 && alice.union(u, v) && bob.union(u, v)) count++;
    }
    for (let [type, u, v] of edges) {
        if (
            (type === 1 && alice.union(u, v)) ||
            (type === 2 && bob.union(u, v))
        )
            count++;
    }

    if (!bob.groups && !alice.groups) return edges.length - count;
    else return -1;
};

/**
 * @class UnionFind
 * @param {number} n
 */
class UnionFind {
    constructor(n) {
        this.parent = new Array(n).fill().map((_, i) => i);
        this.groups = n - 1;
    }

    /**
     * @param {number} i
     * @returns {number}
     */
    find(i) {
        if (this.parent[i] !== i) this.parent[i] = this.find(this.parent[i]);
        return this.parent[i];
    }

    /**
     * @param {number} i
     * @param {number} j
     * @returns {boolean}
     */
    union(i, j) {
        const x = this.find(i),
            y = this.find(j);
        if (x === y) return false;
        else {
            this.parent[y] = x;
            this.groups--;
            return true;
        }
    }
}