【LeetCode每日一题】【2023/1/1】2351. 第一个出现两次的字母


2351. 第一个出现两次的字母

LeetCode: 2351. 第一个出现两次的字母

简单 \color{#00AF9B}{简单} 简单

给你一个由小写英文字母组成的字符串 s ,请你找出并返回第一个出现 两次 的字母。

注意:

  • 如果 a第二次 出现比 b第二次 出现在字符串中的位置更靠前,则认为字母 a 在字母 b 之前出现两次。
  • s 包含至少一个出现两次的字母。

示例 1:

输入:s = "abccbaacz"
输出:"c"
解释:
字母 'a' 在下标 056 处出现。
字母 'b' 在下标 14 处出现。
字母 'c' 在下标 237 处出现。
字母 'z' 在下标 8 处出现。
字母 'c' 是第一个出现两次的字母,因为在所有字母中,'c' 第二次出现的下标是最小的。

示例 2:

输入:s = "abcdd"
输出:"d"
解释:
只有字母 'd' 出现两次,所以返回 'd'

提示:

  • 2 <= s.length <= 100
  • s 由小写英文字母组成
  • s 包含至少一个重复字母

方法1:哈希表

我们可以将每一个字符映射到哈希表中。初始状态下,每一个字符键所对应的值都为 false

开始遍历字符串中的字符。每次遍历到后,将该字符对应的哈希表值设为 true

如何来确定某个字符已经是第二次出现了呢?实际上,我们需要在设置 true 之前,对哈希表值进行判断。如果在设置之前已为 true ,则代表在之前的循环中,这个字符已经出现了,已经把对应位置设置为 true 了,所以这次遍历我们才能得到一个真值。这种情况下,该字符就是第二次出现了。

我们也可以将哈希表的记录类型设置为整型,以记录字符的出现次数。当某字符的计数为 2 时,就代表该字符第二次出现。

(没有那么重要)
需要注意的是:由于题目所给条件中指明,每个字符串都至少包含一个重复字符。也就是说,我们在循环的过程中一定能够找到目标字符,这时我们就可以直接返回结果了。但是为了确保函数的完整,我们在循环外仍然需要“虚空”返回一个值,确保函数的“每一条路”都能够返回 char 类型的值。
在下面的例子中,便是最后的那句 “return 0;”

下面是布尔类型的哈希表实现。

class Solution {
public:
    char repeatedCharacter(string s) {
        bool hashmap[26] {false};
        for (const char& c : s)
        {
            int code = c - 'a';
            if (hashmap[code] == true)
                return c;
            hashmap[code] = true;
        }
        return 0;
    }
};

复杂度分析

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

  • 空间复杂度: O ( ∣ Σ ∣ ) O(|Σ|) O(∣Σ∣)。|Σ|为 字符集 的大小,本题中为26长的小写英文字母集。

参考结果

Accepted
92/92 cases passed (0 ms)
Your runtime beats 100.00 % of cpp submissions
Your memory usage beats 90.15 % of cpp submissions (5.9 MB)

方法2:位图(哈希表)<int类型实现>

在方法1中,哈希表的值类型是 bool (或 int 之类)。实际上,对于本题的条件,对于一个字符要记录的东西只有“它出现过”和“它没有出现过”。这是一个只有2种结果的状态。那么计算机中,1位二进制的 01 便可以存储,何必使用占据 8 位的 bool 类型或 32 位的 int 类型呢?

因此,我们可以选择将一个字符映射到一个bit上。对于本题中给出的字符集,其为26个小写英文字母。也就是说我们实现本题的要求至少需要 26位 的空间去做哈希表。自然而然,C++中能够存放至少26位的最佳选择,那么就是占据 32 位空间的 int 类型变量了。

我们可以用一个 int 类型变量的最低位,也就是第 0 位,去存放字符 ‘a’ 的状态。当第0位为二进制 0 时,该字符还未出现过;当其为二进制 1 时,则该字符已经出现过。

那么如何去实现从 字符 的映射呢?首先我们要知道某个字符在字符集中的顺序索引,例如字符 ‘a’ 映射到第 0 位、字符 ‘b’ 映射到第 1 位。这个数值可以通过 c - 'a' 来得到。

接下来就是如何在位图中定位。我们可以通过移位操作和操作来获取某字符键所对应的哈希表值。例如现在有一个无符号整型变量 bitmap ,并且我们需要知道字符 ‘a’ 的状态。那我们可以将变量 bitmap01H0000 0001B)作与操作,将高位的31位全部置1,将第0位保留。如果这个结果是 0 ,则代表字符 ‘a’ 对应的哈希表值是 0 ,也就是它还未出现过;如果结果不是 0 ,则代表字符 ‘a’ 对应的哈希表值为 1 ,也就是它已经出现过了。

同样地,如果要获取字符 ‘b’ 的状态,那么将 bitmap02H0000 0010B)作与操作即可。而字符与哈希表之间映射的表达式就为:

1 << (c - 'a')

即将整型的 0000 0001H (32位)向左移 c - 'a' 位,即可得到该字符对应的哈希表索引。

#include <cstdint>

class Solution {
public:
    char repeatedCharacter(string s) {
        uint32_t bitmap = 0;
        for (const char& c : s)
        {
            uint32_t position = 1 << (c - 'a');
            if ((bitmap & position) != 0)
                return c;
            bitmap |= position;
        }
        return 0;
    }
};

注意:

  • 上述代码中包含了头文件 cstdint 是为了使用类型 uint32_t 。这个类型实际上就是 unsigned int ,可以在 stdint.h 文件中看到其定义:
    typedef unsigned int uint32_t;
    而使用 uint32_t 这个写法只是为了说清该变量占据 32 位。除此之外没有什么特别的含义。
  • 位运算符 的优先级低于 逻辑运算符 。所以在 if 语句的判断条件中千万不要写成这样:
    bitmap & position != 0
    它的运算实际上等同于 bitmap & (position != 0)
  • 从上面的思路其实可以看出, 位图 实际上就是一种 哈希表

复杂度分析(同方法1)

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

  • 空间复杂度: O ( ∣ Σ ∣ ) O(|Σ|) O(∣Σ∣)。|Σ|为 字符集 的大小,本题中为26长的小写英文字母集。


方法3:位图(哈希表)<bitset类型实现>

也许很多人可能不太喜欢去思考如何实现从 元素位图索引 的映射。

假设我们的字符集不是只有26个字母的小写英文字母集合,而是一个更大的,例如100个字符的集合。我们不好找一个变量去一次性覆盖100位,也没有办法把 0000 0001H 向左移99位。这种情况下或许我们会想到用一个存放 char 类型(8位)的数组去存放位图。这样这个数组就有 ⌈ 100 / 8 ⌉ = 13 的长度。然后我们再在这个条件下去实现映射(哈希函数)。

可能很多人都觉得这样去思考实在有点复杂。如果位图能像数组一样用下标去访问就会简单许多。想要访问第99位的话,我就直接传99这个数字进去。这里就可以用到C++标准库中提供的 bitset 类型了。

该类型可以通过包含头文件 bitset 使用。其具体的说明详见cppreference/bitset

不妨先来看下代码:

#include <bitset>

class Solution {
public:
    char repeatedCharacter(string s) {
        std::bitset<26> hashmap {0};
        for (const auto& c: s)
        {
            if (hashmap[c - 'a'] == true)
                return c;
            hashmap[c - 'a'] = true;
        }
        return 0;
    }
};

代码中,我们首先定义了一个 26 定长的bitset。在随后的遍历中我们可以看到,我们就是把 c - 'a' ,也就是 c 是第几个字符,像访问数组一样作为下标去访问。我们也没有去实现特别的映射关系。

如果不使用 []运算符重载 的话,也可以用其它bitset提供的成员函数去实现。效果上是一样的。

#include <bitset>

class Solution {
public:
    char repeatedCharacter(string s) {
        std::bitset<26> hashmap {0};
        for (const auto& c: s)
        {
            if (hashmap.test(c - 'a'))
                return c;
            hashmap.set(c - 'a');
        }
        return 0;
    }
};

复杂度分析(同方法1)

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

  • 空间复杂度: O ( ∣ Σ ∣ ) O(|Σ|) O(∣Σ∣)。|Σ|为 字符集 的大小,本题中为26长的小写英文字母集。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

亡心灵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值