387. 字符串中的第一个唯一字符--初级算法

链接:387. 字符串中的第一个唯一字符 - 力扣(LeetCode)

题目描述

        给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1 。

示例 1:

输入: s = "leetcode"
输出: 0

示例 2:

输入: s = "loveleetcode"
输出: 2

示例 3:

输入: s = "aabb"
输出: -1

解题思路

方法一:数组存计数

         第一遍先统计每个字符出现的次数,第二遍再次从前往后遍历字符串s中的每个字符,如果某个字符出现一次直接返回。

实现

        采用一个用于计数的数组vec大小为英文字母个数26,将所有元素都初始化为0。和一个将字符串转为char类型的指针数组data用于遍历,统计字符时用data[i] - 'a'获取字母所在位置,然后将计数数组的对应位置加一。第二次遍历字符串,判断出现个数是否为1,是则返回下标 i ,否则继续遍历,若最后跳出循环也未找到唯一字符则返回0。

代码
class Solution {
public:
    int firstUniqChar(string s) {
        int vec[26]={0};
        // memset(vec,0,sizeof(26));
        char* data = s.data();//和const char* data = s.c_str效果一样
        // 先统计每个字符出现的次数
        for(int i=0;i<s.size();++i){
            vec[data[i]-'a']++;
        }
        // 然后在遍历字符串s中的字符,如果出现次数是1就直接返回
        for(int i=0;i<s.size();++i){
            if(vec[data[i]-'a']==1){
                return i;
            }
        }
        return -1;
    }
};
结果

注意

        在C/C++中,字符串是以null终止的字符数组。使用下标访问字符串s[i]是合法的,可以获取到第i个字符。然而,直接使用下标访问字符串可能会导致以下问题:

  1. 不可变性:字符串是不可变的,也就是说,不能直接通过s[i]的方式来修改字符串中的某个字符

  2. 内存越界

        当需要对字符串进行增删改查、赋值等操作时,将字符串转换为char型数组可以更方便地进行操作。可以直接访问和修改特定位置上的字符,而不受字符串不可变性的限制。此外,字符数组不带有终止符(‘\0’),允许在数组中存储任意字符,包括特殊字符,这在某些场景下是有用的。

方法二:哈希表存计数

        和上面的一样,先统计每个字符的数量,然后再查找。

实现

        用哈希表代替上述的数组实现同样的计数,哈希表的第一维存储字母,第二维存储计数

代码
class Solution {
public:
    int firstUniqChar(string s) {
        unordered_map<int,int> hash;//哈希表
        for(char ch:s){
            hash[ch]++;//计数加一
        }
        for(int i=0;i<s.size();++i){
            if(hash[s[i]]==1){//寻找只出现一次的字母
                return i;
            }
        }
        return -1;
    }
};
结果

        用时要比方法一多。

 方法三:哈希表存索引

        与方法二不同,本方法第一次遍历用哈希表存索引,使得第二次遍历不是查找字母本身而是哈希映射。

实现        

        具体地,利用哈希表的查询键的函数count判断所查字母是否第一次出现,若返回次数大于0则说明该字母不是第一次出现,则将对应值设为-1,若返回0,说明该字母是第一次出现,则将相应值设为所在字符串的索引。

         第二次再遍历哈希表的所有值,查找不为-1且最小的值,这个值即为索引。若最后遍历完哈希表还是没有找到,说明不存在唯一字母,返回-1 。要找最小索引,则初始设为最大索引。

代码
class Solution {
public:
    int firstUniqChar(string s) {
        unordered_map<int,int> hash;//哈希表
        for(int i=0;i<s.size();++i){
            if(hash.count(s[i])){   //count函数用于统计某个键的出现次数
                hash[s[i]]=-1;  //哈希表已经出现过相同字母,不是第一次出现
            }else{  //未找到相同字母,则将该字母作为键,索引作为值存入哈希表
                hash[s[i]]=i;
            }
        }
        int first=s.size();
        for(auto [_,pos]:hash){ //[_,pos]对哈希表的索引,只查值
            if(pos!=-1 && pos<first){   //找到只出现一次的字母且保证是第一次出现
                first=pos;
            }
        }
        if(first==s.size()){
            first=-1;   //没有找到只出现一次的字母         
        }
        return first;
    }
};
结果

        时间更长了

心得

        由于这里只需要对26个字母赋值,小数据量直接对数组赋初值比memset函数所用时间更短;如果数据量很大用memset函数比直接赋值要快一些。

        字符串转字符数组的两种方法:const char* data = s.data();和const char* data = s.c_str

        哈希表是键值对存储的,声明时用unordered_map<int,int> hash;其中键的类型可变,本题中将int改为char,运行时间变长了。

        哈希表的count函数用于统计某个键的出现次数,找不到则返回0

        哈希表遍历时可以只遍历值 for(auto [_,pos]:hash)

        找最小(大)值时,变量初始设为最大(小)值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值