剑指 Offer 50. 第一个只出现一次的字符

问题

在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。

示例:
s = “abaccdeff”
返回 “b”
s = “”
返回 " "

限制:
0 <= s 的长度 <= 50000

分析
  1. 使用哈希表(HashMap)来存储
    要求的是:字符串中,第一个只出现一次的字符
    =>对字符串进行遍历,统计其中的各个字符出现的次数,最后确认第一个只出现一次的字符,即可。
    因为统计的时候,对容器的要求:既要确定该字符是什么,又要可以记录该字符出现的次数。并且,该容器的大小是不能确定的,最大为26(因为s只包含小写字母)。
    ==>想到了用集合来进行存储。
    在集合中,list类的,与数组比较相似,不能完全满足要求;set类的也不能满足要求;=>map类的可以满足要求。
    采用hashmap来作为容器,统计该字符串中每个元素出现的次数;

(1)时间复杂度:O(n)
=>在第一次扫描时,在哈希表中更新一个字符出现的次数的时间是O(1)。如果字符串的长度为n,那么,第一次扫描的时间复杂度是O(n)。
=>第二次扫描时,同样在O(1)时间内能读出一个字符出现的次数,所以时间复杂度仍是O(n)。
==>总的时间复杂度为:O(n)
(2)空间复杂度:O(1)
使用hashmap,构建起来的辅助数组的大小是一个常数,因此可以认为这种算法的空间复杂度为O(1)

  1. 使用“有序”哈希表(LinkedHashMap)来存储
    要求的是:字符串中,第一个只出现一次的字符
    =>仍旧采用集合来存储
    ==>只不过在存储某个字符(key)的出现次数时,对它的键值的设置处比较巧妙:
    如果该字符是第一次出现,则put(item,1);
    如果该字符不是第一次出现,则修改他的键值为-1:put(item,-1);
    =>因为在第一次遍历string的时候,对于其中的字符(以key来存储)的键值的设置比较巧妙,所以第二趟遍历的时候,可以直接遍历linkedhashmap的对象(理论上可以比直接遍历string要快一点)
    采用linkedhashmap的(map接口的)entrySet():返回所有的key-value构成的集合(用Set存储):
    其中,出现的第一个:item.value != -1 的item对应的key,
    即为:string中第一个只出现一次的字符。

原理:
LinkedHashMap:可以保证在遍历Map元素时,可以按照添加的顺序实现便利------>对于频繁的遍历操作,此类执行效果高于HashMap


1)时间复杂度:O(n)
=>在第一次扫描时,在哈希表中更新一个字符出现的次数的时间是O(1)。如果字符串的长度为n,那么,第一次扫描的时间复杂度是O(n)。
=>第二次扫描时,扫描的是建立起来的LinkedHashMap的对象,理论上会比直接遍历string得到结果的时间短。
==>总的时间复杂度为:O(n)
(2)空间复杂度:O(1)
使用linkedhashmap,构建起来的辅助数组的大小是一个常数,因此可以认为这种算法的空间复杂度为O(1)

  1. 用一维数组 来实现简单的“哈希表”
    实现的思路为:
    (1)string为空 的特殊情况
    =>如果该string为空,直接return ’ ';
    (2)string中包含只出现一次的字符
    =>对string进行遍历,在遍历的过程中,对于string中的元素,将该字符对应的map[]中的值,+1;
    =>对string进行第二遍遍历,如果某个字符c对应的的map[c-‘a’] == 1,表示该字符只出现了一次,return c;
    (3)string中不包含只出现一次的字符
    =>如果该string中不包含只出现一次的字符,return ’ ';

1)时间复杂度:O(n)
=>在第一次扫描时,在哈希表中更新一个字符出现的次数的时间是O(1)。如果字符串的长度为n,那么,第一次扫描的时间复杂度是O(n)。
=>第二次扫描时,同样在O(1)的时间内能读出一个字符出现的次数。
==>总的时间复杂度为:O(n)
(2)空间复杂度:O(1)
构建起来的辅助数组的大小是一个常数26*4bit=104bit,因此可以认为这种算法的空间复杂度为O(1)

  1. 用string类的方法来求解
实现

1、使用哈希表(HashMap)来存储

class Solution {
    public char firstUniqChar(String s) {
        //定义一个hashmap,来统计字符串中各个字符出现的次数
        Map<Character,Integer> map = new HashMap<Character,Integer>();
        char resChar = ' ';//因为如果没有 只出现一次的字符,要求返回一个但空格,所以将 一个但空格设置为resChar的初始值
        char item;//暂存从string中读取的字符
        //遍历字符串s
        for(int i=0;i < s.length();i++){
            item = s.charAt(i);
            //先判断map中是否包含这个item
            if(map.containsKey(item)){
                //直接将该元素对应的 键值+1
                map.put(item,map.get(item)+1);//更新该item对应的键值
            } else{
                //如果map中不包含这个item
                map.put(item, 1);
            }
        }

        //仍旧选择遍历string,[因为hashmap是无序的]
        //并且同时,依据string中对应的字符,判断该字符在map中对应的键值(出现次数)的大小,读到第一个键值为1的字符,就返回该字符
        for(int i=0;i < s.length();i++){
            item = s.charAt(i);
            if(map.get(item) == 1){
                //该字符对应的键值为1------该字符只出现了一次
                resChar = item;//将该字符的值,赋给最终要返回的变量
                //这也就意味着,如果没有找到只出现一次的字符,该resChar最终的值,仍旧是' '(返回的是 空格符)
                break;
            }
        }
        return resChar;
    }
}

2、使用“有序”哈希表(LinkedHashMap)来存储

class Solution {
    public char firstUniqChar(String s) {
        //定义一个linkedhashmap,来统计字符串中各个字符出现的次数
        Map<Character,Integer> map = new LinkedHashMap<>();
        char resChar = ' ';//因为如果没有 只出现一次的字符,要求返回一个但空格,所以将 一个但空格设置为resChar的初始值
        //遍历字符串s
        //暂存从string中读取的字符
        char c;
        for(int i=0;i < s.length();i++){
            c = s.charAt(i);
            //先判断map中是否包含这个item
            if(map.containsKey(c)){
                //直接将该元素对应的 键值+1
                map.put(c,-1);//更新该item对应的键值
            } else{
                //如果map中不包含这个item
                map.put(c, 1);
            }
        }
        //因为linkedhashmap是“有序的”----可以按照添加顺序实现遍历,所以可以直接在map中进行查找
        //查找第一个value值为1的字符,就是所要求的 第一个只出现一次的字符
        //但是,要对linkedhashmap进行遍历的话,需要使用map接口的entrySet()得到一个由所有的key-value对构成的集合(set集合)
        for(Map.Entry<Character,Integer> entry : map.entrySet()){
            if(entry.getValue() == 1){
                resChar = entry.getKey();
                break;
            }
        } 
        return resChar;
    }
}

3、用一维数组 来实现简单的“哈希表”

class Solution {
    public char firstUniqChar(String s) {
        //string为空的情况
        if(s == ""){
            return ' ';
        }

        //string不为空的情况
        //用数组来实现 简单的 哈希表
        int[] map = new int[26];
        char c;//暂存string中的字符
        //因为只有小写字母的可能,所以需要的辅助空间为26
        //数组元素,可以默认初始化,不用手动置0
        //对string的第一遍 遍历
        for (int i=0;i<s.length();i++) {
            c = s.charAt(i);
            //
            map[c - 'a']++;
        }
        //对string的第二遍 遍历
        for (int i=0;i<s.length();i++) {
            c = s.charAt(i);
            if(map[c-'a']==1){
                return c;
            }
        }
        //该string中没有:只出现一次的字符
        return ' ';
    }
}

==>可以用数组,来实现简单的 “哈希表”。

4、用string类的方法来求解

class Solution {
    public char firstUniqChar(String s) {
        if(s.length() == 0) return ' ';
        int index = s.length();
        for(char c = 'a'; c <= 'z'; c++) {
            int ci = s.indexOf(c);
            if(ci >= 0 && ci < index && ci == s.lastIndexOf(c)) {
                index = ci;
            }
        }
        return index == s.length() ? ' ' : s.charAt(index);
    }
}

换个心情学习,换个状态提升自己!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值