1722. Minimize Hamming Distance After Swap Operations - cocoder39/coco39_LC GitHub Wiki
1722. Minimize Hamming Distance After Swap Operations
2 challenges:
- Given index a, index b are swappable, index b and index c are swappable, then we can swap index a and index c without side effect. It means you may use b to help move value at index a to index c, and meanwhile move value at index c to index a, however value at index b can stay at index b after all the movements. This analysis drives us to consider building connection group for indexes that are inter-swappable, and UF is a good data structure for this.
 
Proof: Given index 0 and 1 are swappable, and index 1 and 2 are swappable
- a, b, c
 - b, a, c (by swapping index 0 and 1)
 - b, c, a (by swapping index 1 and 2)
 - c, b, a (by swapping index 0 and 1)
 
Given index 0 and 1 are swappable, index 1 and 2 are swappable, index 2 and 3 are swappable, prove that index 0 and 3 are swappable
- a, b, c, d
 - b, a, c, d (by swapping index 0, 1)
 - b, d, c, a (based on first example)
 - d, b, c, a (by swapping index 0, 1)
 
No matter how long the swappable chain is, we can convert the problem to the base case where there are only 3 elements.
- Once connection groups are built, I was stuck at how to calculate the hamming distance.
building 
counters = collections.defaultdict(collections.Counter)so that counters[root] is the frequencies of each number under root. 
counters[root][s] += 1
counters[root][t] -= 1
is equal to
        for i, s in enumerate(source):
            root = find(i)
            counters[root][s] += 1
        
        res = 0
        for i, t in enumerate(target):
            root = find(i)
            if counters[root][t] > 0:
                counters[root][t] -= 1
            else:
                res += 1
                
        return res
class Solution:
    def minimumHammingDistance(self, source: List[int], target: List[int], allowedSwaps: List[List[int]]) -> int:
        
        def find(i):
            while parent[i] != i:
                parent[i] = parent[parent[i]]
                i = parent[i]
            return i
        
        def union(i, j):
            root_i, root_j = find(i), find(j)
            if root_i != root_j:
                if wight[root_i] > wight[root_j]:
                    parent[root_j] = root_i
                    wight[root_i] += wight[root_j]
                else:
                    parent[root_i] = root_j
                    wight[root_j] += wight[root_i]
        
        n = len(source)
        parent = [i for i in range(n)]
        wight = [1 for i in range(n)]
        for i, j in allowedSwaps:
            union(i, j)
        
        counters = collections.defaultdict(collections.Counter)
        for i, (s, t) in enumerate(zip(source, target)):
            root = find(i)
            counters[root][s] += 1
            counters[root][t] -= 1
        
        res = 0
        for root, counter in counters.items():
            for num in counter:
                if counter[num] > 0:
                    res += counter[num]
        return res