目录
题目描述
思路与算法
方法一:哈希表
HashSet说明
解题代码
方法二:位运算
左移运算符(<<)
右移运算符(>>)
解题代码
题目描述
给你一个由小写英文字母组成的字符串 s
,请你找出并返回第一个出现 两次 的字母。
注意
- 如果
a
的 第二次 出现比b
的 第二次 出现在字符串中的位置更靠前,则认为字母a
在字母b
之前出现两次。 s
包含至少一个出现两次的字母。
示例 1
输入:s = "abccbaacz" 输出:"c" 解释: 字母 'a' 在下标 0 、5 和 6 处出现。 字母 'b' 在下标 1 和 4 处出现。 字母 'c' 在下标 2 、3 和 7 处出现。 字母 'z' 在下标 8 处出现。 字母 'c' 是第一个出现两次的字母,因为在所有字母中,'c' 第二次出现的下标是最小的。示例 2
输入:s = "abcdd" 输出:"d" 解释: 只有字母 'd' 出现两次,所以返回 'd' 。提示
2 <= s.length <= 100
s
由小写英文字母组成s
包含至少一个重复字母
思路与算法
方法一:哈希表
我们可以使用一个哈希表记录每个字母是否出现过。
具体地,我们对字符串 s 进行一次遍历。当遍历到字母 c 时,如果哈希表中包含 c,我们返回 c 作为答案即可;否则,我们将 c 加入哈希表。
HashSet说明
HashSet 实现了Set接口
HashSet 实际上是HashMap
执行 Set set = new HashSet();
public HashSet() {
map = new HashMap<>();
}
可以存放null值,但是只能有一个null
HashSet不能保证元素的存取顺序一致
不能有重复的元素
HashSet线程不安全
没有带索引的方法,所以不能通过普通for循环进行遍历
解题代码
class Solution {
public char repeatedCharacter(String s) {
Set<Character> seen = new HashSet<Character>();
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (!seen.add(ch)) {
return ch;
}
}
// impossible
return ' ';
}
}
方法二:位运算
注意到字符集的大小为 26,因此我们可以使用一个 32 位的二进制数 seen 完美地存储哈希表。如果 seen 的第 i (0≤i<26) 位是 1,说明第 i 个小写字母已经出现过。
具体地,我们对字符串 s 进行一次遍历。当遍历到字母 c 时,记它是第 i 个字母,seen 的第 i (0≤i<26) 位是 1,我们返回 c 作为答案即可;否则,我们将 seen 的第 i 位置为 1。
左移运算符(<<)
右边空出的位用0填补
高位左移溢出则舍弃该高位
例如:10 的二进制为 0000 1010 ,那么10左移3为就是 0101 0000,结果就是80
10<<3 ==80
右移运算符(>>)
左边空出的位用0或者1填补。正数用0填补,负数用1填补。注:不同的环境填补方式可能不同;
低位右移溢出则舍弃该位
例如:20 的二进制为 0001 0100 ,那么10右移3为就是 0000 0010,结果就是2
20>>3 ==2
解题代码
class Solution {
public char repeatedCharacter(String s) {
int seen = 0;
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
int x = ch - 'a';
if ((seen & (1 << x)) != 0) {
return ch;
}
seen |= (1 << x);
}
// impossible
return ' ';
}
}
代码的关键步骤我们来解释一下, seen & (1 << x),首先 1 << x,为什么是 1 呢,1 的二进制是 001,也就代表从 a 开始左移,左移多少,由字符的决定,也就是如图的意思:
比如输入的字符是 “abcd”,循环到字符 a,seen 存储二进制 001,循环到字符 b,seen 存储二进制 011(|= 操作),依次类推。
& 操作的意思是,只有两个变量都是 1,值才为 1,如果某字符再次出现,比如字符 a 再次出现,则 001 & 011 的值为 001,值不为 0,则把字符 a 输出。
如何存储呢,我们用到 |,或运算任何一个变量为 1,则值为 1,比如 001 | 010 的值为 011,用于存储状态。