重新排序数组中的相似元素

问题描述

我有一个需求,我需要将原始列表中的相似元素组合在一起。

例如:

I/P 数组:

[1,2,3,A1,B1,4,B2,5,6,C1,B3,B4,7,8,9,10,A2,A3,11,12,A4,C2,D1]

现在我想对以字母开头的元素进行分组,这样属于特定字母的所有元素都放在一起,并且将被放置在该字母第一次出现之后。

O/P 数组:

[1,D1]

我想出的一个简单的解决方案是维护一个表示字母及其元素 Map<Character,Queue<Element>> 的 HashMap 并执行以下步骤:

  1. 遍历列表,如果遇到字母,请执行以下操作之一:

    1.1 如果地图中不存在字母表,则将其添加到地图中,队列为空,map.put('A',new LinkedList<>())

    1.2 如果地图中存在字母表,则将其从原始列表中移除,并将其添加到地图中对应的队列中,list.remove(element)map.get('A').add(element)

  2. 再次遍历原始列表,当遇到字母表时,立即从地图中添加其对应的队列。

我认为这个解决方案可行,但我不确定它是否会因边缘情况而失败,或者它是否是最佳解决方案(即使其复杂度为 O(n))。

谁能提出更好的替代方案?

解决方法

在这种情况下可以使用Stream API:

  1. 在每个输入元素中按字母前缀或数字构建一个LinkedHashMap分组,并将具有相同前缀的元素收集到有序集合(如果可能重复,则为有序列表)
  2. 获取第 1 步中间映射的值,并使用 flatMap 将集合/列表连接到单个列表/数组中
String[] arr = {
    "1","2","3","A1","B1","4","B2","5","6","C1","B3","B4","7","8","9","10","A2","A3","11","12","A4","C2","D1"
};

List<String> values = Arrays.stream(arr)
    .collect(Collectors.groupingBy(
        s -> s.matches("[A-Z]\\d+") ? s.charAt(0) : s,LinkedHashMap::new,Collectors.mapping(s -> s,Collectors.toCollection(TreeSet::new))
    )).values().stream()
    .flatMap(TreeSet::stream)
    .collect(Collectors.toList());
System.out.println(values);

输出

[1,2,3,A1,A2,A3,A4,B1,B2,B3,B4,4,5,6,C1,C2,7,8,9,10,11,12,D1]
,

我认为是 O(n) 或接近的两阶段方法。

  1. 分析阶段:按照问题中的描述构建您的地图,但不要从数组中删除任何内容,因为这会导致元素移动并破坏 O(n)。
  2. 从旧列表构建列表。对于旧列表中的每个元素:
    1. 如果元素以字母(字母字符)开头,则从地图中取出列表,将所有元素添加到新列表中并从地图中删除条目。如果在地图中未找到任何条目,则表示该条目已被删除并添加到新列表中,因此什么也不做。
    2. 否则只需将元素添加到新列表中。
  3. 如果需要,将新列表的内容写回旧列表。

对于地图中的列表,我的首选是 ArrayList。如果重要,您可以自己进行绩效衡量。

,

这是一种与您所描述的有些相似的方法:

  1. 数组的初始迭代:
    1. 将所有字符串的索引存储在一个 Set
    2. 根据字符在 Map>
    3. 中存储索引
  2. 构建结果数组的最终迭代:
    • 如果当前索引包含在上一组字符串索引中,并且尚未遇到该字符,则插入Map>中引用的相关字符串批次,
    • 否则它只是简单地插入到结果数组中。
public static String[] groupElements(String[] elements) {
    String[] groupedElements = new String[elements.length];
    Set<Integer> characterIndexes = new HashSet<>();
    Map<Character,List<Integer>> characterIndexesMap = new HashMap<>();
    for (int i = 0; i < elements.length; i++) {
        char firstCharacter = elements[i].charAt(0);
        if (Character.isLetter(firstCharacter)) {
            characterIndexes.add(i);
            if (!characterIndexesMap.containsKey(firstCharacter)) {
                List<Integer> newCharacterIndexes = new ArrayList<>();
                newCharacterIndexes.add(i);
                characterIndexesMap.put(firstCharacter,newCharacterIndexes);
            }
            else {
                characterIndexesMap.get(firstCharacter).add(i);
            }
        }
    }
    for (int i = 0,j = 0; i < elements.length && j < elements.length; i++) {
        if (!characterIndexes.contains(i)) {
            groupedElements[j++] = elements[i];
        }
        else {
            char firstCharacter = elements[i].charAt(0);
            if (!characterIndexesMap.containsKey(firstCharacter)) continue;
            List<Integer> indexes = characterIndexesMap.get(firstCharacter);
            for (int k = 0; k < indexes.size(); k++) {
                groupedElements[j + k] = elements[indexes.get(k)];
            }
            j += indexes.size();
            characterIndexesMap.remove(firstCharacter);
        }
    }
    return groupedElements;
}

编辑:上述使用 Streams API 的解决方案易于使用和理解,但与我发布的内容相比,它具有显着的性能成本。根据您的应用程序的需要使用哪个。