【问题标题】:Reordering similar elements within an array对数组中的相似元素重新排序
【发布时间】:2021-06-07 19:50:28
【问题描述】:

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

例如:

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, 2, 3, A1, A2, A3, A4, B1, B2, B3, B4, 4, 5, 6, C1, C2, 7, 8, 9, 10, 11, 12, D1]

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

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

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

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

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

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

谁能推荐一个更好的选择?

【问题讨论】:

  • 您可以记住每个“字母”的第一个在哪里,因此您不必重新扫描列表即可找到它们。
  • 这实现起来可能有点复杂,因为每当我在第一次迭代期间删除元素并在第二次迭代期间添加元素时,索引都会发生变化。
  • 元素是否总是一个整数或单个字符后跟一个整数?
  • @bliss 是的,没错
  • 为什么输出数组不是以 1..12 开头的?这不是字母吗?

标签: java arrays algorithm data-structures hashmap


【解决方案1】:

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

  1. 数组的初始迭代:
    1. 将所有字符串的索引存储在一个Set
    2. 根据字符在Map中存储索引>
  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 的解决方案易于使用和理解,但与我发布的相比,它具有显着的性能成本。根据您的应用程序的需要使用任何一种。

【讨论】:

    【解决方案2】:

    这种情况下可以使用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]
    

    【讨论】:

    • 这是一个非常棒的解决方案,但我认为我错过了在我的要求中添加一个信息,即最终输出应与输入具有相同的顺序,而输入可能不一定具有有序数字。例如,输入可能是 [2, 1, 4....]。尽管如此,我认为用 ArrayList 替换 TreeSet 应该可行。
    • LinkedHashMap 将保持数字/字母数字键的插入顺序不变。但是,对于重复的数值,初始顺序可能会被破坏,当前的实现需要稍微修改以使用索引作为数字元素的键。
    • 是的,我现在同意 LinkedHashmap 部分。但是我们如何使用索引作为数字元素的键(因为我们使用的是流)?
    • 使用IntStream.range(0, arr.length).boxed() 给出了Stream&lt;Integer&gt; 索引流,所以在groupingBy 收集器中我们将有一个索引可以用来检索arr[i]
    【解决方案3】:

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

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

    对于地图中的列表,我的第一选择是ArrayList。如果这很重要,您可以自己进行性能测量。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-08
      • 2020-11-01
      • 1970-01-01
      相关资源
      最近更新 更多