leetcode
leetcode 1101 ~ 1150
交换字符串中的元素

交换字符串中的元素

难度:

标签:

题目描述

代码结果

运行时间: 145 ms, 内存: 39.3 MB


/*
 * The idea is the same as the previous Java solution, using Union-Find to group connected indices.
 * This version leverages Java Streams to perform some of the operations in a more functional style.
 */
import java.util.*;
import java.util.stream.Collectors;

public class SmallestStringWithSwapsStream {
    public String smallestStringWithSwaps(String s, List<List<Integer>> pairs) {
        int n = s.length();
        UnionFind uf = new UnionFind(n);

        // Union step for all pairs
        pairs.forEach(pair -> uf.union(pair.get(0), pair.get(1)));

        // Group all characters by root parent
        Map<Integer, List<Character>> charGroups = new HashMap<>();
        for (int i = 0; i < n; i++) {
            int root = uf.find(i);
            charGroups.computeIfAbsent(root, k -> new ArrayList<>()).add(s.charAt(i));
        }

        // Sort the characters in each group
        charGroups.values().forEach(Collections::sort);

        // Reconstruct the smallest string by replacing characters in sorted order
        StringBuilder result = new StringBuilder(s);
        Map<Integer, Iterator<Character>> groupIterators = charGroups.entrySet().stream()
            .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().iterator()));
        for (int i = 0; i < n; i++) {
            int root = uf.find(i);
            result.setCharAt(i, groupIterators.get(root).next());
        }

        return result.toString();
    }

    // Union-Find class
    class UnionFind {
        int[] parent;
        int[] rank;

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

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

        public void union(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);

            if (rootX != rootY) {
                if (rank[rootX] > rank[rootY]) {
                    parent[rootY] = rootX;
                } else if (rank[rootX] < rank[rootY]) {
                    parent[rootX] = rootY;
                } else {
                    parent[rootY] = rootX;
                    rank[rootX] += 1;
                }
            }
        }
    }
}

解释

方法:

这个题解使用了并查集(Union-Find)数据结构来解决问题。首先,使用并查集将可以互相交换的字符分组。然后,将每个组内的字符按照字典序排序,以确保组内的字符排列是最小的。最后,重新构建字符串,确保每个位置的字符都是所在组内字典序最小的。

时间复杂度:

O(m + nlogn)

空间复杂度:

O(n)

代码细节讲解

🦆
在并查集中,如何处理具有多个连接组件的索引对,以确保所有相关索引都能正确合并?
在使用并查集处理多个连接组件的索引对时,我们通过`union`方法将每一对索引合并。对于每个索引对(例如`(i, j)`),我们首先找到各自的根节点(使用`find`方法),然后确定这两个根节点是否相同。如果不同,说明它们属于不同的组,此时将它们合并,通常是把一个根节点的父节点设置为另一个根节点,从而将两个组合并为一个组。这样,通过逐对处理所有给出的索引对,可以确保所有相关索引在并查集中正确地合并到相应的组中。
🦆
为什么在处理每个组内的字符时选择对它们进行字典序排序,这种排序方式是否总是能保证最终结果是全局字典序最小?
在处理每个组内的字符时选择对它们进行字典序排序的原因是确保在重构字符串时,每个位置能使用该组中字典序最小的字符。这种排序方式能够确保在每个可以互换的字符组内,字符都是按照字典序排列的最优方式。然而,虽然这可以保证局部最优(组内字符是最小的),但不一定是全局最优。全局最小的字典序需要考虑所有可能的字符置换,这通常需要更复杂的算法来确保。所以,这种方法在局部组内是最优的,但在全局可能不是最小的。
🦆
并查集的`union`方法中,如果`root_x`等于`root_y`时直接返回,这种情况是否意味着已经处理过这对索引,会不会对最终结果造成影响?
在并查集的`union`方法中,如果`root_x`等于`root_y`,这意味着两个索引已经在同一个连接组件中,即它们已经是通过某种方式直接或间接连接的。在这种情况下直接返回,是因为不需要进行任何操作就可以保持它们的连接状态。这种情况不会对最终的结果造成负面影响,因为它只是确认了这两个索引已经处于同一个组内,无需重复合并。
🦆
在最终重构字符串时,为什么选择使用`pop()`方法从列表中取出字符,这种方式是否可能导致不稳定的字符选择,从而影响字典序的结果?
在最终重构字符串时使用`pop()`方法从列表中取出字符是为了从已排序的字符列表中选择当前最小的字符(因为在字典序排序后使用`pop()`可以从列表尾部取出最小元素)。这种方式是稳定的,因为每个组内的字符已经按照字典序进行了排序,因此每次使用`pop()`都确保从该组中取出当前可用的最小字符。这有助于确保每个位置的字符都是该位置可能的所有字符中字典序最小的,从而使得最终结果字符串尽可能小。

相关问题