今日题目链接:字符流中第一个不重复的字符
字符流中第一个不重复的字符
难度:中等
描述
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符 "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)
方法一:
java
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 '#';
}
}
方法二:
java
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. 数组中出现次数超过一半的数字
数组中出现次数超过一半的数字
难度:中等
描述
给一个长度为 n 的数组,数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
例如输入一个长度为9的数组[1,2,3,2,2,2,5,4,2]。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。
数据范围
n≤50000,数组中元素的值 0≤val≤10000
要求:空间复杂度:O(1),时间复杂度O(n)
举例
解题思路
方法一:主要分三步:
- 判断给定的array长度是否为零,为零则没有这样符合条件的数字,直接return 0
- 我们创建一个hashmap,然后遍历这个array,hashmap的key是array里的不同数字,value是这些数字出现在array里的次数
- 我们遍历hashmap,检测value(即当前数字出现的次数)是否大于数组array长度的一半,如果有这个数字,我们return回key(即当前遍历到的数字),如果我们走完了整个hashmap还没有发现这样一个数字,我们需要return 0
实现代码(java)
方法一:
java
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)
java
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};
}
}