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

本文提供了一种高效的方法来解决剑指Offer50题目中的“第一个只出现一次的字符”。通过两次遍历字符串并利用HashMap记录字符出现次数,快速定位到目标字符。

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

387. 字符串中的第一个唯一字符

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

示例:

s = “abaccdeff”
返回 “b”


s = “”
返回 " "

限制:

  • 0 <= s 的长度 <= 50000

解题思路

直接遍历一遍字符串,用Map保存每个字符出现了多少次,再次遍历字符串,找到第一个只出现一次的字符即可。

Java代码

class Solution {
    public char firstUniqChar(String s) {
        //从头至尾遍历每个字符,用一个Map存好,key为字符,value为出现的次数
        if(s == null || s.length() == 0) return ' ';
        Map<Character,Integer> map = new HashMap<>();
        for(int i = 0;i < s.length();i++){
            map.merge(s.charAt(i),1,Integer::sum);//merge方法介绍在后面
        }
        for(int i = 0;i < s.length();i++){
            if(map.get(s.charAt(i)) == 1){
                return s.charAt(i);
            }
        }
        return ' ';
    }
}

在这里插入图片描述

merge方法介绍

处理映射时的一个难点就是更新映射项。正常情况下,可以很容易的得到一个键关联的原值,完成更新,再放回更新后的值。不过必须考虑一个特殊情况,即键第一次出现。比如类似上面的例子,我们需要使用一个映射统计一个单词在文件中出现的频度。看到一个单词(word)时,我们将计数器增1,学过C++的朋友肯定觉得太简单了,直接counts[word]++即可,因为counts中没有为word的键时,会自动添加该键。但Java中就没有那么方便了,Java中的做法如下所示:

counts.put(word,counts.get(word)+1);

这是可以的,不过有一种情况除外:就是word是第一次出现时。在该情况下,get会返回null,因此会出现一个NullPointerException异常。

避免该情况一个简单的做法是使用getOrDefault方法:

counts.put(word,counts.getOrDefault(word,0) + 1);

当然也可以使用putIfAbsent方法。

//如果传入的key已经有对应的value存在,返回存在的value,若不存在,则添加key和value,返回null
counts.putIfAbsent(word,0);
counts.put(word,counts.get(word) + 1);

但是还有更好的做法,merge方法可以简化这个常见的操作。

counts.merge(word,1,Integer::sum);

如果键原先不存在,将把word1关联,否则使用Integer.sum函数组合原值和1(也就是将原值与1求和)。

merge方法在下算法题和以及工作中还是很常用的,应该吃透。

下面贴一下Mapmerge方法的源码:

    default V merge(K key, V value,
            BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
        Objects.requireNonNull(remappingFunction);
        Objects.requireNonNull(value);
        V oldValue = get(key);
        V newValue = (oldValue == null) ? value :
                   remappingFunction.apply(oldValue, value);
        if(newValue == null) {
            remove(key);
        } else {
            put(key, newValue);
        }
        return newValue;
    }

这里的源码还是很简单的,基本不用解释了。

扩展题 LeetCode 387. 字符串中的第一个唯一字符

387. 字符串中的第一个唯一字符

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

示例 1:

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

示例 2:

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

示例 3:

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

提示:

  • 1 <= s.length <= 10^5
  • s 只包含小写字母

解题思路

与上题一模一样,唯一区别就是上题是返回字符,这题是返回对应的索引。

Go代码

func firstUniqChar(s string) int {
    // 第一轮遍历,用一个map记录每个字符出现的次数
    // 第二轮遍历,当前字符只出现过一次,则返回该字符对应的索引即可
    m := make(map[byte]int)
    for i := 0;i < len(s);i++ {
        m[s[i]]++
    }
    for i := 0;i < len(s);i++ {
        if m[s[i]] == 1 {
            return i
        }
    }

    return -1
}

在这里插入图片描述

在C语言中,找出第一个只出现一次字符主要有以下两种方法: ### 方法:双重循环遍历 通过双重循环遍历字符串,统计每个字符出现的次数,当某个字符的出现次数为1时,即为第一个只出现一次字符。若遍历完整个字符串都没有找到,则输出 "no"。 示例代码如下: ```c #include <stdio.h> #include <string.h> int main() { char arr[200] = { 0 }; fgets(arr, sizeof(arr), stdin); arr[strcspn(arr, "\n")] = '\0'; // 去除可能读取到的换行符 int found = 0; // 用于标记是否找到仅出现一次字符 for (int i = 0; arr[i] != '\0'; i++) { int count = 0; for (int j = 0; arr[j] != '\0'; j++) { if (arr[i] == arr[j]) { count++; } } if (count == 1) { printf("%c", arr[i]); found = 1; // 标记已找到 break; // 找到后退出循环 } } if (!found) { printf("no"); } return 0; } ``` 此代码首先使用 `fgets` 读取输入的字符串,并去除可能的换行符。然后通过双重循环统计每个字符的出现次数,若某个字符只出现一次,则输出该字符并标记已找到,最后若未找到则输出 "no" [^1]。 ### 方法二:使用哈希表 当字符数组比较大时,可使用哈希表来记录每个字符出现的个数。第遍扫描数组时,每碰到字符,在哈希表中找到对应的项并把出现的次数增加一次。第二遍扫描时,直接从哈希表中得到每个字符出现的次数,找到第一个出现次数为1的字符 [^3]。 示例代码如下: ```c #include <stdio.h> #include <string.h> #define MAX_CHARS 256 int main() { char s[100000]; int hash[MAX_CHARS] = {0}; fgets(s, sizeof(s), stdin); s[strcspn(s, "\n")] = '\0'; // 去除可能读取到的换行符 // 第遍扫描,统计每个字符出现的次数 for (int i = 0; s[i] != '\0'; i++) { hash[s[i]]++; } // 第二遍扫描,找到第一个只出现一次字符 for (int i = 0; s[i] != '\0'; i++) { if (hash[s[i]] == 1) { printf("%c", s[i]); return 0; } } printf("no"); return 0; } ``` 此代码使用个大小为 `256` 的数组作为哈希表,第遍扫描字符串统计每个字符的出现次数,第二遍扫描找到第一个出现次数为1的字符,若未找到则输出 "no"。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值