每日一题《剑指offer》字符串篇之字符流中第一个不重复的字符

2023-11-23 09:53:42 浏览数 (1)

今日题目链接:字符流中第一个不重复的字符

字符流中第一个不重复的字符

难度:中等

描述

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符 "go" 时,第一个只出现一次的字符是 "g" 。当从该字符流中读出前六个字符 “google" 时,第一个只出现一次的字符是"l"。

数据范围

数据范围:字符串长度满足 1≤n≤1000  ,字符串中出现的字符一定在 ASCII 码内。 进阶:空间复杂度O(n)  ,时间复杂度O(n)

举例

解题思路

方法一:哈希表 字符串;又是一个找到是否重复的问题。我们还是可以用哈希表来记录各个字符出现的次数,根据这样只要是字符串最前面且哈希表中次数为1的字符就是我们要找的。

具体做法:

  • step 1:准备一个字符串来记录输入的字符流,用哈希表统计每个字符的次数,二者都是全局变量。
  • step 2:在Insert函数中对输入的字符,加到字符串最后,然后统计出现次数。
  • step 3:在FirstAppearingOnce函数遍历该字符串,对于每个字符查找哈希表,返回第一个计数为1的字符,如果遍历完字符串以后都没,则返回#。

方法二:哈希表 队列;除了使用字符串记录字符流,还可以用队列记录字符流,每次插入的时候,只需要将第一次出现的字符加入到队列中,然后正常计数。

查找第一个不重复出现的字符的时候,从队首开始查询哈希表,如果出现次数为1,则返回该字符,如果不为1,则从队首将其弹出,因为反正后续也不可能是这个已经重复的字符了。

具体做法:

  • step 1:准备一个队列来记录输入的字符流,用哈希表统计每个字符的次数,二者都是全局变量。
  • step 2:在Insert函数中对输入的字符,加到队列最后,然后统计出现次数。
  • step 3:在FirstAppearingOnce函数中,不断检查队首元素直到队列为空,队首出现次数为1次,就返回,队首出现次数不为1就弹出。

实现代码(java)

方法一:

代码语言:javascript复制
import java.util.*;
public class Solution {
    private StringBuilder s = new StringBuilder();
    private HashMap<Character, Integer> mp = new HashMap<>();
    //Insert one char from stringstream
    public void Insert(char ch)
    {
        //插入字符
        s.append(ch);  
        //哈希表记录字符出现次数
        mp.put(ch, mp.getOrDefault(ch, 0)   1);
    }
    //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        //遍历字符串
        for(int i = 0; i < s.length(); i  ) 
            //找到第一个出现次数为1的
            if(mp.get(s.charAt(i)) == 1)
                return s.charAt(i);
        //没有找到
        return '#'; 
    }
}

方法二:

代码语言:javascript复制
import java.util.*;
public class Solution {
    private Queue<Character> q = new LinkedList<>();
    private HashMap<Character, Integer> mp = new HashMap<>();
    //Insert one char from stringstream
    public void Insert(char ch)
    {
        //插入字符
        if(!mp.containsKey(ch))
            q.offer(ch);
        //哈希表记录字符出现次数
        mp.put(ch, mp.getOrDefault(ch, 0)   1);
    }
    //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        while(!q.isEmpty()){
            //第一个不重复的字符
            if(mp.get(q.peek()) == 1) 
                return q.peek();
            //弹出前面的已经重复的字符
            else 
                q.poll();
        }
        //都重复了
        return '#'; 
    }
}

学习完本题的思路你可以解决如下题目: JZ39. 数组中出现次数超过一半的数字

JZ56. 数组中只出现一次的两个数字

数组中出现次数超过一半的数字

难度:中等

描述

给一个长度为 n 的数组,数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

例如输入一个长度为9的数组[1,2,3,2,2,2,5,4,2]。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。

数据范围

n≤50000,数组中元素的值 0≤val≤10000

要求:空间复杂度:O(1),时间复杂度O(n)

举例

解题思路

方法一:主要分三步:

  1. 判断给定的array长度是否为零,为零则没有这样符合条件的数字,直接return 0
  2. 我们创建一个hashmap,然后遍历这个array,hashmap的key是array里的不同数字,value是这些数字出现在array里的次数
  3. 我们遍历hashmap,检测value(即当前数字出现的次数)是否大于数组array长度的一半,如果有这个数字,我们return回key(即当前遍历到的数字),如果我们走完了整个hashmap还没有发现这样一个数字,我们需要return 0

实现代码(java)

方法一:

代码语言:javascript复制
import java.util.*;

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        if(array.length == 0){
            return 0;
        }

        int len = array.length;
        int threshold = len/2;
        Map<Integer,Integer> map = new HashMap<>();
        for(int i = 0; i < len; i  ){
            if(!map.keySet().contains(array[i])){
                map.put(array[i],1);
            }else{
                map.put(array[i],map.get(array[i]) 1);
            }
        }

        for(Integer key: map.keySet()){
            if(map.get(key) > threshold){
                return key;
            }
        }

        return 0;
    }
}

数组中只出现一次的两个数字

难度:中等

描述

一个整型数组里除了两个数字只出现一次,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

数据范围

数组长度 2≤n≤1000,数组中每个数的大小 0<val≤1000000

要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)

举例

解题思路

用异或^可解此题。

但是首先要知道一个知识点,a^b^a = a^a^b = b^a^a =b,这个知识点也就是本题的简单版本:如果数组中除了某一个数字,其他数字都出现了两次,找出该数字。思路就是遍历数组,对每一个数字都求异或,最后得到的值就是要找的数字。

有了该知识点的储备,再来看看本题。本题是要找两个数字a和b,那我们把该数组分成两个数组,其中a和一部分出现两次的数字在一块儿,b和另一部分出现两次的数字在一块儿,那这两个数组不是就变成了上面讲的那个简单版本中的数组吗?然后再分别对这两个数组求异或,就可以找到这两个数字了。

举例:[a,2,2,3,3,b]。把该数组分成[a,2,2]和[3,3,b],再对这两个数组求异或,便能得到a和b。

问题:怎么把a和b区分开来? 可以利用二进制来区分。先对整个数组求异或得到c,根据上面的知识,可以知道c其实就是a^b=c。那么对于c,假如c二进制的第一位是1,其实就代表a二进制的第一位是1(或0),b二进制的第一位是0(或1),总而言之如果第一位的c等于1,那么a和b在第一位肯定不相等。

所以我们就可以想到利用二进制的第一位(有可能是第二位,第三位 。。。因为上面是假设的c第一位是1)为1来区分两个数组,第一位为1的是数组一,第一位为0的是数组二。这样就相当于把a和b给区分开来了。

a和b区分开以后,剩下的就简单了,判断数组中其他数字的二进制的第一位是否为1,是的话就分到数组一,为0就分到数组二。最后对数组一和数组二分别进行异或,得到的就是a和b。

有个地方没有讲清楚,利用二进制的第一位为1来区分两个数组,如果第一位不是1,那就判断第二位,第三位,一直到找到为1的地方。假设一直找到第n位才为1,那就判断数组中的其他数字的二进制的第n位是否为1,做&运算即可判断。

实现代码(java)

代码语言:javascript复制
import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param array int整型一维数组 
     * @return int整型一维数组
     */
    public int[] FindNumsAppearOnce (int[] array) {

        /**
         * 异或的运算方法是一个二进制运算:
         *   1^1=0
         *   0^0=0
         *   1^0=1
         *   0^1=1
         */
        int res = 0;

        //对整个数组求异或的结果
        for (int i = 0; i < array.length; i  ) {
            res ^= array[i];
        }

        int temp = 1;

        //判断异或结果的二进制第一位是否为1,为1则直接跳过该循环
        while ((temp & res) == 0) {
            //为0则继续往前找,一直到找到为1的二进制位
            temp <<= 1;
        }

        int lastNumIs1 = 0;

        int lastNumIs0 = 0;

        for (int j = 0; j < array.length; j  ) {

            //如果该数字二进制的第某位为0,则分到数组一
            if ((array[j] & temp) == 0) {
                lastNumIs1 ^= array[j];
            } else {
                //如果该数字二进制的第某位为1,则分到数组二 
                lastNumIs0 ^= array[j];
            }
        }

        return lastNumIs1 < lastNumIs0 ? new int[]{lastNumIs1,lastNumIs0} : new int[]{lastNumIs0,lastNumIs1};
    }
}

0 人点赞