问题
超时的初解:
太久没用string了,忘记了相关的功能,直接设置了一个计数flag,在遍历不相等时加一,当计数等于n-1时说明只有自身,此时返回该索引。如此的时间复杂度为O(n^2)空间复杂度为O(1)。后面学习一下正派解法。
class Solution {
public:
int firstUniqChar(string s) {
int n = s.size();
int flagN=0;
for (int i=0; i<n; ++i){
flagN=0;
for (int j=0; j<n; ++j){
if (s[i] != s[j])
flagN++;
}
if (flagN==n-1)
return i;
}
return -1;
}
};
对官方解法的思考
看到了官方的解,第一次遍历使用哈希表映射统计每次出现的次数,第二次遍历哈希表返回第一个为1的字符,返回其索引。个人感觉不如数组简洁高效
谈一谈收获:
-
看到官方哈希表的声明
unordered_map<int, int> frequency;
key竟然是int类型而不是char类型的,试着跑了一下,还能跑通。难道char和int有某种羁绊?char通常为1字节,int通常为4字节,char可能有符合也可能无符号,int必然有符号。这里应该是自动转换了吧,测试了一下使用int和char效果差不多。int string char 的相互转化 -
另一个值得学习的是简洁的for循环用法
for (char ch: s)
for循环的范围迭代 -
执行语句中简洁的自加
++frequency[ch];
-
对于②中,直接存储索引,核心是判断
if (position.count(s[i]))
。count(key) 函数在容器中查找以 key 键的键值对的个数。只要有了就索引变成-1,没有就加上。 -
另一个学到的东西是for循环迭代时,不获取key只获取value:
for (auto [_, pos]: position)
,这里使用的是C++17的结构化绑定
①使用哈希表存储频数:
class Solution {
public:
int firstUniqChar(string s) {
unordered_map<int, int> frequency;
for (char ch: s) {
++frequency[ch];
}
for (int i = 0; i < s.size(); ++i) {
if (frequency[s[i]] == 1) {
return i;
}
}
return -1;
}
};
②使用哈希表存储索引
class Solution {
public:
int firstUniqChar(string s) {
unordered_map<int, int> position;
int n = s.size();
for (int i = 0; i < n; ++i) {
if (position.count(s[i])) {
position[s[i]] = -1;
}
else {
position[s[i]] = i;
}
}
int first = n;
for (auto [_, pos]: position) {
if (pos != -1 && pos < first) {
first = pos;
}
}
if (first == n) {
first = -1;
}
return first;
}
};
③队列
class Solution {
public:
int firstUniqChar(string s) {
unordered_map<char, int> position;
queue<pair<char, int>> q;
int n = s.size();
for (int i = 0; i < n; ++i) {
if (!position.count(s[i])) {
position[s[i]] = i;
q.emplace(s[i], i);
}
else {
position[s[i]] = -1;
while (!q.empty() && position[q.front().first] == -1) {
q.pop();
}
}
}
return q.empty() ? -1 : q.front().second;
}
};
学习后的新思路:
因为只有26个小写字母,这里使用int* abc = new int[26]{0};
来存储索引
- 其中数组指针的索引为小写字母
-'a'
后的int值 - 数组指向的值为待求字符串的索引+1(因为数组初始化为全0,与第1个索引0冲突)
①首先遍历字符串将有效索引+1存入数组,已经非0的数组值说明已经存入了索引,这里置-1(后续用>0将其过滤)
②遍历数组,>0的有效索引值-1后与上一次的有效索引值resMin比较,保留最小的索引
③返回索引,如果没有索引(resMin == INT_MAX
)返回-1,有则返回索引
此外,注意一些小细节:
int abc[26] = {0};
代替int* abc = new int[26]{0};
需要注意的是,{0}是全为0,{-1}仅第一个元素为-1- 大小比较可以直接
resMin = resMin < (abc[i]-1) ? resMin:(abc[i]-1)
来代替使用函数resMin = min(abc[i]-1,resMin);
class Solution {
public:
int firstUniqChar(string s) {
//int* abc = new int[26]{0};//只可以{0} 其他的不好使
int abc[26] = {0};
for (int i=0; i<s.size(); ++i){
if (abc[s[i]-'a'] == 0){
abc[s[i]-'a'] = i+1;//i+1防止索引0的无效,后面运算要-1
}
else {
abc[s[i]-'a'] = -1;
}
}
int resMin=INT_MAX;
for (int i=0; i<26; ++i){
if (abc[i]>0){ //有真正索引的值
// resMin = min(abc[i]-1,resMin);//收集最小的索引
resMin = resMin < (abc[i]-1) ? resMin:(abc[i]-1);
}
}
if (resMin == INT_MAX)
return -1;
else
return resMin;
}
};