{ }codelper
分组遍历字典自定义排序P0108

字母异位词分组

第一行给出单词数 n,第二行给出 n 个小写英文单词,用一个空格分隔。
只是字母排列顺序不同、用了相同字母且个数相同的单词称为字母异位词(eat、tea、ate)。
把单词按字母异位词分组。
同一个单词出现多次时,出现几次就全部放进该组。
每组输出一行 "个数: 单词 单词 …"。
组内单词按字典序书写(从第一个字母开始比较,字母表中靠前的在前;若一个是另一个的开头部分,短的在前)。
单词多的组先输出。
单词数相同时,按字典序比较两组的第一个单词(排好序的组中最前面的单词),靠前的组先输出。

输入
1 ≤ n ≤ 5000,每个单词为 1 到 10 个小写英文字母
输出
每组一行:单词数、冒号(:),然后每个单词前一个空格
样例 1 · stdin
6
eat tea tan ate nat bat
stdout
3: ate eat tea
2: nat tan
1: bat
样例 2 · stdin
7
listen silent enlist google banana elgoog tinsel
stdout
4: enlist listen silent tinsel
2: elgoog google
1: banana
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

字母异位词的字母排序后的字符串相同(eat、tea → aet),用它作字典的键分组即可。
陷阱在键的类型。
Python sorted(w) 是列表,作键会抛 TypeError,要用 "".join(sorted(w)) 变成字符串。
JS 若把数组 [...w].sort() 直接当 Map 的键,数组只有是同一个对象时才相等,结果每个单词各成一组(要加 .join(""))。
Java、C# 用 char[] 作键也是按数组对象而不是内容比较,要转成 new String(c)、new string(c)。
C++ 的 std::string 是值,排好序的副本直接就能当键。
Craykon 把字母放进列表 排序()(韩语 정렬()),再 连接("")(韩语 잇기(""))得到键。
用字符编码之和作键也是常见错误:ad 和 bc(97+100 = 98+99)会被分到一组。
C 没有字典,按(排序后的字母,单词)qsort,同一组就会连在一起,组内也已是字典序。
遍历分组时,Python dict、JS Map、Craykon 键列表(韩语 열쇠목록)按插入顺序,Java HashMap、C# Dictionary 不保证顺序,C++ std::map 按键的顺序。
这些都不是输出顺序,所以要按“大组在前,相同则看第一个单词”重新排序。
只按大小排序,大小相同的组会保持首次出现的顺序。

Pythonmain.py · 13 行
n = int(input())words = input().split() # 글자를 정렬한 문자열이 같으면 같은 묶음이다(eat, tea → "aet")groups = {}for w in words:    key = "".join(sorted(w))      # sorted(w)는 목록이라 사전 열쇠가 될 수 없다    groups.setdefault(key, []).append(w) result = [sorted(g) for g in groups.values()]result.sort(key=lambda g: (-len(g), g[0]))   # 큰 묶음 먼저, 같으면 첫 낱말 사전 순for g in result:    print(f"{len(g)}: {' '.join(g)}")
JavaScriptmain.js · 14 行
const [n, ...words] = require("fs").readFileSync(0, "utf8").trim().split(/\s+/); // 글자를 정렬한 문자열이 같으면 같은 묶음이다(eat, tea → "aet")const groups = new Map();for (const w of words) {  const key = [...w].sort().join(""); // 배열 그대로는 열쇠로 못 쓴다(배열은 같은 객체일 때만 같다)  if (!groups.has(key)) groups.set(key, []);  groups.get(key).push(w);} const result = [...groups.values()].map((g) => g.sort());const cmp = (a, b) => (a < b ? -1 : a > b ? 1 : 0);result.sort((a, b) => b.length - a.length || cmp(a[0], b[0])); // 큰 묶음 먼저, 같으면 첫 낱말console.log(result.map((g) => `${g.length}: ${g.join(" ")}`).join("\n"));
TypeScriptmain.ts · 16 行
import { readFileSync } from "node:fs";const [, ...words]: string[] = readFileSync(0, "utf8").trim().split(/\s+/); // 글자를 정렬한 문자열이 같으면 같은 묶음이다(eat, tea → "aet")const groups = new Map<string, string[]>();for (const w of words) {  const key = [...w].sort().join(""); // 배열 그대로는 열쇠로 못 쓴다(배열은 같은 객체일 때만 같다)  const g = groups.get(key);  if (g) g.push(w);  else groups.set(key, [w]);} const result: string[][] = [...groups.values()].map((g) => g.sort());const cmp = (a: string, b: string): number => (a < b ? -1 : a > b ? 1 : 0);result.sort((a, b) => b.length - a.length || cmp(a[0], b[0])); // 큰 묶음 먼저, 같으면 첫 낱말console.log(result.map((g) => `${g.length}: ${g.join(" ")}`).join("\n"));
JavaMain.java · 30 行
import java.io.*;import java.util.*; public class Main {    public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        br.readLine();        String[] words = br.readLine().trim().split(" ");         // 글자를 정렬한 문자열이 같으면 같은 묶음이다(eat, tea → "aet")        Map<String, List<String>> groups = new HashMap<>();        for (String w : words) {            char[] c = w.toCharArray();            Arrays.sort(c);            String key = new String(c); // char[] 그대로는 열쇠가 안 된다(배열은 내용이 아니라 객체로 비교)            groups.computeIfAbsent(key, k -> new ArrayList<>()).add(w);        }         List<List<String>> result = new ArrayList<>();        for (List<String> g : groups.values()) {            Collections.sort(g);            result.add(g);        }        result.sort(Comparator.<List<String>>comparingInt(g -> -g.size()).thenComparing(g -> g.get(0)));         StringBuilder out = new StringBuilder();        for (List<String> g : result) out.append(g.size()).append(": ").append(String.join(" ", g)).append('\n');        System.out.print(out);    }}
C#Main.cs · 34 行
using System;using System.Collections.Generic;using System.Linq; class Program{    static void Main()    {        Console.ReadLine();        string[] words = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);         // 글자를 정렬한 문자열이 같으면 같은 묶음이다(eat, tea → "aet")        var groups = new Dictionary<string, List<string>>();        foreach (string w in words)        {            char[] c = w.ToCharArray();            Array.Sort(c);            string key = new string(c); // char[] 그대로는 열쇠가 안 된다(배열은 객체로 비교)            if (!groups.TryGetValue(key, out var list))            {                list = new List<string>();                groups[key] = list;            }            list.Add(w);        }         // 문자열은 Ordinal로 비교한다(기본 비교는 문화권 규칙을 따른다)        var result = groups.Values            .Select(g => g.OrderBy(w => w, StringComparer.Ordinal).ToList())            .OrderByDescending(g => g.Count)            .ThenBy(g => g[0], StringComparer.Ordinal);        foreach (var g in result) Console.WriteLine($"{g.Count}: {string.Join(" ", g)}");    }}
C++main.cpp · 35 行
#include <algorithm>#include <iostream>#include <map>#include <string>#include <vector> int main() {    int n;    std::cin >> n;    // 글자를 정렬한 문자열이 같으면 같은 묶음이다(eat, tea → "aet")    std::map<std::string, std::vector<std::string>> groups;    for (int i = 0; i < n; i++) {        std::string w;        std::cin >> w;        std::string key = w;        std::sort(key.begin(), key.end()); // std::string은 값이라 정렬한 복사본이 곧 열쇠        groups[key].push_back(w);          // []가 없으면 빈 vector를 만들어 준다    }     std::vector<std::vector<std::string>> result;    for (auto& [key, g] : groups) {        std::sort(g.begin(), g.end());        result.push_back(g);    }    std::sort(result.begin(), result.end(), [](const auto& a, const auto& b) {        if (a.size() != b.size()) return a.size() > b.size(); // 큰 묶음 먼저        return a[0] < b[0];                                    // 같으면 첫 낱말 사전 순    });     for (const auto& g : result) {        std::cout << g.size() << ":";        for (const auto& w : g) std::cout << " " << w;        std::cout << "\n";    }}
Cmain.c · 55 行
#include <stdio.h>#include <stdlib.h>#include <string.h> /* C에는 사전이 없다: (정렬한 글자, 낱말) 차례로 정렬하면 같은 묶음이 붙어 나오고 묶음 안도 사전 순이 된다 */typedef struct {    char key[11];    char word[11];} Item; typedef struct {    int start, size; /* items[start] ~ items[start + size - 1] */} Group; static Item items[5000];static Group groups[5000]; static int cmp_char(const void *a, const void *b) { return *(const char *)a - *(const char *)b; } static int cmp_item(const void *a, const void *b) {    const Item *x = a, *y = b;    int c = strcmp(x->key, y->key);    return c != 0 ? c : strcmp(x->word, y->word);} static int cmp_group(const void *a, const void *b) {    const Group *x = a, *y = b;    if (x->size != y->size) return y->size - x->size;           /* 큰 묶음 먼저 */    return strcmp(items[x->start].word, items[y->start].word); /* 같으면 첫 낱말 */} int main(void) {    int n;    scanf("%d", &n);    for (int i = 0; i < n; i++) {        scanf("%10s", items[i].word);        strcpy(items[i].key, items[i].word);        qsort(items[i].key, strlen(items[i].key), 1, cmp_char);    }    qsort(items, n, sizeof(Item), cmp_item);     int g = 0;    for (int i = 0; i < n; i++) {        if (i == 0 || strcmp(items[i].key, items[i - 1].key) != 0) groups[g++] = (Group){i, 0};        groups[g - 1].size++;    }    qsort(groups, g, sizeof(Group), cmp_group);     for (int i = 0; i < g; i++) {        printf("%d:", groups[i].size);        for (int j = 0; j < groups[i].size; j++) printf(" %s", items[groups[i].start + j].word);        printf("\n");    }    return 0;}
Craykonmain.cray · 52 行
控制台.读行();words = 控制台.读行().去空白().分割(); // 글자를 정렬한 문자열이 같으면 같은 묶음이다(eat, tea → "aet")groups = 字典();遍历 (w 在 words){    key = 글자정렬(w);    如果 (!groups.包含(key))    {        groups[key] = [];    }    groups[key].添加(w);} keys = groups.键列表;遍历 (key 在 keys){    groups[key].排序();}keys.排序(큰묶음먼저);遍历 (key 在 keys){    g = groups[key];    控制台.输出(g.数量 + ": " + g.连接(" "));} // 낱말의 글자를 하나씩 목록에 담아 정렬한 뒤 다시 잇는다글자정렬(w){    c = [];    循环 (i = 0; i < w.长度; i++)    {        c.添加(w[i]);    }    c.排序();    返回 c.连接("");} // 큰 묶음 먼저, 크기가 같으면 첫 낱말이 사전 순으로 앞선 묶음 먼저큰묶음먼저(앞, 뒤){    如果 (groups[앞].数量 != groups[뒤].数量)    {        返回 groups[뒤].数量 - groups[앞].数量;    }    如果 (groups[앞][0] < groups[뒤][0])    {        返回 -1;    }    返回 1;}