剑指offer(36):第一个只出现一次的字符

题目描述

在一个字符串(1<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符的位置。若为空串,返回-1。位置索引从0开始。

分析

直观思路:从头到尾扫描字符串,访问到某个字符是将其和后面的每个字符比较。如果没有发现重复字符,则只出现一次,即找到该字符。但是如果字符串有n个字符,则每个字符需要与后面的 O(n) 个字符比较,总的时间复杂度为 O(n2)

推荐思路:只进行一趟扫描,时间复杂度为 O(n) ,利用一个map容器存储字符及其出现的次数,另外一个map容器存储字符及其第一次出现的索引。扫描完成后,通过第一个map可以在 O(1) 时间内找到只出现1次的字符(利用LinkedHashMap,保持输出和输入顺序一致),接着可以找到该字符第一次出现的索引。总的时间复杂度为 O(n)

牛客AC:

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

public class Solution {
    public int FirstNotRepeatingChar(String str) {
        if(str == null || str.length() <= 0)
            return -1;

        // LinkedHashMap 保证输出顺序与输入顺序一致
        Map<Character, Integer> mapCount = new LinkedHashMap<Character, Integer>(); // 字符和对应的而次数
        Map<Character, Integer> mapIndex = new LinkedHashMap<Character, Integer>(); // 字符和首次出现的索引
        for(int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if(mapCount.containsKey(c)) {
                int count = mapCount.get(c) + 1;
                // mapCount.replace(c, count); // replace java1.8才开始支持
                mapCount.remove(c);
                mapCount.put(c, count);
            } else {
                mapCount.put(c, 1);
                mapIndex.put(c, i);
            }
        }

        char resChar = '0';
        int resIndex = -1;
        for(Entry<Character, Integer> entry : mapCount.entrySet()) {
            // 只出现一次的字符和首次出现的索引
            if(entry.getValue() == 1) {
                resChar = entry.getKey();
                resIndex = mapIndex.get(resChar);
                //System.out.println(resIndex);
                break;
            }
        }

        return resIndex; // 如需返回首次出现的索引,修改即可
    }
}

相关问题扩展

1、定义一个函数,输入两个字符串,从第一个字符串中删除在第二个字符串中出现过的所有字符。例如从第一个字符串“We are students”中删除在第二个字符串“aeiou”中出现过的字符得到“W r Stdnts”。
思路:使用HashMap存储第二个字符串,对第一个字符串进行一次扫描,每扫描到一个字符就判断map.containsKey(),如果出现,则删除该字符。

public String removeAppeared(String iniStr, String appearStr) {
    if(iniStr == null || iniStr.length() <= 0)
        return null;
    if(appearStr == null || appearStr.length() <= 0)
        return iniStr;

    // map存储第二个字符串
    Map<Character, String> map = new HashMap<Character, String>();
    for(int i = 0; i < appearStr.length(); i++) {
        char c = appearStr.charAt(i);
        map.put(c, null);
    }

    // 判断是否出现过
    StringBuffer sb = new StringBuffer();
    for(int i = 0; i < iniStr.length(); i++) {
        char c = iniStr.charAt(i);
        if(map.containsKey(c))
            continue;
        sb.append(c);
    }
    return sb.toString();
}

2、定义一个函数,删除字符串中所有重复出现的字符。例如输入“google”,删除重复字符之后的结果是“gole“。
思路:和上题类似,对字符串进行一趟扫描,每扫描到一个字符就判断map.containsKey(),如果出现,则删除该字符。

    public String removeRepeat(String iniStr) {
        if(iniStr == null || iniStr.length() <= 0)
            return null;

        Map<Character, Boolean> map = new HashMap<Character, Boolean>();
        StringBuffer sb = new StringBuffer();
        for(int i = 0; i < iniStr.length(); i++) {
            char c = iniStr.charAt(i);
            if(map.containsKey(c)) {    // 如果map包含key,则该字符一定出现过
                continue;
            } else {
                map.put(c, true);
                sb.append(c);
            }
        }
        return sb.toString();
    }

3、在英语中,如果两个单词中出现的字母相同,并且每个字母出现的次数也相同,那么这两个单词互为变位词(Anagram)。例如silent与listen、evil与live等。请完成一个函数,判断输入的两个字符串是不是互为变位词。
思路:使用HashMap存储字符串中每个字符出现的次数。对第一个字符串扫描一次,每扫描一个字符,相应的字符的次数增加1;对第二个字符串扫描一次,每扫描一个字符,相应的字符的次数减去1。如果扫描完第二个字符后,HashMap中所有的值都是0,那么这两个字符串就是互为变位词。

public boolean isAnagram(String str1, String str2) {
        if(str1 == null || str1.length() <= 0 ||
                str2 == null || str2.length() <=0 || str1.length() != str2.length())
            return false;

        boolean isAnagram = false; // 返回值

        // 遍历第一个字符串,存储字符及其次数
        Map<Character, Integer> map = new HashMap<Character, Integer>();
        for(int i = 0; i < str1.length(); i++) {
            char c = str1.charAt(i);
            if(map.containsKey(c)) {
                int count = map.get(c) + 1; // 每次加1
                map.remove(c);
                map.put(c, count);
            } else {
                map.put(c, 1);
            }
        }

        // 比那里第二个字符串,更新次数
        for(int i = 0; i < str2.length(); i++) {
            char c = str2.charAt(i);
            if(map.containsKey(c)) {
                int count = map.get(c) - 1; // 每次减1
                map.remove(c);
                map.put(c, count);
            } else {
                return isAnagram;
            }
        }

        // 判断所有的字符的次数是否为0
        for(Integer value : map.values()) {
            if (value != 0) 
                return isAnagram;
        }

        isAnagram = true;
        return isAnagram;
    }

参考
1. 何海涛,剑指offer名企面试官精讲典型编程题(纪念版),电子工业出版社

你可能感兴趣的:(出现一次的字符,剑指offer)