寻找字符串中的第一个唯一字符(C++,哈希表,先遍历找符合条件后遍历检索)

问题

在这里插入图片描述

超时的初解:

太久没用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的结构化绑定

  • ③中的队列方法使用了emplace而非push,为了节省空间考虑

①使用哈希表存储频数:

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;
    }
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值