找出给定字符串中第一个不重复的字符

给定一串字符串,找出其中第一个不重复的字符。
如:输入”abcddcaeb1~soop”,输出’e’

方法一

思路:
定义list<char> storelist<char> storeDel,对输入字符串str进行遍历,对str的每一个字符,分别在store和storeDel中查找,如果在store中存在该字符,则把store中的该字符删除,并存入storeDel中(如果storeDel中已经存在该字符,则不需要再存入),如果store和storeDel都没有该字符,则将该字符放进store中。
即用store存放字符串中只出现了一次的字符,而storeDel存储出现了多次的字符。那么当遍历完str后,store的第一个元素就是我们要找的字符,将之返回即可。

代码:

#include <iostream>
#include <map>
#include <algorithm>
#include <list>

using namespace std;
char findFirstCh(string str){
       list<char> store;//存储只出现一次的字符
       list<char> storeDel;//存储重复出现的字符
       for(unsigned int i = 0; i < str.size(); ++i){
               list<char>::iterator litor = find(store.begin(), store.end(), str[i]);//查找list里是否已经存在当前字符
               list<char>::iterator litorDel = find(storeDel.begin(), storeDel.end(), str[i]);//还要查找已经出现过的重复字符
               if(litor != store.end()){//如果存在,说明该字符在字符串里是重复的,把字符从list里删除
                       store.remove(str[i]);
                       if(litorDel == storeDel.end()){//记录重复的字符
                            storeDel.push_back(str[i]);
                       }
               }
               else{//list不存在该字符,当前字符暂时符合要求
                       if(litorDel == storeDel.end())//重复的字符里没有当前字符,符合要求,存入store                       
                            store.push_back(str[i]);               
               }       
       }

       char ch = -1;
       list<char>::iterator it = store.begin();
       if(it != store.end()){//如果list的第一个元素存在,就是我们要找的字符
               ch = *it;
               return ch;
       }
       return -1;//不存在,返回-1
}

int main() {
        string s = "bbccdfeesbsbdfh1h";
        string s1 = "ababcdefghscdfe";
        char ch = findFirstCh(s1);
        if(ch != -1)
            cout << ch <<endl;
        else
            cout << "not match character" << endl;
}

分析:
这种方法用vector也可以实现,但是注意到遍历字符串会发生push_back和remove的操作,vector是用数组实现的,在数组某个位置删除元素(除了最后一个元素),后面的元素都要往前移;而进行push_back操作,当vector预分配的大小不足时,会开辟一块更大的内存,将原来内存的元素复制到新的内存。
而list的底层是利用一个双向环形链表实现,对链表的某个元素进行删除,不会造成后续元素在内存中的移动,也不存在预分配大小不足的问题。

空间复杂度:
定义了两个list,list的大小随着字符串长度的增加也会相应地增加。

时间复杂度:
list是链表,随机查找某个元素的时间复杂度为O(n);同理,对list进行remove操作,也要先找到字符所在的位置,因此这个算法的时间复杂度为O(n^2)。

总结:
时间复杂度和空间复杂度都比较大,必须要遍历完整个字符串才能确定符合要求的字符。

方法二

思路:
遍历待查找字符串,对每个字符,分别查找字符串中是否还存在相同字符,如果不存在,则说明已经找到我们要查找的字符,可以返回;如果存在,说明不符合要求,继续对后面的字符进行相同操作。

代码:

#include <iostream>
#include <map>
#include <algorithm>
#include <list>

using namespace std;

char findFirstCh(string str){
    for(string::iterator iter = str.begin(); iter != str.end(); ++iter){
        char ch = *iter;
        string::iterator fitor1 = find(str.begin(), iter, ch);//查找字符串开头到当前字符中间是否存在和当前字符相同的字符(当前字符所在的位置不在查找范围之内)
        string::iterator fitor2 = find(iter+1, str.end(), ch);//从当前字符后一个位置开始到字符串结尾,查找是否存在和当前字符一样的字符,注意,str.end()并不是最后一个元素,而是最后一个元素后的一个位置
        if(fitor1 == iter && fitor2 == str.end()){//如果两次查找都失败,那么证明当前这个字符在整个串只出现一次,就是我们要找的字符,马上返回
            return ch;
        }   
    }
    return -1;
}

int main() {
    string s = "bbczcdfeesbs1bdfh1h";
    string s1 = "ababcdefghscdfeg";
    char ch = findFirstCh(s);
    if(ch != -1)
        cout << ch <<endl;
    else
        cout << "not match character" << endl;
}

分析:
这个方法,关键在于查找的时候,要把待查找字符串分成两部分,即进行两次find操作,如果对整个字符串进行查找,因为字符本身就属于字符串,返回的结果肯定是存在的。而find操作中两个迭代器参数,正好是半开半闭区间,即查找范围是[a,b),b不在查找范围内。所以这种方法中对字符串的遍历要使用迭代器,两次查找的分界正好就是指向当前字符串的迭代器。

空间复杂度:
只需要一个char记录返回值,O(1),与字符串长度无关。

时间复杂度:
最坏的情况下,要查找的字符位于字符串的最后一位,那么查找时间将是O(n^2)。

总结:
相比第一种方法,方法二不需要定义额外的数据结构,而且在循环中第一个符合要求的字符就是我们要查找的元素,可以马上返回,不需要对剩下的字符进行遍历,这也比第一种方法有了改进。当然,像上面所说,最坏的情况下,这种方法的时间复杂度还是达到了O(n^2)。

方法三

思路:
在遍历待查找字符串的情况下,对list或者string进行find操作,最坏情况下时间复杂度为O(n^2)。
考虑使用map,查到的资料提到,由于map底层用红黑树实现,查找的时间复杂度为O(logn)。因此可以定义map<char,int>,遍历字符串时在map查找当前字符,如果存在,将second置为0,如果不存在将second置为1。那么当遍历完字符串时,这个map里second为1的char就代表只出现了一次的字符。构造完map后,再次遍历字符串,利用字符查找map中对应second值(value值),第一个遇到value为1的字符就是要查找的字符。

代码:

#include <iostream>
#include <map>
#include <algorithm>
#include <list>

using namespace std;

char findFirstCh(string str){
    map<char,int> res;
    for(unsigned int i = 0; i < str.size(); ++i){
            map<char,int>::iterator itor = res.find(str[i]);
            if(itor != res.end()){//res中存在和当前字符相同的字符
                res[str[i]] = 0;//将对应的value置为0
            }
            else{//res中不存在和当前字符一样的字符
                res[str[i]] = 1;//将对应的value置为1
            }
    }//循环执行完毕后,second为1表示对应key在字符串中只出现一次,second为0的key表示在字符串中出现了多次


    for(unsigned int i = 0; i < str.size(); ++i){
        if(res[str[i]] == 1)
            return str;
    }

    return -1;
}

int main() {
        string s = "bbccdfeesbsbdfh1h";
        string s1 = "ababcdefghscdfe";
        char ch = findFirstCh(s1);
        if(ch != -1)
            cout << ch <<endl;
        else
            cout << "not match character" << endl;  
}

分析:
这个方法相比前两种方法,主要是在map中进行find操作,时间复杂度有所改进。
这里容易犯一个错误,就是在构造完map后,查找map的第一个second为1的key,将其作为结果返回。
其实map的存取顺序是不一致的,即当我们按自己的顺序把元素存放进map后,重新读取,map的元素已经不是我们存储的顺序了,而是按照key进行了排序。
比如把key为b和a的元素先后存入map,那么读取到map的第一个数据将会是key为a那个。所以如果查找map中第一个second为1的元素,是无法达到效果的。举个例子:”aabbcfec”,按照题目要求,应该输出’f’,如果遍历查找map中第一个second为1的元素,那么就会输出’e’。所以建立map后,这里要遍历的是待查找字符串,用每一个字符作为key去获取map中对应的value(即second)的值,第一个second为1的字符才是正确结果。

空间复杂度:
定义了map<char,int>结构,map占用内存和字符串包含不同字符个数相关,不同字符个数越多,map越大。

时间复杂度:
第一次遍历必须遍历完整个字符串,而每次进行查找时间复杂度为O(logn),因此第一个循环时间复杂度为O(nlogn)。第二个循环最坏情况下时间复杂度为O(n)。因此整个算法时间复杂度为O(nlogn+n)。

总结:
如果对map的存储方式不清楚,会误以为map中元素的存取数据是按照用户写入顺序存储,那么每次输出的将会是字符串中所有不重复字符里字典顺序最小的字符。

方法四

思路:
建立哈希表char hashTable[256],数组元素初始化为0,哈希函数

void hashFunc(char ch){
    hashTable[ch]++;//在哈希表的存储位置就是ch对应的ascii码,
}

构造完哈希表后,表中每个元素的值就代表数组下标对应的字符在字符串中出现的次数,再次遍历字符串,以每个字符对应的ascii码表作为下标访问哈希表对应的元素,第一次遇到元素值为1,即表示当前字符就是要查找的字符。

代码:

#include <iostream>
#include <map>
#include <algorithm>
#include <list>

using namespace std;

char findFirstCh(string str){
    char store[256] = {0};
    for(unsigned int i = 0; i < str.size(); ++i){
        store[str[i]]++;
    }

    for(unsigned int i = 0; i < str.size(); ++i){
        if(store[str[i]] == 1)
        return str[i];
    }

    return -1;
}

int main() {
        string s = "bbcc3fhghqgf3@op@qpnmmno";
        string s1 = "aba";
        char ch = findFirstCh(s);
        if(ch != -1)
            cout << ch << endl;
        else
            cout << "not match character" << endl;
}

分析:
其实这个所谓的哈希表就相当于ascii码表,只是它还多出了一个功能,记录每次字符出现的次数。
这里面会容易进入误区,就是建立完哈希表后,从哈希表开始遍历,输出遇到的第一个为1的元素对应的字符。因为题目要求是找出第一个不重复的字符,而哈希表中第一个为1的元素对应的字符不一定能就满足条件,只能保证是不重复的字符里字典顺序最小的字符。
这和第三种方法的易犯错误其实是一个道理。

空间复杂度:
数组char[256]作为哈希表,256是ascii码表的大小,与字符串长度无关,即空间复杂度为常量。

时间复杂度:
对字符串进行两次遍历操作,时间复杂度为O(n)。

总结:
这种方法其实和第三种方法大同小异,思路基本一致,只是第三种方法构造哈希表的过程交给map去做而已。当然,第四种方法在时间复杂度上无疑更有优势。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值