问题描述
例如:
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 如果地图中不存在字母表,则将其添加到地图中,队列为空,
map.put('A',new LinkedList<>())
1.2 如果地图中存在字母表,则将其从原始列表中移除,并将其添加到地图中对应的队列中,
list.remove(element)
和map.get('A').add(element)
-
再次遍历原始列表,当遇到字母表时,立即从地图中添加其对应的队列。
我认为这个解决方案可行,但我不确定它是否会因边缘情况而失败,或者它是否是最佳解决方案(即使其复杂度为 O(n))。
谁能提出更好的替代方案?
解决方法
在这种情况下可以使用Stream API:
- 在每个输入元素中按字母前缀或数字构建一个
LinkedHashMap
分组,并将具有相同前缀的元素收集到有序集合(如果可能重复,则为有序列表) - 获取第 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) 或接近的两阶段方法。
- 分析阶段:按照问题中的描述构建您的地图,但不要从数组中删除任何内容,因为这会导致元素移动并破坏 O(n)。
- 从旧列表构建新列表。对于旧列表中的每个元素:
- 如果元素以字母(字母字符)开头,则从地图中取出列表,将所有元素添加到新列表中并从地图中删除条目。如果在地图中未找到任何条目,则表示该条目已被删除并添加到新列表中,因此什么也不做。
- 否则只需将元素添加到新列表中。
- 如果需要,将新列表的内容写回旧列表。
对于地图中的列表,我的首选是 ArrayList
。如果重要,您可以自己进行绩效衡量。
这是一种与您所描述的有些相似的方法:
- 数组的初始迭代:
- 将所有字符串的索引存储在一个 Set
- 根据字符在 Map
> 中存储索引
- 将所有字符串的索引存储在一个 Set
- 构建结果数组的最终迭代:
- 如果当前索引包含在上一组字符串索引中,并且尚未遇到该字符,则插入Map
>中引用的相关字符串批次, - 否则它只是简单地插入到结果数组中。
- 如果当前索引包含在上一组字符串索引中,并且尚未遇到该字符,则插入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 的解决方案易于使用和理解,但与我发布的内容相比,它具有显着的性能成本。根据您的应用程序的需要使用哪个。