• 数据结构刷题:第六天


    目录

    一,字符串中的第一个唯一字符

    1,使用哈希表存储频数

    思路与算法

    复杂度分析

    2,使用哈希表存储索引

    思路与算法

    复杂度分析

    3,队列

    思路与算法

    小贴士

    复杂度分析

    二,赎金信

    1,字符统计 

    复杂度分析

    三,有效的字母异位词

    ​编辑1,排序

    复杂度分析

    2,哈希表

    复杂度分析

    一,字符串中的第一个唯一字符

    387. 字符串中的第一个唯一字符 - 力扣(LeetCode)icon-default.png?t=M7J4https://leetcode.cn/problems/first-unique-character-in-a-string/?plan=data-structures&plan_progress=ggfacv7

    1,使用哈希表存储频数

    思路与算法

    我们可以对字符串进行两次遍历。

    在第一次遍历时,我们使用哈希映射统计出字符串中每个字符出现的次数。在第二次遍历时,我们只要遍历到了一个只出现一次的字符,那么就返回它的索引,否则在遍历结束后返回 −1。

    1. class Solution {
    2. public:
    3. int firstUniqChar(string s) {
    4. unordered_map<int, int> frequency;
    5. for (char ch: s) {
    6. ++frequency[ch];
    7. }
    8. for (int i = 0; i < s.size(); ++i) {
    9. if (frequency[s[i]] == 1) {
    10. return i;
    11. }
    12. }
    13. return -1;
    14. }
    15. };

    复杂度分析

    时间复杂度:O(n),其中 n 是字符串 s 的长度。我们需要进行两次遍历。

    空间复杂度:O(∣Σ∣),其中 Σ 是字符集,在本题中 s 只包含小写字母,因此 ∣Σ∣≤26。我们需要 O(∣Σ∣) 的空间存储哈希映射。

    2,使用哈希表存储索引

    思路与算法

    我们可以对方法一进行修改,使得第二次遍历的对象从字符串变为哈希映射。

    具体地,对于哈希映射中的每一个键值对,键表示一个字符,值表示它的首次出现的索引(如果该字符只出现一次)或者 −1(如果该字符出现多次)。当我们第一次遍历字符串时,设当前遍历到的字符为 c,如果 c 不在哈希映射中,我们就将 cc 与它的索引作为一个键值对加入哈希映射中,否则我们将 c 在哈希映射中对应的值修改为 −1。

    在第一次遍历结束后,我们只需要再遍历一次哈希映射中的所有值,找出其中不为 -1 的最小值,即为第一个不重复字符的索引。如果哈希映射中的所有值均为 −1,我们就返回 −1。

    1. class Solution {
    2. public:
    3. int firstUniqChar(string s) {
    4. unordered_map<int, int> position;
    5. int n = s.size();
    6. for (int i = 0; i < n; ++i) {
    7. if (position.count(s[i])) {
    8. position[s[i]] = -1;
    9. }
    10. else {
    11. position[s[i]] = i;
    12. }
    13. }
    14. int first = n;
    15. for (auto [_, pos]: position) {
    16. if (pos != -1 && pos < first) {
    17. first = pos;
    18. }
    19. }
    20. if (first == n) {
    21. first = -1;
    22. }
    23. return first;
    24. }
    25. };

    复杂度分析

    时间复杂度:O(n),其中 n 是字符串 s 的长度。第一次遍历字符串的时间复杂度为 O(n),第二次遍历哈希映射的时间复杂度为 O(∣Σ∣),由于 ss 包含的字符种类数一定小于 s 的长度,因此 O(∣Σ∣) 在渐进意义下小于 O(n),可以忽略。

    空间复杂度:O(∣Σ∣),其中 Σ 是字符集,在本题中 s 只包含小写字母,因此 ∣Σ∣≤26。我们需要 O(∣Σ∣) 的空间存储哈希映射。

    3,队列

    思路与算法

    我们也可以借助队列找到第一个不重复的字符。队列具有「先进先出」的性质,因此很适合用来找出第一个满足某个条件的元素。

    具体地,我们使用与方法二相同的哈希映射,并且使用一个额外的队列,按照顺序存储每一个字符以及它们第一次出现的位置。当我们对字符串进行遍历时,设当前遍历到的字符为 c,如果 c 不在哈希映射中,我们就将 c 与它的索引作为一个二元组放入队尾,否则我们就需要检查队列中的元素是否都满足「只出现一次」的要求,即我们不断地根据哈希映射中存储的值(是否为 −1)选择弹出队首的元素,直到队首元素「真的」只出现了一次或者队列为空。

    在遍历完成后,如果队列为空,说明没有不重复的字符,返回 −1,否则队首的元素即为第一个不重复的字符以及其索引的二元组。

    小贴士

    在维护队列时,我们使用了「延迟删除」这一技巧。也就是说,即使队列中有一些字符出现了超过一次,但它只要不位于队首,那么就不会对答案造成影响,我们也就可以不用去删除它。只有当它前面的所有字符被移出队列,它成为队首时,我们才需要将它移除。

    1. class Solution {
    2. public:
    3. int firstUniqChar(string s) {
    4. unordered_map<char, int> position;
    5. queue<pair<char, int>> q;
    6. int n = s.size();
    7. for (int i = 0; i < n; ++i) {
    8. if (!position.count(s[i])) {
    9. position[s[i]] = i;
    10. q.emplace(s[i], i);
    11. }
    12. else {
    13. position[s[i]] = -1;
    14. while (!q.empty() && position[q.front().first] == -1) {
    15. q.pop();
    16. }
    17. }
    18. }
    19. return q.empty() ? -1 : q.front().second;
    20. }
    21. };

     

    复杂度分析

    时间复杂度:O(n),其中 n 是字符串 s 的长度。遍历字符串的时间复杂度为 O(n),而在遍历的过程中我们还维护了一个队列,由于每一个字符最多只会被放入和弹出队列最多各一次,因此维护队列的总时间复杂度为O(∣Σ∣),由于 s 包含的字符种类数一定小于 s 的长度,因此 O(∣Σ∣) 在渐进意义下小于 O(n),可以忽略。

    空间复杂度:O(∣Σ∣),其中Σ 是字符集,在本题中 s 只包含小写字母,因此∣Σ∣≤26。我们需要 O(∣Σ∣) 的空间存储哈希映射以及队列。

    二,赎金信

    383. 赎金信 - 力扣(LeetCode)icon-default.png?t=M7J4https://leetcode.cn/problems/ransom-note/

    1,字符统计 

    题目要求使用字符串magazine中的字符来构建新的字符串ransomNote,且ransomNote中的每个字符只能使用一次,只需要满足字符串magazine中的每个英文母('a'-'z')的统计次数都大于等于ransomNote中相同字母的统计次数即可。
    ●如果字符串magazine 的长度小于字符串ransomNote的长度,则我们可以肯定magazine无法构成ransomNote,此时直接返回false。
    ●首先统计magazine; 中每个英文字母a的次数cnt[a],再遍历统计ransomNote中每个英文字母
    的次数,如果发现ransomNote中存在某个英文字母c的统计次数大于magazir
    中该字母统计次数cnt[c],则此时我们直接返回false。
     

    1. class Solution {
    2. public:
    3. bool canConstruct(string ransomNote, string magazine) {
    4. if (ransomNote.size() > magazine.size()) {
    5. return false;
    6. }
    7. vector<int> cnt(26);
    8. for (auto & c : magazine) {
    9. cnt[c - 'a']++;
    10. }
    11. for (auto & c : ransomNote) {
    12. cnt[c - 'a']--;
    13. if (cnt[c - 'a'] < 0) {
    14. return false;
    15. }
    16. }
    17. return true;
    18. }
    19. };

    复杂度分析


    ●时间复杂度: O(m+n),其中m是字符串ransomNote的长度,n是字符串
    magazine的长度,我们只需要遍历两个字符一-次即可。
    ●空间复杂度: O(|S|), S是字符集,这道题中S为全部小写英语字母,因此|S|= 26。
     

    三,有效的字母异位词

    242. 有效的字母异位词 - 力扣(LeetCode)icon-default.png?t=M7J4https://leetcode.cn/problems/valid-anagram/?plan=data-structures&plan_progress=ggfacv7

    1,排序

    t 是 s 的异位词等价于「两个字符串排序后相等」。因此我们可以对字符串 s 和 t 分别排序,看排序后的字符串是否相等即可判断。此外,如果 s 和 t 的长度不同,t 必然不是 s 的异位词。

    1. class Solution {
    2. public:
    3. bool isAnagram(string s, string t) {
    4. if (s.length() != t.length()) {
    5. return false;
    6. }
    7. sort(s.begin(), s.end());
    8. sort(t.begin(), t.end());
    9. return s == t;
    10. }
    11. };

    复杂度分析


    ●时间复杂度: O(nlogn), 其中n为s的长度。排序的时间复杂度为O(n logn),比较两个字符串是否相等时间复杂度为O(n),因此总体时间复杂度为O(nlogn+ n) = O(n logn)。
    ●空间复杂度: O(logn)。 排序需要O(logn)的空间复杂度。注意,在某些语言(比如Java&JavaScript)中字符串是不可变的,因此我们需要额外的O(n)的空间来拷贝字符串。但是我们
    忽略这一复杂度分析,因为:
    。这依赖于语言的细节;
    。这取决于函数的设计方式,例如,可以将函数参数类型更改为char[] 。
     

    2,哈希表

    从另一个角度考虑,t 是 s 的异位词等价于「两个字符串中字符出现的种类和次数均相等」。由于字符串只包含 26 个小写字母,因此我们可以维护一个长度为 26 的频次数组 table,先遍历记录字符串 s 中字符出现的频次,然后遍历字符串 t,减去 table 中对应的频次,如果出现 table[i]<0,则说明 t 包含一个不在 s 中的额外字符,返回 false 即可。

    1. class Solution {
    2. public:
    3. bool isAnagram(string s, string t) {
    4. if (s.length() != t.length()) {
    5. return false;
    6. }
    7. vector<int> table(26, 0);
    8. for (auto& ch: s) {
    9. table[ch - 'a']++;
    10. }
    11. for (auto& ch: t) {
    12. table[ch - 'a']--;
    13. if (table[ch - 'a'] < 0) {
    14. return false;
    15. }
    16. }
    17. return true;
    18. }
    19. };

    对于进阶问题,Unicode 是为了解决传统字符编码的局限性而产生的方案,它为每个语言中的字符规定了一个唯一的二进制编码。而 Unicode 中可能存在一个字符对应多个字节的问题,为了让计算机知道多少字节表示一个字符,面向传输的编码方式的 UTF−8 和 UTF−16 也随之诞生逐渐广泛使用,具体相关的知识读者可以继续查阅相关资料拓展视野,这里不再展开。

    回到本题,进阶问题的核心点在于「字符是离散未知的」,因此我们用哈希表维护对应字符的频次即可。同时读者需要注意 Unicode 一个字符可能对应多个字节的问题,不同语言对于字符串读取处理的方式是不同的。

    复杂度分析

    • 时间复杂度:O(n)O(n),其中 n 为 s 的长度。

    • 空间复杂度:O(S),其中 S 为字符集大小,此处S=26。

    1. class Solution {
    2. public:
    3. bool isAnagram(string s, string t) {
    4. int freq[26] {};
    5. for (char ch : s) ++freq[ch - 'a'];
    6. for (char ch : t) --freq[ch - 'a'];
    7. return all_of(begin(freq), end(freq), [](int x) { return x == 0; });
    8. }
    9. };

  • 相关阅读:
    高企技术企业对企业的作用
    盘点Win前端开发下常用的软件
    linux 使用yum给已安装的软件降级
    安陆TD使用问题记录1---使用modelsim联合仿真
    【已解决】Vue项目中Vite以及Webpack代码混淆处理
    洛谷P1227 完美的对称
    Postman之CSV或JOSN文件实现数据驱动
    CSDN博客编写的相关操作
    程序员,阿里 P8java 大神讲的 Spring 大家族原理汇总,你确定不看?
    便捷高效的文件批量备份与清理!轻松管理您的文件存储!
  • 原文地址:https://blog.csdn.net/m0_63309778/article/details/126702424