普通视图

发现新文章,点击刷新页面。
今天 — 2025年9月14日LeetCode 每日一题题解

每日一题-元音拼写检查器🟡

2025年9月14日 00:00

在给定单词列表 wordlist 的情况下,我们希望实现一个拼写检查器,将查询单词转换为正确的单词。

对于给定的查询单词 query,拼写检查器将会处理两类拼写错误:

  • 大小写:如果查询匹配单词列表中的某个单词(不区分大小写),则返回的正确单词与单词列表中的大小写相同。
    • 例如:wordlist = ["yellow"], query = "YellOw": correct = "yellow"
    • 例如:wordlist = ["Yellow"], query = "yellow": correct = "Yellow"
    • 例如:wordlist = ["yellow"], query = "yellow": correct = "yellow"
  • 元音错误:如果在将查询单词中的元音 ('a', 'e', 'i', 'o', 'u')  分别替换为任何元音后,能与单词列表中的单词匹配(不区分大小写),则返回的正确单词与单词列表中的匹配项大小写相同。
    • 例如:wordlist = ["YellOw"], query = "yollow": correct = "YellOw"
    • 例如:wordlist = ["YellOw"], query = "yeellow": correct = "" (无匹配项)
    • 例如:wordlist = ["YellOw"], query = "yllw": correct = "" (无匹配项)

此外,拼写检查器还按照以下优先级规则操作:

  • 当查询完全匹配单词列表中的某个单词(区分大小写)时,应返回相同的单词。
  • 当查询匹配到大小写问题的单词时,您应该返回单词列表中的第一个这样的匹配项。
  • 当查询匹配到元音错误的单词时,您应该返回单词列表中的第一个这样的匹配项。
  • 如果该查询在单词列表中没有匹配项,则应返回空字符串。

给出一些查询 queries,返回一个单词列表 answer,其中 answer[i] 是由查询 query = queries[i] 得到的正确单词。

 

示例 1:

输入:wordlist = ["KiTe","kite","hare","Hare"], queries = ["kite","Kite","KiTe","Hare","HARE","Hear","hear","keti","keet","keto"]
输出:["kite","KiTe","KiTe","Hare","hare","","","KiTe","","KiTe"]

示例 2:

输入:wordlist = ["yellow"], queries = ["YellOw"]
输出:["yellow"]

 

提示:

  • 1 <= wordlist.length, queries.length <= 5000
  • 1 <= wordlist[i].length, queries[i].length <= 7
  • wordlist[i] 和 queries[i] 只包含英文字母

三个哈希表(Python/Java/C++/Go/JS/Rust)

作者 endlesscheng
2025年9月8日 08:54

设 $s$ 是 $\textit{wordlist}$ 中的一个字符串,$q$ 是 $\textit{queries}$ 中的一个字符串。

实现完全匹配,可以把 $\textit{wordlist}$ 中的字符串保存到一个哈希集合中,直接查询 $q$ 是否在哈希集合中。

实现不区分大小写的匹配,可以创建一个哈希表 $\textit{lowerToOrigin}$,哈希表的 key 是 $s$ 的每个字母都变成小写字母后的字符串,value 是 $s$。把 $q$ 的每个字母都变成小写字母,查询是否在 $\textit{lowerToOrigin}$ 中,如果在,则为不区分大小写的匹配,匹配结果为 value 值。

实现不区分大小写+元音模糊匹配,可以创建一个哈希表 $\textit{vowelToOrigin}$,哈希表的 key 是 $s$ 的每个字母都变成小写字母,且每个元音都替换成 $\texttt{?}$ 的字符串,value 是 $s$。把 $q$ 的每个字母都变成小写字母,每个元音都替换成 $\texttt{?}$ 后,查询是否在 $\textit{vowelToOrigin}$ 中,如果在,则为不区分大小写+元音模糊匹配,匹配结果为 value 值。

注意:题目要求返回单词列表中的第一个匹配项,即最靠左的匹配项。我们可以倒着遍历 $\textit{wordlist}$,处理每个字符串。这样对于相同的 key,哈希表中的 value 就是 $\textit{wordlist}$ 中的最靠左的匹配项了。

###py

class Solution:
    def spellchecker(self, wordlist: List[str], queries: List[str]) -> List[str]:
        origin = set(wordlist)
        lower_to_origin = {}
        vowel_to_origin = {}
        trans = str.maketrans("aeiou", "?????")  # 替换元音为 '?'

        for s in reversed(wordlist):
            lower = s.lower()
            lower_to_origin[lower] = s  # 例如 kite -> KiTe
            vowel_to_origin[lower.translate(trans)] = s  # 例如 k?t? -> KiTe

        for i, q in enumerate(queries):
            if q in origin:  # 完全匹配
                continue
            lower = q.lower()
            if lower in lower_to_origin:  # 不区分大小写的匹配
                queries[i] = lower_to_origin[lower]
            else:  # 不区分大小写+元音模糊匹配
                queries[i] = vowel_to_origin.get(lower.translate(trans), "")
        return queries

###java

class Solution {
    public String[] spellchecker(String[] wordlist, String[] queries) {
        int n = wordlist.length;
        Set<String> origin = new HashSet<>(Arrays.asList(wordlist));
        Map<String, String> lowerToOrigin = new HashMap<>(n); // 预分配空间
        Map<String, String> vowelToOrigin = new HashMap<>(n);

        for (int i = n - 1; i >= 0; i--) {
            String s = wordlist[i];
            String lower = s.toLowerCase();
            lowerToOrigin.put(lower, s); // 例如 kite -> KiTe
            vowelToOrigin.put(replaceVowels(lower), s); // 例如 k?t? -> KiTe
        }

        for (int i = 0; i < queries.length; i++) {
            String q = queries[i];
            if (origin.contains(q)) { // 完全匹配
                continue;
            }
            String lower = q.toLowerCase();
            if (lowerToOrigin.containsKey(lower)) { // 不区分大小写的匹配
                queries[i] = lowerToOrigin.get(lower);
            } else { // 不区分大小写+元音模糊匹配
                queries[i] = vowelToOrigin.getOrDefault(replaceVowels(lower), "");
            }
        }
        return queries;
    }

    private String replaceVowels(String str) {
        char[] s = str.toCharArray();
        for (int i = 0; i < s.length; ++i) {
            char c = s[i];
            if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
                s[i] = '?';
            }
        }
        return new String(s);
    }
}

###cpp

class Solution {
    string tolower_string(string s) {
        for (char& c : s) {
            c = tolower(c);
        }
        return s;
    }

    string replace_vowels(string s) {
        for (char& c : s) {
            if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
                c = '?';
            }
        }
        return s;
    }

public:
    vector<string> spellchecker(vector<string>& wordlist, vector<string>& queries) {
        int n = wordlist.size();
        unordered_set<string> origin(wordlist.begin(), wordlist.end());
        unordered_map<string, string> lower_to_origin;
        unordered_map<string, string> vowel_to_origin;

        for (int i = n - 1; i >= 0; i--) {
            string& s = wordlist[i];
            string lower = tolower_string(s);
            lower_to_origin[lower] = s; // 例如 kite -> KiTe
            vowel_to_origin[replace_vowels(lower)] = s; // 例如 k?t? -> KiTe
        }

        for (string& q : queries) {
            if (origin.contains(q)) { // 完全匹配
                continue;
            }
            string lower = tolower_string(q);
            auto it = lower_to_origin.find(lower);
            if (it != lower_to_origin.end()) { // 不区分大小写的匹配
                q = it->second;
            } else { // 不区分大小写+元音模糊匹配
                auto it = vowel_to_origin.find(replace_vowels(lower));
                q = it != vowel_to_origin.end() ? it->second : "";
            }
        }
        return queries;
    }
};

###go

func spellchecker(wordlist, queries []string) []string {
n := len(wordlist)
origin := make(map[string]bool, n) // 预分配空间
lowerToOrigin := make(map[string]string, n)
vowelToOrigin := make(map[string]string, n)
// 把元音都替换成 '?'
vowelReplacer := strings.NewReplacer("a", "?", "e", "?", "i", "?", "o", "?", "u", "?")

for _, s := range slices.Backward(wordlist) {
origin[s] = true
lower := strings.ToLower(s)
lowerToOrigin[lower] = s // 例如 kite -> KiTe
vowelToOrigin[vowelReplacer.Replace(lower)] = s // 例如 k?t? -> KiTe
}

for i, q := range queries {
if origin[q] { // 完全匹配
continue
}
lower := strings.ToLower(q)
if s, ok := lowerToOrigin[lower]; ok { // 不区分大小写的匹配
queries[i] = s
} else { // 不区分大小写+元音模糊匹配
queries[i] = vowelToOrigin[vowelReplacer.Replace(lower)]
}
}

return queries
}

###js

var spellchecker = function(wordlist, queries) {
    const origin = new Set(wordlist);
    const lowerToOrigin = new Map();
    const vowelToOrigin = new Map();

    for (let i = wordlist.length - 1; i >= 0; i--) {
        const s = wordlist[i];
        const lower = s.toLowerCase();
        lowerToOrigin.set(lower, s); // 例如 kite -> KiTe
        vowelToOrigin.set(lower.replace(/[aeiou]/g, '?'), s); // 例如 k?t? -> KiTe
    }

    for (let i = 0; i < queries.length; i++) {
        const q = queries[i];
        if (origin.has(q)) { // 完全匹配
            continue;
        }
        const lower = q.toLowerCase();
        // 先看能否不区分大小写匹配,再看能否不区分大小写+元音模糊匹配
        queries[i] = lowerToOrigin.get(lower) ?? vowelToOrigin.get(lower.replace(/[aeiou]/g, '?')) ?? "";
    }
    return queries;
};

###rust

use std::collections::{HashSet, HashMap};

impl Solution {
    pub fn spellchecker(wordlist: Vec<String>, queries: Vec<String>) -> Vec<String> {
        // 替换元音为 '?'
        fn replace_vowels(s: String) -> String {
            let s = s.bytes()
                .map(|c| match c {
                    b'a' | b'e' | b'i' | b'o' | b'u' => b'?',
                    _ => c,
                })
                .collect();
            unsafe { String::from_utf8_unchecked(s) }
        }

        let n = wordlist.len();
        let mut origin = HashSet::with_capacity(n); // 预分配空间
        let mut lower_to_origin = HashMap::with_capacity(n);
        let mut vowel_to_origin = HashMap::with_capacity(n);

        for s in wordlist.into_iter().rev() {
            origin.insert(s.clone());
            let lower = s.to_lowercase();
            lower_to_origin.insert(lower.clone(), s.clone()); // 例如 kite -> KiTe
            vowel_to_origin.insert(replace_vowels(lower), s); // 例如 k?t? -> KiTe
        }

        queries.into_iter()
            .map(|q| {
                if origin.contains(&q) {
                    return q; // 完全匹配
                }
                let lower = q.to_lowercase();
                if let Some(s) = lower_to_origin.get(&lower) {
                    s.clone() // 不区分大小写的匹配
                } else if let Some(s) = vowel_to_origin.get(&replace_vowels(lower)) {
                    s.clone() // 不区分大小写+元音模糊匹配
                } else {
                    "".to_string() // 匹配失败
                }
            })
            .collect()
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}((n+m)L)$,其中 $n$ 是 $\textit{wordlist}$ 的长度,$m$ 是 $\textit{queries}$ 的长度,$L\le 7$ 是字符串的最大长度。
  • 空间复杂度:$\mathcal{O}(nL)$。返回值不计入。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

C++ 字典树+位运算+回溯

作者 Goffe
2022年5月21日 00:39

查找单词有3种匹配方式
1、完全匹配,包括拼写与大小写
2、不完全匹配,找到相同拼写中第一个出现的单词
3、替换元音字母匹配

对于字母的匹配,可以将字典树以不区分字母大小写来建立

单词最长只有7位,可以用二进制记录单词的大小写状态,再在字典树中用哈希表记录相同拼写的不同大小写状态

替换元音字母匹配则用回溯来穷举实现

经测试,一棵字典树用回溯查找元音字母匹配,时间上和空间上都优于两棵字典树

时间复杂度理论上是O(L(M + N)), M是wordlist长度,N是queries长度,L是单词最长长度

执行用时:44 ms
image.png

###cpp

class Solution {
    struct node{
        int first;
        node* next[26];
        unordered_map<int, int> pos;  //记录大小写状态
        node() {
            first = -1;
            for(int i = 0; i < 26; i++) next[i] = NULL;
        }
    }*head;

    int findNear(string &q, int x, node* &cur) {
        if(cur == NULL) return -1;

        if(x == q.length()) {
            return cur->first;
        }else {
            int k = q[x] - (q[x] >= 'a' ? 'a' : 'A'), ret = -1, temp;
            if(k == 0 || k == 4 || k == 8 || k == 14 || k == 20) {  //0 == 'a', 4 == 'e', 8 == 'i', 14 == 'o', 20 == 'u'
                ret = findNear(q, x + 1, cur->next[0]);

                temp = findNear(q, x + 1, cur->next[4]);
                if(ret == -1 || (temp > -1 && temp < ret)) ret = temp;

                temp = findNear(q, x + 1, cur->next[8]);
                if(ret == -1 || (temp > -1 && temp < ret)) ret = temp;

                temp = findNear(q, x + 1, cur->next[14]);
                if(ret == -1 || (temp > -1 && temp < ret)) ret = temp;

                temp = findNear(q, x + 1, cur->next[20]);
                if(ret == -1 || (temp > -1 && temp < ret)) ret = temp;

                return ret;
            }else {
                return findNear(q, x + 1, cur->next[k]);
            }
        }
    }
public:
    vector<string> spellchecker(vector<string>& wordlist, vector<string>& queries) {
        vector<string> res;
        int i, j, k, cap, pos;
        head = new node();

        for(i = wordlist.size() - 1; i >= 0; i--) { //建立字典树
            node* cur = head;
            cap = 0;    //cap记录单词的大小写状态
            for(j = 0; j < wordlist[i].length(); j++) {
                if(wordlist[i][j] >= 'a') {
                    k = wordlist[i][j] - 'a';   //k是字母的序号
                    cap = cap * 2;  //小写状态+0
                }else {
                    k = wordlist[i][j] - 'A';   //k是字母的序号
                    cap = cap * 2 + 1;  //大写状态+1
                }
                if(cur->next[k] == NULL) cur->next[k] = new node();
                cur = cur->next[k];
            }
            cur->pos[cap] = i;  //记录大小写状态对应的wordlist中的出现位置
            cur->first = i; //wordlist是从后往前遍历,所以每次都可以更新first
        }

        for(auto &q : queries) {    //查找符合要求的单词
            node* cur = head;
            cap = 0;    //cap记录要查找的单词的大小写状态
            pos = -1;
            for(j = 0; cur != NULL && j < q.length(); j++) {    //对每个字母进行匹配
                if(q[j] >= 'a') {
                    k = q[j] - 'a'; //k是字母的序号
                    cap = cap * 2;  //小写状态+0
                }else {
                    k = q[j] - 'A'; //k是字母的序号
                    cap = cap * 2 + 1;  //大写状态+1
                }
                cur = cur->next[k];
            }
            if(cur == NULL || cur->first == -1) {   //匹配失败
                pos = findNear(q, 0, head); //替换元音字母查找
                if(pos == -1) res.push_back("");    //替换元音字母匹配失败
                else res.push_back(wordlist[pos]);
            }else { //单词字母匹配成功
                pos = cur->first;   //wordlist中最早出现的单词
                if(cur->pos.find(cap) != cur->pos.end()) pos = cur->pos[cap];   //大小写状态匹配成功,则找到对应的单词出现的位置
                res.push_back(wordlist[pos]);
            }
        }

        return res;
    }
};

元音拼写检查器

作者 LeetCode
2019年1月15日 01:04

方法:哈希映射(HashMap)

思路与算法

我们分析了算法需要考虑的 3 种情况: 当查询完全匹配时,当查询存在大小写不同的单词匹配时,当查询与出现元音错误的单词匹配时。

在所有 3 种情况下,我们都可以使用哈希表来查询答案。

  • 对于第一种情况(完全匹配),我们使用集合存放单词以有效地测试查询单词是否在该组中。
  • 对于第二种情况(大小写不同),我们使用一个哈希表,该哈希表将单词从其小写形式转换为原始单词(大小写正确的形式)。
  • 对于第三种情况(元音错误),我们使用一个哈希表,将单词从其小写形式(忽略元音的情况下)转换为原始单词。

该算法仅剩的要求是认真规划和仔细阅读问题。

###java

class Solution {
    Set<String> words_perfect;
    Map<String, String> words_cap;
    Map<String, String> words_vow;

    public String[] spellchecker(String[] wordlist, String[] queries) {
        words_perfect = new HashSet();
        words_cap = new HashMap();
        words_vow = new HashMap();

        for (String word: wordlist) {
            words_perfect.add(word);

            String wordlow = word.toLowerCase();
            words_cap.putIfAbsent(wordlow, word);

            String wordlowDV = devowel(wordlow);
            words_vow.putIfAbsent(wordlowDV, word);
        }

        String[] ans = new String[queries.length];
        int t = 0;
        for (String query: queries)
            ans[t++] = solve(query);
        return ans;
    }

    public String solve(String query) {
        if (words_perfect.contains(query))
            return query;

        String queryL = query.toLowerCase();
        if (words_cap.containsKey(queryL))
            return words_cap.get(queryL);

        String queryLV = devowel(queryL);
        if (words_vow.containsKey(queryLV))
            return words_vow.get(queryLV);

        return "";
    }

    public String devowel(String word) {
        StringBuilder ans = new StringBuilder();
        for (char c: word.toCharArray())
            ans.append(isVowel(c) ? '*' : c);
        return ans.toString();
    }

    public boolean isVowel(char c) {
        return (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u');
    }
}

###python

class Solution(object):
    def spellchecker(self, wordlist, queries):
        def devowel(word):
            return "".join('*' if c in 'aeiou' else c
                           for c in word)

        words_perfect = set(wordlist)
        words_cap = {}
        words_vow = {}

        for word in wordlist:
            wordlow = word.lower()
            words_cap.setdefault(wordlow, word)
            words_vow.setdefault(devowel(wordlow), word)

        def solve(query):
            if query in words_perfect:
                return query

            queryL = query.lower()
            if queryL in words_cap:
                return words_cap[queryL]

            queryLV = devowel(queryL)
            if queryLV in words_vow:
                return words_vow[queryLV]
            return ""

        return map(solve, queries)

复杂度分析

  • 时间复杂度:$O(\mathcal{C})$,其中 $\mathcal{C}$ 是 wordlistqueries 中内容的总数。

  • 空间复杂度:$O(\mathcal{C})$。

昨天 — 2025年9月13日LeetCode 每日一题题解

每日一题-找到频率最高的元音和辅音🟢

2025年9月13日 00:00

给你一个由小写英文字母('a''z')组成的字符串 s。你的任务是找出出现频率 最高 的元音('a''e''i''o''u' 中的一个)和出现频率最高的辅音(除元音以外的所有字母),并返回这两个频率之和。

注意:如果有多个元音或辅音具有相同的最高频率,可以任选其中一个。如果字符串中没有元音或没有辅音,则其频率视为 0。

一个字母 x 的 频率 是它在字符串中出现的次数。

 

示例 1:

输入: s = "successes"

输出: 6

解释:

  • 元音有:'u' 出现 1 次,'e' 出现 2 次。最大元音频率 = 2。
  • 辅音有:'s' 出现 4 次,'c' 出现 2 次。最大辅音频率 = 4。
  • 输出为 2 + 4 = 6

示例 2:

输入: s = "aeiaeia"

输出: 3

解释:

  • 元音有:'a' 出现 3 次,'e' 出现 2 次,'i' 出现 2 次。最大元音频率 = 3。
  • s 中没有辅音。因此,最大辅音频率 = 0。
  • 输出为 3 + 0 = 3

 

提示:

  • 1 <= s.length <= 100
  • s 只包含小写英文字母

统计字母出现次数+位运算优化(Python/Java/C++/C/Go/JS/Rust)

作者 endlesscheng
2025年5月11日 10:03

在遍历 $s$ 的过程中,用哈希表(或者数组)统计每个字母的出现次数 $\textit{cnt}$。

  • 如果字母是元音,更新 $\textit{maxVowelCnt}$ 的最大值。
  • 如果字母是辅音,更新 $\textit{maxConsonantCnt}$ 的最大值。

最终答案是 $\textit{maxVowelCnt} + \textit{maxConsonantCnt}$。

写法一

###py

class Solution:
    def maxFreqSum(self, s: str) -> int:
        cnt = [0] * 26
        max_vowel_cnt = max_consonant_cnt = 0
        for ch in s:
            idx = ord(ch) - ord('a')
            cnt[idx] += 1
            if ch in "aeiou":
                max_vowel_cnt = max(max_vowel_cnt, cnt[idx])
            else:
                max_consonant_cnt = max(max_consonant_cnt, cnt[idx])
        return max_vowel_cnt + max_consonant_cnt

###py

class Solution:
    def maxFreqSum(self, s: str) -> int:
        cnt = Counter(s)

        max_vowel_cnt = 0
        for ch in "aeiou":
            max_vowel_cnt = max(max_vowel_cnt, cnt[ch])
            del cnt[ch]  # 这样下面计算的一定是辅音出现次数的最大值

        max_consonant_cnt = max(cnt.values(), default=0)
        return max_vowel_cnt + max_consonant_cnt

###java

class Solution {
    public int maxFreqSum(String s) {
        int[] cnt = new int[26];
        int maxVowelCnt = 0;
        int maxConsonantCnt = 0;
        for (char ch : s.toCharArray()) {
            cnt[ch - 'a']++;
            if (ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u') {
                maxVowelCnt = Math.max(maxVowelCnt, cnt[ch - 'a']);
            } else {
                maxConsonantCnt = Math.max(maxConsonantCnt, cnt[ch - 'a']);
            }
        }
        return maxVowelCnt + maxConsonantCnt;
    }
}

###cpp

class Solution {
public:
    int maxFreqSum(string s) {
        int cnt[26]{};
        int max_vowel_cnt = 0;
        int max_consonant_cnt = 0;
        for (char ch : s) {
            cnt[ch - 'a']++;
            if (ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u') {
                max_vowel_cnt = max(max_vowel_cnt, cnt[ch - 'a']);
            } else {
                max_consonant_cnt = max(max_consonant_cnt, cnt[ch - 'a']);
            }
        }
        return max_vowel_cnt + max_consonant_cnt;
    }
};

###c

#define MAX(a, b) ((b) > (a) ? (b) : (a))

int maxFreqSum(char* s) {
    int cnt[26] = {};
    int max_vowel_cnt = 0;
    int max_consonant_cnt = 0;
    for (int i = 0; s[i]; i++) {
        char ch = s[i];
        cnt[ch - 'a']++;
        if (ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u') {
            max_vowel_cnt = MAX(max_vowel_cnt, cnt[ch - 'a']);
        } else {
            max_consonant_cnt = MAX(max_consonant_cnt, cnt[ch - 'a']);
        }
    }
    return max_vowel_cnt + max_consonant_cnt;
}

###go

func maxFreqSum(s string) int {
cnt := [26]int{}
maxVowelCnt := 0
maxConsonantCnt := 0
for _, ch := range s {
cnt[ch-'a']++
if strings.ContainsRune("aeiou", ch) {
maxVowelCnt = max(maxVowelCnt, cnt[ch-'a'])
} else {
maxConsonantCnt = max(maxConsonantCnt, cnt[ch-'a'])
}
}
return maxVowelCnt + maxConsonantCnt
}

###js

var maxFreqSum = function(s) {
    const cnt = Array(26).fill(0);
    let maxVowelCnt = 0;
    let maxConsonantCnt = 0;
    for (const ch of s) {
        const idx = ch.charCodeAt(0) - 'a'.charCodeAt(0);
        cnt[idx]++;
        if ("aeiou".includes(ch)) {
            maxVowelCnt = Math.max(maxVowelCnt, cnt[idx]);
        } else {
            maxConsonantCnt = Math.max(maxConsonantCnt, cnt[idx]);
        }
    }
    return maxVowelCnt + maxConsonantCnt;
};

###rust

impl Solution {
    pub fn max_freq_sum(s: String) -> i32 {
        let mut cnt = [0; 26];
        let mut max_vowel_cnt = 0;
        let mut max_consonant_cnt = 0;
        for ch in s.bytes() {
            let idx = (ch as u8 - b'a') as usize;
            cnt[idx] += 1;
            if "aeiou".contains(ch as char) {
                max_vowel_cnt = max_vowel_cnt.max(cnt[idx]);
            } else {
                max_consonant_cnt = max_consonant_cnt.max(cnt[idx]);
            }
        }
        max_vowel_cnt + max_consonant_cnt
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$ 或 $\mathcal{O}(n + |\Sigma|)$,其中 $n$ 是 $s$ 的长度,$|\Sigma|=26$ 是字符集合的大小。
  • 空间复杂度:$\mathcal{O}(|\Sigma|)$。

写法二

根据 从集合论到位运算,我们可以把元音集合

$$
{\texttt{a},\texttt{e},\texttt{i},\texttt{o},\texttt{u}}
$$

视作数字

$$
2^0 + 2^4 + 2^8 + 2^{14} + 2^{20} = 1065233
$$

即十六进制的 $\texttt{0x104111}$。

可以用位运算快速判断字母是否在元音集合中。

###py

class Solution:
    def maxFreqSum(self, s: str) -> int:
        VOWEL_MASK = 0x104111
        cnt = [0] * 26
        max_cnt = [0] * 2
        for ch in s:
            ch = ord(ch) - ord('a')
            bit = VOWEL_MASK >> ch & 1
            cnt[ch] += 1
            max_cnt[bit] = max(max_cnt[bit], cnt[ch])
        return sum(max_cnt)

###java

class Solution {
    public int maxFreqSum(String s) {
        final int VOWEL_MASK = 0x104111;
        int[] cnt = new int[26];
        int[] maxCnt = new int[2];
        for (char ch : s.toCharArray()) {
            ch -= 'a';
            int bit = VOWEL_MASK >> ch & 1;
            cnt[ch]++;
            maxCnt[bit] = Math.max(maxCnt[bit], cnt[ch]);
        }
        return maxCnt[0] + maxCnt[1];
    }
}

###cpp

class Solution {
public:
    int maxFreqSum(string s) {
        const int VOWEL_MASK = 0x104111;
        int cnt[26]{};
        int max_cnt[2]{};
        for (char ch : s) {
            ch -= 'a';
            int bit = VOWEL_MASK >> ch & 1;
            cnt[ch]++;
            max_cnt[bit] = max(max_cnt[bit], cnt[ch]);
        }
        return max_cnt[0] + max_cnt[1];
    }
};

###c

#define VOWEL_MASK 0x104111
#define MAX(a, b) ((b) > (a) ? (b) : (a))

int maxFreqSum(char* s) {
    int cnt[26] = {};
    int max_cnt[2] = {};
    for (int i = 0; s[i]; i++) {
        int ch = s[i] - 'a';
        int bit = VOWEL_MASK >> ch & 1;
        cnt[ch]++;
        max_cnt[bit] = MAX(max_cnt[bit], cnt[ch]);
    }
    return max_cnt[0] + max_cnt[1];
}

###go

func maxFreqSum(s string) int {
const vowelMask = 0x104111
cnt := [26]int{}
maxCnt := [2]int{}
for _, ch := range s {
ch -= 'a'
bit := vowelMask >> ch & 1
cnt[ch]++
maxCnt[bit] = max(maxCnt[bit], cnt[ch])
}
return maxCnt[0] + maxCnt[1]
}

###js

var maxFreqSum = function(s) {
    const VOWEL_MASK = 0x104111;
    const cnt = Array(26).fill(0);
    const maxCnt = [0, 0];
    for (const ch of s) {
        const idx = ch.charCodeAt(0) - 'a'.charCodeAt(0);
        const bit = VOWEL_MASK >> idx & 1;
        cnt[idx]++;
        maxCnt[bit] = Math.max(maxCnt[bit], cnt[idx]);
    }
    return maxCnt[0] + maxCnt[1];
};

###rust

impl Solution {
    pub fn max_freq_sum(s: String) -> i32 {
        const VOWEL_MASK: usize = 0x104111;
        let mut cnt = [0; 26];
        let mut max_cnt = [0; 2];
        for ch in s.bytes() {
            let idx = (ch as u8 - b'a') as usize;
            let bit = VOWEL_MASK >> idx & 1;
            cnt[idx] += 1;
            max_cnt[bit] = max_cnt[bit].max(cnt[idx]);
        }
        max_cnt[0] + max_cnt[1]
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $s$ 的长度。
  • 空间复杂度:$\mathcal{O}(1)$。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

昨天以前LeetCode 每日一题题解

[Python3/Java/C++/Go/TypeScript] 一题一解:脑筋急转弯(清晰题解)

作者 lcbin
2025年9月12日 06:52

方法一:脑筋急转弯

我们不妨记字符串中元音字母的个数为 $k$。

如果 $k = 0$,即字符串中没有元音字母,那么小红无法移除任何子字符串,小明直接获胜。

如果 $k$ 为奇数,那么小红可以移除整个字符串,小红直接获胜。

如果 $k$ 为偶数,那么小红可以移除 $k - 1$ 个元音字母,此时剩下一个元音字母,小明无法移除任何子字符串,小红直接获胜。

综上所述,如果字符串中包含元音字母,那么小红获胜,否则小明获胜。

###python

class Solution:
    def doesAliceWin(self, s: str) -> bool:
        vowels = set("aeiou")
        return any(c in vowels for c in s)

###java

class Solution {
    public boolean doesAliceWin(String s) {
        for (int i = 0; i < s.length(); ++i) {
            if ("aeiou".indexOf(s.charAt(i)) != -1) {
                return true;
            }
        }
        return false;
    }
}

###cpp

class Solution {
public:
    bool doesAliceWin(string s) {
        string vowels = "aeiou";
        for (char c : s) {
            if (vowels.find(c) != string::npos) {
                return true;
            }
        }
        return false;
    }
};

###go

func doesAliceWin(s string) bool {
vowels := "aeiou"
for _, c := range s {
if strings.ContainsRune(vowels, c) {
return true
}
}
return false
}

###ts

function doesAliceWin(s: string): boolean {
    const vowels = 'aeiou';
    for (const c of s) {
        if (vowels.includes(c)) {
            return true;
        }
    }
    return false;
}

###rust

impl Solution {
    pub fn does_alice_win(s: String) -> bool {
        let vowels = "aeiou";
        for c in s.chars() {
            if vowels.contains(c) {
                return true;
            }
        }
        false
    }
}

###cs

public class Solution {
    public bool DoesAliceWin(string s) {
        string vowels = "aeiou";
        foreach (char c in s) {
            if (vowels.Contains(c)) {
                return true;
            }
        }
        return false;
    }
}

时间复杂度 $O(n)$,其中 $n$ 为字符串 $s$ 的长度。空间复杂度 $O(1)$。


有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~

每日一题-字符串元音游戏🟡

2025年9月12日 00:00

小红和小明在玩一个字符串元音游戏。

给你一个字符串 s,小红和小明将轮流参与游戏,小红开始:

  • 在小红的回合,她必须移除 s 中包含 奇数 个元音的任意 非空 子字符串
  • 在小明的回合,他必须移除 s 中包含 偶数 个元音的任意 非空 子字符串

第一个无法在其回合内进行移除操作的玩家输掉游戏。假设小红和小明都采取 最优策略

如果小红赢得游戏,返回 true,否则返回 false

英文元音字母包括:a, e, i, o, 和 u

 

示例 1:

输入: s = "leetcoder"

输出: true

解释:
小红可以执行如下移除操作来赢得游戏:

  • 小红先手,她可以移除加下划线的子字符串 s = "leetcoder",其中包含 3 个元音。结果字符串为 s = "der"
  • 小明接着,他可以移除加下划线的子字符串 s = "der",其中包含 0 个元音。结果字符串为 s = "er"
  • 小红再次操作,她可以移除整个字符串 s = "er",其中包含 1 个元音。
  • 又轮到小明,由于字符串为空,无法执行移除操作,因此小红赢得游戏。

示例 2:

输入: s = "bbcd"

输出: false

解释:
小红在她的第一回合无法执行移除操作,因此小红输掉了游戏。

 

提示:

  • 1 <= s.length <= 105
  • s 仅由小写英文字母组成。

脑筋急转弯(Python/Java/C++/Go)

作者 endlesscheng
2024年7月21日 12:08

分类讨论:

  • 如果 $s$ 不包含任何元音,小红输。
  • 如果 $s$ 包含奇数个元音,小红可以直接把整个 $s$ 移除,小红赢。
  • 如果 $s$ 包含正偶数个元音,由于偶数减奇数等于奇数,小红移除任意包含奇数个元音的子串后,剩余元音个数仍然为奇数。由于奇数减偶数还是奇数,所以无论小明怎么操作,仍然会剩下奇数个元音,此时小红可以直接把整个 $s$ 移除,小红赢。

总结:

$$
\begin{aligned}
& 0 \to 输 \
& 奇数 \xrightarrow{小红} 赢 \
& 正偶数 \xrightarrow{小红} 奇数 \xrightarrow{小明} 奇数 \xrightarrow{小红} 赢 \
\end{aligned}
$$

所以只要 $s$ 包含至少一个元音,就返回 $\texttt{true}$,否则返回 $\texttt{false}$。

###py

class Solution:
    def doesAliceWin(self, s: str) -> bool:
        return any(c in s for c in "aeiou")

###py

class Solution:
    def doesAliceWin(self, s: str) -> bool:
        return any(c in "aeiou" for c in s)

###java

class Solution {
    public boolean doesAliceWin(String s) {
        for (char c : s.toCharArray()) {
            if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
                return true;
            }
        }
        return false;
    }
}

###cpp

class Solution {
public:
    bool doesAliceWin(string s) {
        return ranges::any_of(s, [](char c) {
            return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
        });
    }
};

###c

bool doesAliceWin(char* s) {
    for (int i = 0; s[i]; i++) {
        char c = s[i];
        if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
            return true;
        }
    }
    return false;
}

###go

func doesAliceWin(s string) bool {
return strings.ContainsAny(s, "aeiou")
}

###js

var doesAliceWin = function(s) {
    return /[aeiou]/.test(s);
};

###rust

impl Solution {
    pub fn does_alice_win(s: String) -> bool {
        s.bytes().any(|c| "aeiou".contains(c as char))
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $s$ 的长度。
  • 空间复杂度:$\mathcal{O}(1)$。

思考题

如果小明先手呢?

欢迎在评论区发表你的思路/代码。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

每日一题-将字符串中的元音字母排序🟡

2025年9月11日 00:00

给你一个下标从 0 开始的字符串 s ,将 s 中的元素重新 排列 得到新的字符串 t ,它满足:

  • 所有辅音字母都在原来的位置上。更正式的,如果满足 0 <= i < s.length 的下标 i 处的 s[i] 是个辅音字母,那么 t[i] = s[i] 。
  • 元音字母都必须以他们的 ASCII 值按 非递减 顺序排列。更正式的,对于满足 0 <= i < j < s.length 的下标 i 和 j  ,如果 s[i] 和 s[j] 都是元音字母,那么 t[i] 的 ASCII 值不能大于 t[j] 的 ASCII 值。

请你返回结果字母串。

元音字母为 'a' ,'e' ,'i' ,'o' 和 'u' ,它们可能是小写字母也可能是大写字母,辅音字母是除了这 5 个字母以外的所有字母。

 

示例 1:

输入:s = "lEetcOde"
输出:"lEOtcede"
解释:'E' ,'O' 和 'e' 是 s 中的元音字母,'l' ,'t' ,'c' 和 'd' 是所有的辅音。将元音字母按照 ASCII 值排序,辅音字母留在原地。

示例 2:

输入:s = "lYmpH"
输出:"lYmpH"
解释:s 中没有元音字母(s 中都为辅音字母),所以我们返回 "lYmpH" 。

 

提示:

  • 1 <= s.length <= 105
  • s 只包含英语字母表中的 大写 小写 字母。

C++, 模拟

作者 liu-xiang-3
2023年7月23日 21:19

思路

  1. 找出所有的元音, 排序;
  2. 遍历字符串, 如果为元音, 插入排序后的元音字母;
class Solution {
public:
    string vowels = "aeiouAEIOU";
    bool check(char c) {
        for (auto &ch : vowels) {
            if (ch == c) {
                return true;
            }
        }
        return false;
    }

    string sortVowels(string s) {
        string vow;
        /* 找出元音并排序 */
        for (auto &ch : s) {
            if (check(ch)) {
                vow.push_back(ch);
            }
        }
        sort(vow.begin(), vow.end());
        string ans;
        int i = 0;
        /* 遍历字符串, 按照元音字母排序 */
        for (auto &ch : s) {
            if (check(ch)) {
                ans.push_back(vow[i++]);
            } else {
                ans.push_back(ch);
            }
        }
        return ans;
    }
};

三种写法:排序/位运算优化/计数排序(Python/Java/C++/C/Go/JS/Rust)

作者 endlesscheng
2023年7月23日 08:18

示例 1 的 $s = \texttt{lEetcOde}$,其中元音字母为 $\texttt{EeOe}$,排序后为 $\texttt{EOee}$。(大写字母排前面是因为大写字母的 ASCII 值更小)

原来的 $s = \texttt{l\underline{Ee}tc\underline{O}d\underline{e}}$ 视作 $\texttt{l}__\texttt{tc}_\texttt{d}_$,包含 $4$ 个空位。

在空位依次填入 $\texttt{EOee}$,得到答案 $\texttt{l\underline{EO}tc\underline{e}d\underline{e}}$。

写法一

class Solution:
    def sortVowels(self, s: str) -> str:
        vowels = sorted(ch for ch in s if ch in "AEIOUaeiou")
        t = list(s)  # str 无法修改,转成 list
        j = 0
        for i, ch in enumerate(t):
            if ch in "AEIOUaeiou":
                t[i] = vowels[j]  # 填空
                j += 1
        return ''.join(t)
class Solution {
    public String sortVowels(String S) {
        StringBuilder vowels = new StringBuilder();
        char[] s = S.toCharArray();
        for (char ch : s) {
            char c = Character.toLowerCase(ch);
            if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
                vowels.append(ch);
            }
        }

        char[] sortedVowels = vowels.toString().toCharArray();
        Arrays.sort(sortedVowels);

        int j = 0;
        for (int i = 0; i < s.length; i++) {
            char c = Character.toLowerCase(s[i]);
            if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
                s[i] = sortedVowels[j++];
            }
        }
        return new String(s);
    }
}
class Solution {
public:
    string sortVowels(string s) {
        string vowels;
        for (char ch : s) {
            char c = tolower(ch);
            if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
                vowels += ch;
            }
        }

        ranges::sort(vowels);

        int j = 0;
        for (char& ch : s) {
            char c = tolower(ch);
            if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
                ch = vowels[j++];
            }
        }
        return s;
    }
};
#define VOWEL_MASK 0x208222

int cmp(const void* a, const void* b) {
    return *(char*)a - *(char*)b;
}

char* sortVowels(char* s) {
    int n = strlen(s);
    char* vowels = malloc(n * sizeof(char));
    int k = 0;
    for (int i = 0; i < n; i++) {
        char c = tolower(s[i]);
        if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
            vowels[k++] = s[i];
        }
    }

    qsort(vowels, k, sizeof(char), cmp);

    k = 0;
    for (int i = 0; i < n; i++) {
        char c = tolower(s[i]);
        if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
            s[i] = vowels[k++];
        }
    }

    free(vowels);
    return s;
}
func sortVowels(s string) string {
vowels := []byte{}
for _, ch := range s {
c := unicode.ToLower(ch)
if strings.IndexRune("aeiou", c) >= 0 {
vowels = append(vowels, byte(ch))
}
}

slices.Sort(vowels)

t := []byte(s)
j := 0
for i, ch := range t {
c := unicode.ToLower(rune(ch))
if strings.IndexRune("aeiou", c) >= 0 {
t[i] = vowels[j]
j++
}
}
return string(t)
}
var sortVowels = function(s) {
    const vowels = [];
    for (const ch of s) {
        if ("AEIOUaeiou".includes(ch)) {
            vowels.push(ch);
        }
    }

    vowels.sort();

    let j = 0;
    const t = s.split('');
    for (let i = 0; i < t.length; i++) {
        if ("AEIOUaeiou".includes(t[i])) {
            t[i] = vowels[j++];
        }
    }
    return t.join('');
};
impl Solution {
    pub fn sort_vowels(s: String) -> String {
        let mut vowels = s.bytes()
            .filter(|&ch| "AEIOUaeiou".contains(ch as char))
            .collect::<Vec<_>>();

        vowels.sort_unstable();

        let mut s = s.into_bytes();
        let mut j = 0;
        for ch in s.iter_mut() {
            if "AEIOUaeiou".contains(*ch as char) {
                *ch = vowels[j];
                j += 1;
            }
        }
        unsafe { String::from_utf8_unchecked(s) }
    }
}

写法二(优化)

查看 ASCII 表可知,$\texttt{A}$ 到 $\texttt{Z}$ 的 ASCII 值的二进制低 $5$ 位是 $1$ 到 $26$,$\texttt{a}$ 到 $\texttt{z}$ 的 ASCII 值的二进制低 $5$ 位也是 $1$ 到 $26$。

所以可以用 ch & 31 把字母 $\textit{ch}$ 转成 $1$ 到 $26$,无论 $\textit{ch}$ 是大写还是小写,规则是统一的。

由于 $\texttt{aeiou}$ 分别是第 $1,5,7,15,21$ 个字母,根据 从集合论到位运算,我们可以把元音集合

$$
{\texttt{a},\texttt{e},\texttt{i},\texttt{o},\texttt{u}}
$$

视作数字

$$
2^1 + 2^5 + 2^7 + 2^{15} + 2^{21} = 2130466
$$

即十六进制的 $\texttt{0x208222}$。

可以用位运算快速判断字母是否在元音集合中。

class Solution:
    def sortVowels(self, s: str) -> str:
        VOWEL_MASK = 0x208222
        is_vowel = lambda ch: VOWEL_MASK >> (ord(ch) & 31) & 1

        vowels = sorted(filter(is_vowel, s))
        t = list(s)  # str 无法修改,转成 list
        j = 0
        for i, ch in enumerate(t):
            if is_vowel(ch):
                t[i] = vowels[j]  # 填空
                j += 1
        return ''.join(t)
class Solution {
    public String sortVowels(String S) {
        final int VOWEL_MASK = 0x208222;

        char[] s = S.toCharArray();
        byte[] vowels = new byte[s.length]; // 比 StringBuilder 快
        int k = 0;
        for (char ch : s) {
            if ((VOWEL_MASK >> (ch & 31) & 1) > 0) {
                vowels[k++] = (byte) ch;
            }
        }

        Arrays.sort(vowels, 0, k);

        k = 0;
        for (int i = 0; i < s.length; i++) {
            if ((VOWEL_MASK >> (s[i] & 31) & 1) > 0) {
                s[i] = (char) vowels[k++];
            }
        }
        return new String(s);
    }
}
class Solution {
public:
    string sortVowels(string s) {
        const int VOWEL_MASK = 0x208222;
        string vowels;
        for (char ch : s) {
            if (VOWEL_MASK >> (ch & 31) & 1) { // ch 是元音
                vowels += ch;
            }
        }

        ranges::sort(vowels);

        int j = 0;
        for (char& ch : s) {
            if (VOWEL_MASK >> (ch & 31) & 1) { // ch 是元音
                ch = vowels[j++];
            }
        }
        return s;
    }
};
#define VOWEL_MASK 0x208222

int cmp(const void* a, const void* b) {
    return *(char*)a - *(char*)b;
}

char* sortVowels(char* s) {
    int n = strlen(s);
    char* vowels = malloc(n * sizeof(char));
    int k = 0;
    for (int i = 0; i < n; i++) {
        if (VOWEL_MASK >> (s[i] & 31) & 1) {
            vowels[k++] = s[i];
        }
    }

    qsort(vowels, k, sizeof(char), cmp);

    k = 0;
    for (int i = 0; i < n; i++) {
        if (VOWEL_MASK >> (s[i] & 31) & 1) {
            s[i] = vowels[k++];
        }
    }

    free(vowels);
    return s;
}
func sortVowels(s string) string {
const vowelMask = 0x208222
vowels := []byte{}
for _, ch := range s {
if vowelMask>>(ch&31)&1 > 0 { // ch 是元音
vowels = append(vowels, byte(ch))
}
}

slices.Sort(vowels)

t := []byte(s)
j := 0
for i, ch := range t {
if vowelMask>>(ch&31)&1 > 0 { // ch 是元音
t[i] = vowels[j]
j++
}
}
return string(t)
}
var sortVowels = function(s) {
    const VOWEL_MASK = 0x208222;
    const vowels = [];
    for (const ch of s) {
        if (VOWEL_MASK >> (ch.charCodeAt(0) & 31) & 1) {
            vowels.push(ch);
        }
    }

    vowels.sort();

    const t = s.split('');
    let j = 0;
    for (let i = 0; i < t.length; i++) {
        if (VOWEL_MASK >> (t[i].charCodeAt(0) & 31) & 1) {
            t[i] = vowels[j++];
        }
    }
    return t.join('');
};
impl Solution {
    pub fn sort_vowels(s: String) -> String {
        const VOWEL_MASK: u32 = 0x208222;
        let mut vowels = s.bytes()
            .filter(|&ch| VOWEL_MASK >> (ch & 31) & 1 > 0)
            .collect::<Vec<_>>();

        vowels.sort_unstable();

        let mut s = s.into_bytes();
        let mut j = 0;
        for ch in s.iter_mut() {
            if VOWEL_MASK >> (*ch & 31) & 1 > 0 {
                *ch = vowels[j];
                j += 1;
            }
        }
        unsafe { String::from_utf8_unchecked(s) }
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n\log n)$,其中 $n$ 是 $s$ 的长度。
  • 空间复杂度:$\mathcal{O}(n)$。

写法三:计数排序

class Solution:
    def sortVowels(self, s: str) -> str:
        VOWELS = "AEIOUaeiou"
        cnt = Counter(ch for ch in s if ch in VOWELS)

        it = iter(VOWELS)
        cur = next(it)

        t = list(s)  # str 无法修改,转成 list
        for i, ch in enumerate(t):
            if ch in VOWELS:
                if cnt[cur] == 0:
                    # 找下一个出现次数大于 0 的元音字母
                    cur = next(c for c in it if cnt[c])
                t[i] = cur
                cnt[cur] -= 1
        return ''.join(t)
class Solution {
    public String sortVowels(String S) {
        final int VOWEL_MASK = 0x208222;

        char[] s = S.toCharArray();
        int[] cnt = new int['u' + 1];
        for (char ch : s) {
            if ((VOWEL_MASK >> (ch & 31) & 1) > 0) {
                cnt[ch]++;
            }
        }

        int j = 'A';
        for (int i = 0; i < s.length; i++) {
            if ((VOWEL_MASK >> (s[i] & 31) & 1) == 0) {
                continue;
            }
            // 找下一个出现次数大于 0 的元音字母
            while (cnt[j] == 0) {
                j = j == 'Z' ? 'a' : j + 1;
            }
            s[i] = (char) j;
            cnt[j]--;
        }
        return new String(s);
    }
}
class Solution {
public:
    string sortVowels(string s) {
        const int VOWEL_MASK = 0x208222;
        int cnt['u' + 1]{};
        for (char ch : s) {
            if (VOWEL_MASK >> (ch & 31) & 1) {
                cnt[ch]++;
            }
        }

        char j = 'A';
        for (char& ch : s) {
            if ((VOWEL_MASK >> (ch & 31) & 1) == 0) {
                continue;
            }
            // 找下一个出现次数大于 0 的元音字母
            while (cnt[j] == 0) {
                j = j == 'Z' ? 'a' : j + 1;
            }
            ch = j;
            cnt[j]--;
        }
        return s;
    }
};
#define VOWEL_MASK 0x208222

char* sortVowels(char* s) {
    int cnt['z' + 1] = {};
    for (int i = 0; s[i]; i++) {
        if (VOWEL_MASK >> (s[i] & 31) & 1) {
            cnt[s[i]]++;
        }
    }

    char j = 'A';
    for (int i = 0; s[i]; i++) {
        if ((VOWEL_MASK >> (s[i] & 31) & 1) == 0) {
            continue;
        }
        // 找下一个出现次数大于 0 的元音字母
        while (cnt[j] == 0) {
            j = j == 'Z' ? 'a' : j + 1;
        }
        s[i] = j;
        cnt[j]--;
    }
    return s;
}
func sortVowels(s string) string {
const vowelMask = 0x208222
cnt := ['u' + 1]int{}
for _, ch := range s {
if vowelMask>>(ch&31)&1 > 0 {
cnt[ch]++
}
}

t := []byte(s)
j := byte('A')
for i, ch := range t {
if vowelMask>>(ch&31)&1 == 0 {
continue
}
// 找下一个出现次数大于 0 的元音字母
for cnt[j] == 0 {
if j == 'Z' {
j = 'a'
} else {
j++
}
}
t[i] = j
cnt[j]--
}
return string(t)
}
var sortVowels = function(s) {
    const VOWEL_MASK = 0x208222;
    const cnt = Array('u'.charCodeAt(0) + 1).fill(0);
    for (const ch of s) {
        const c = ch.charCodeAt(0);
        if (VOWEL_MASK >> (c & 31) & 1) {
            cnt[c]++;
        }
    }

    const t = s.split('');
    const ordZ = 'Z'.charCodeAt(0);
    let j = 'A'.charCodeAt(0);
    for (let i = 0; i < t.length; i++) {
        if ((VOWEL_MASK >> (t[i].charCodeAt(0) & 31) & 1) === 0) {
            continue;
        }
        // 找下一个出现次数大于 0 的元音字母
        while (cnt[j] === 0) {
            j = j == ordZ ? 'a'.charCodeAt(0) : j + 1;
        }
        t[i] = String.fromCharCode(j);
        cnt[j]--;
    }
    return t.join('');
};
impl Solution {
    pub fn sort_vowels(s: String) -> String {
        const VOWEL_MASK: u32 = 0x208222;
        let mut cnt = [0; 'z' as usize + 1];
        for ch in s.bytes() {
            if (VOWEL_MASK >> (ch & 31)) & 1 > 0 {
                cnt[ch as usize] += 1;
            }
        }

        let mut s = s.into_bytes();
        let mut j = 0;
        for ch in s.iter_mut() {
            if VOWEL_MASK >> (*ch & 31) & 1 == 0 {
                continue;
            }
            // 找下一个出现次数大于 0 的元音字母
            while cnt[j as usize] == 0 {
                if j == b'Z' {
                    j = b'a';
                } else {
                    j += 1;
                }
            }
            *ch = j;
            cnt[j as usize] -= 1;
        }
        unsafe { String::from_utf8_unchecked(s) }
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n+|\Sigma|)$,其中 $n$ 是 $s$ 的长度,$|\Sigma|=10$ 或 $52$ 或 $128$ 是字符集合的大小。
  • 空间复杂度:$\mathcal{O}(|\Sigma|)$。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

字符串排序处理, Python两行双百 | 6926. 将字符串中的元音字母排序

2023年7月23日 06:29

Problem: 6926. 将字符串中的元音字母排序

[TOC]

思路

第一步,提取s中的元音字符,保存至列表s1

第二步,排序s1

第三步,遍历s:

  • 遇到元音,则顺序插入s1排序后的字符

  • 遇到非元音,保留

最后,返回拼接后的字符串

Code

Python两行实现,双百:

时间128 ms击败100%;内存18.9 MB击败100%

###Python3

class Solution:
    def sortVowels(self, s: str) -> str:
        s1 = sorted([c for c in s if c in "AEIOUaeiou"], reverse = True)
        return ''.join(s1.pop() if c in "AEIOUaeiou" else c for c in s)

您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^

↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^

每日一题-知道秘密的人数🟡

2025年9月9日 00:00

在第 1 天,有一个人发现了一个秘密。

给你一个整数 delay ,表示每个人会在发现秘密后的 delay 天之后,每天 给一个新的人 分享 秘密。同时给你一个整数 forget ,表示每个人在发现秘密 forget 天之后会 忘记 这个秘密。一个人 不能 在忘记秘密那一天及之后的日子里分享秘密。

给你一个整数 n ,请你返回在第 n 天结束时,知道秘密的人数。由于答案可能会很大,请你将结果对 109 + 7 取余 后返回。

 

示例 1:

输入:n = 6, delay = 2, forget = 4
输出:5
解释:
第 1 天:假设第一个人叫 A 。(一个人知道秘密)
第 2 天:A 是唯一一个知道秘密的人。(一个人知道秘密)
第 3 天:A 把秘密分享给 B 。(两个人知道秘密)
第 4 天:A 把秘密分享给一个新的人 C 。(三个人知道秘密)
第 5 天:A 忘记了秘密,B 把秘密分享给一个新的人 D 。(三个人知道秘密)
第 6 天:B 把秘密分享给 E,C 把秘密分享给 F 。(五个人知道秘密)

示例 2:

输入:n = 4, delay = 1, forget = 3
输出:6
解释:
第 1 天:第一个知道秘密的人为 A 。(一个人知道秘密)
第 2 天:A 把秘密分享给 B 。(两个人知道秘密)
第 3 天:A 和 B 把秘密分享给 2 个新的人 C 和 D 。(四个人知道秘密)
第 4 天:A 忘记了秘密,B、C、D 分别分享给 3 个新的人。(六个人知道秘密)

 

提示:

  • 2 <= n <= 1000
  • 1 <= delay < forget <= n

递推

作者 tsreaper
2022年7月3日 12:13

解法:递推

记 $f[i]$ 表示第 $i$ 天新知道秘密的人数。根据题意有初始条件 $f[1] = 1$。

根据题意,在第 $i$ 天,只有 $(i - forget, i - delay]$ 这个区间里新知道秘密的人才会告诉其它人。因此有递推式 $f[i] = \sum\limits_{j=i-forget+1}^{i-delay} f[j]$。

答案就是在第 $n$ 天还没有忘记秘密的人数之和。也就是 $\sum\limits_{i=n-forget+1}^n f[i]$。

实现的时候注意 $i - forget + 1$ 和 $i - delay$ 可能超出 $[1, n]$ 的范围,需要处理一下边界。

直接递推的复杂度是 $\mathcal{O}(n^2)$ 的,由于 $n$ 只有 $10^3$ 也可以通过。我们也可以维护前缀和把复杂度降到 $\mathcal{O}(n)$。

参考代码(c++)

###c++

class Solution {
    const int MOD = 1000000007;

public:
    int peopleAwareOfSecret(int n, int delay, int forget) {
        vector<long long> f(n + 1), g(n + 1);
        f[1] = g[1] = 1;
        for (int i = 2; i <= n; i++) {
            int L = max(0, i - forget);
            int R = max(0, i - delay);
            f[i] = (g[R] - g[L] + MOD) % MOD;
            g[i] = (g[i - 1] + f[i]) % MOD;
        }
        return (g[n] - g[max(0, n - forget)] + MOD) % MOD;
    }
};

python 动态规划

作者 linn-9k
2022年7月3日 12:12

思路:

在比赛中一开始想复杂了,想要使用一个dp就来统计总人数,然后发现非常不好做,因为后续的dp需要考虑前面forget的人数,很困难,转念一想,我只需要统计第i天新增的人数就好了,然后知道秘密的总人数其实就等于从最后一天往前推forget天的人数和。
每一个第i天知道秘密的人,都对[i+delay,i+forget)这个区间有贡献,从前往后推即可,时间复杂度O(n^2)

class Solution:
    def peopleAwareOfSecret(self, n: int, delay: int, forget: int) -> int:
        # dp[i]的含义是第i天获取秘密的人
        dp = [0] * (n+1)
        dp[1] = 1
        for i in range(1,n+1):
            for j in range(i+delay,i+forget):
                if j <= n:
                    dp[j] += dp[i]
        result = 0
        for i in range(n+1-forget,n+1):
            result += dp[i]
            result %= (10**9+7)
        return result%(10**9+7)

image.png

❌
❌