面试题50:第一个只出现一次的字符
在一个字符串(0 <= 字符串长度 <= 10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置,如果没有则返回-1
方法一:
将字符串从第一个字符开始往后扫描字符串中的每个字符,若后面的字符串中没有发现重复的字符,那么这个字符就是只出现一次的字符
但该种方法的时间复杂度为O(n^2),因此要想一种更加高效的方法,使用一种容器来装载每个字符元素出现的次数
方法二:
那么就想到使用HashMap来解决这个问题,因为HashMap就可以将一个字符串映射成一个数字
核心主要思路:判断HashMap中是否包含某元素,
若包含该元素,将该元素插入到哈希表中,并取出上一个包含元素对应的value值,并加1
若不包含该元素,将该元素直接插入到哈希表中,并直接将其对应的value值赋为1
最后再循环判断并返回哈希表中value值等于1(即第一次出现一次的元素)的下标
下面为该方法中使用的重要方法:
//根据Key值判断哈希表中是否包含某元素的方法
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
//根据Key值来获取对应的value值的方法
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
下面为实现完整代码
package FirstNotRepeatingChar;
import java.util.HashMap;
/**
* @author LXY
* @email [email protected]
* @date 2018/7/25 16:13
*
* 在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,
* 并返回它的位置,
* 如果没有则返回 -1(需要区分大小写).
* 使用HashMap
*/
public class Solution {
public int FirstNotRepeatingChar(String str) {
HashMap<Character,Integer> map = new HashMap<>();
if(str == null)
{
return -1;
}
for(int i = 0;i < str.length();i++)
{
if(map.containsKey(str.charAt(i)))
{
//包含重复元素
int value = map.get(str.charAt(i));//该方法是得到元素对应映射的值
map.put(str.charAt(i),value + 1);
}
else
{
map.put(str.charAt(i),1);
}
}
for(int i = 0;i < str.length();i++)
{
if(map.get(str.charAt(i)) == 1)
{
return i;
}
}
return -1;
}
public static void main(String[] args) {
Solution solution = new Solution();
System.out.println(solution.FirstNotRepeatingChar("google"));
}
}
方法三:
使用数组来装载每个元素出现的次数
创建一个数组count[]来存放元素出现的次数,特别的是下标传入的是每个元素的ASCII值,这样就能保证相同的元素出现的次数会进行累加
下面为该方法完整实现代码:
package FirstNotRepeatingChar;
/**
* @author LXY
* @email [email protected]
* @date 2018/7/27 18:39
* 在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,
* 并返回它的位置,
* 如果没有则返回 -1(需要区分大小写).
* 使用数组来保存
*/
public class SolutionByArray {
public int FirstNotRepeatingChar(String str) {
char[] charStr = str.toCharArray();
int[] count = new int[256];//256表示ASCII值
for(int i = 0;i < charStr.length;i++)
{
//charStr[i]表示循环遍历每个元素,
//count[charStr[i]]表示对应出每个元素的ASCII值为count数组的下标
//++表示每向下遍历一个元素就一定会在该元素对应的count数组中加一
count[charStr[i]]++;
}
for(int i = 0;i < count.length;i++)
{
if(count[charStr[i]] == 1)
{
return i;
}
}
return -1;
}
public static void main(String[] args) {
SolutionByArray solutionByArray = new SolutionByArray();
System.out.println(solutionByArray.FirstNotRepeatingChar("google"));
}
}
面试题50(拓展):字符流中第一个只出现一次的字符
请实现一个函数用来找出字符流中第一个出现一次的字符。例如,当从字符流中只读出前两个字符“go”时,第一个只出现一次的字符是“g”。当该字符流中读出当前六个字符“google”时,第一个只出现一次的字符是“l”,当没有存在一次出现的字符时,返回“#”字符
做过上面一道题目过后,很容易想到,也是使用一个容器来装载,
方法一:使用HashMap存储,详细代码如下
package FirstAppearingOnce;
import java.util.ArrayList;
import java.util.HashMap;
/**
* @author LXY
* @email [email protected]
* @date 2018/7/27 17:29
*
* 请实现一个函数用来找出字符流中第一个只出现一次的字符。
* 例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。
* 当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
* 输出描述:
* 如果当前字符流没有存在出现一次的字符,返回#字符。
*/
public class Solution {
HashMap<Character,Integer> map = new HashMap<>();
ArrayList<Character> arrayList = new ArrayList<>();
//Insert one char from stringstream
public void Insert(char ch)
{
if(map.containsKey(ch))
{
//若哈希表中包含要插入的元素
//将key值插入,value值取出之前的重复ch对应的value值 + 1
map.put(ch,map.get(ch) + 1);
}
else
{
//若哈希表中不包含要插入的元素,则直接进行插入
map.put(ch,1);
}
arrayList.add(ch);
}
//return the first appearence once char in current stringstream
public char FirstAppearingOnce()
{
char res = '#';
for(char ch:arrayList)
{
if(map.get(ch) == 1)
{
res = ch;
break;
}
}
return res;
}
}
方法二:使用数组存储,详细代码如下
package FirstAppearingOnce;
/**
* @author LXY
* @email [email protected]
* @date 2018/7/28 18:50
*
* 数组和StringBuffer解决
*/
public class SolutionByArray {
int[] array = new int[256];
StringBuffer stringBuffer = new StringBuffer();
//Insert one char from stringstream
public void Insert(char ch)
{
stringBuffer.append(ch);
if(array[ch] == 0)
{
array[ch] = 1;
}
else
{
array[ch] += 1;
}
}
//return the first appearence once char in current stringstream
public char FirstAppearingOnce()
{
char[] strChar = stringBuffer.toString().toCharArray();
for(char c : strChar)
{
if(array[c] == 1)
{
return c;
}
}
return '#';
}
}
面试题56:数组中数字出现的次数
题目描述:
一个整型数组里除了两个数字外,其他数字都出现了两次,请写程序找出这两个只出现一次的数字,要求时间复杂度为O(n),空间复杂度为O(1)
方法一:
这道题目第一次见到的时候,我首先想到的也是同上面的方法一样,使用遍历或者使用哈希表来实现,但是想了很久感觉有点困难,而且时间复杂度和空间复杂度很可能会不满足条件,然后就看《剑指Offer》上的讲解,其实没怎么看懂,但是看例子知道的结论:
结论:一组数据,其中只有一个数是只出现一次,其他的数都是出现了两次,那么将这组数中的每个数依次做异或运算,那么最终的结果就是只出现一次的数,这么说可能不是很清楚,下面举一个栗子:
数组 {1,3,2,1,2 }进行异或运算(异或是进行二进制运算):
1 ^ 3 = 2;2 ^ 2 = 0;0 ^ 1 = 1;1 ^ 2 = 3 最终的结果为3就是这组数中唯一出现一次的数(具体为什么我也不知道,但是就是觉得好腻害^o^ ^o^ ^o^)通过上面的结论,回到题目中,知道一组数中有两个数出现一次,其他数都出现两次,那么将这组数逐次进行上面的异或操作,最终得到的结果一定是两个只出现一次的两个数的异或的结果,并且该结果一定不为0,因此该结果的二进制中一定包含至少一个一,下面就是方法
所以,在结果中确定一个1的位置,记为第n位,以此位置为区分,将n位置上为1的数分为一组,n位置上为0的数分为一组,这样就将两个只出现一次的数分别分在两组中(原因很简单,自己去想。。。),再分别在两组中进行上面说过的异或运算,最终两组运算最后的结果就是两个只出现一次的数,下面是一个栗子
数组{2,4,3,6,3,2,5,5}
首先先将该数组中元素逐次进行异或运算
2 ^ 4 ^ 3 ^ 6 ^ 3 ^ 2 ^ 5 ^ 5 = 0010(二进制)根据上面的二进制结果知道该结果的1的位置在第三位上,根据数组中所有元素的第三位为0还是1分为两组{2,3,6,3,2}和{4,5,5}
接下来再分别在两组中进行异或运算
2 ^ 3 ^ 6 ^ 3 ^ 2 = 6
4 ^ 5 ^ 5 = 4最终的结果就是6和4
真的很想给想出这个方法的大佬刷满6666666666666666666666
下面是代码
package FindNumsAppearOnce;
/**
* @author LXY
* @email [email protected]
* @date 2018/7/25 17:26
*
* 一个整型数组里除了两个数字之外,其他的数字都出现了偶数次。
* 请写程序找出这两个只出现一次的数字。
*
*/
//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
int num = 0;
for(int i = 0; i < array.length;i++)
{
num ^= array[i];//将数组中的元素从头到尾开始进行进行异或运算(二进制)
}
int index = Find(num);//在二进制结果中查找最后出现1的位置为index
num1[0] = 0;
num2[0] = 0;
for(int i = 0;i < array.length ;i++)
{
//将数组进行分组(分组标准为:index位置上的数为1/0)
if (((array[i] >> index) & 1) == 1)
{
//index位置上的数为1的数为一组
//然后与同组的元素进行异或操作,最终剩下的结果为最终结果
num1[0] ^= array[i];
}
else
{
//index位置上的数为0的数为一组
//然后与同组的元素进行异或操作,最终剩下的结果为最终结果
num2[0] ^= array[i];
}
}
}
//在二进制中查找第一个出现1的位置为index
private int Find(int num) {
int index = 0;
while((num & 1) == 0)
{
//只要最后一位为0,就一直运算
num >>= 1;
index++;
}
return index;
}
}
后来又在网上找了一下有没有其他的做法,发现有很多种,这里供大家参考
方法二:
- 先将数组进行排序,这样就将相同的数字变成相邻的的顺序
- 然后判断当前点与它前后的数值是否相同
- 若前后有相同array[ i ] == array[ i - 1] || array[ i ] == array[ i + 1],则表示不是所求的元素
- 若不相同array[ i ] != array[ i - 1] && array[ i ] != array[ i + 1],则该元素是所求元素
详细过程见下面程序
package FindNumsAppearOnce;
import java.util.ArrayList;
import java.util.Arrays;
/**
* @author LXY
* @email [email protected]
* @date 2018/7/28 19:43
*/
public class SolutionBySort {
public void FindNumsAppearOnce(int[] array, int num1[], int num2[]) {
ArrayList<Integer> arrayList = new ArrayList<>();
Arrays.sort(array);
for(int i = 0;i < array.length;i++)
{
if(i == array.length - 1 && array[i] != array[i - 1])
{
arrayList.add(array[i]);
}
else if(i == 0 && array[i] != array[i + 1])
{
arrayList.add(array[i]);
}
else if(i != 0 && i != array.length - 1 && array[i] != array[i - 1] && array[i] != array[i + 1])
{
arrayList.add(array[i]);
}
}
num1[0] = arrayList.get(0);
num2[0] = arrayList.get(1);
}
}
方法三
将数组遍历,使用一个ArrayList来存放数组中的元素,若数组中包含遍历的元素,则直接将元素删除,若不包含元素,就将元素插入ArrayList中
详细实现见下面程序
package FindNumsAppearOnce;
import java.util.ArrayList;
/**
* @author LXY
* @email [email protected]
* @date 2018/7/29 12:24
*/
public class SolutionByList {
public void FindNumsAppearOnce(int[] array, int num1[], int num2[]) {
ArrayList<Integer> arrayList = new ArrayList<>();
for(int i = 0;i < array.length;i++)
{
if(!arrayList.contains(array[i]))
{
//若arrayList中不包含待判断元素,就将元素插入到arrayList中
arrayList.add(array[i]);
}
else
{
//若arrayList中包含待判断元素,就将包含的元素从arrayList中删除
arrayList.remove(new Integer(array[i]));
}
}
if(arrayList.size() == 2)
{
num1[0] = arrayList.get(0);
num2[0] = arrayList.get(1);
}
}
}
面试题56(拓展):数组中唯一只出现一次的数字
在一个数组中除一个数字只出现一次外,其他数字都出现三次,请找出那个只出现一次的数字
方法一:
- 根据上面的题目中的方法二的思路,先将数组进行排序,这样相同的三个数字一定紧紧排列在一起,
- 然后就从开头开始判断,若相等,则一次移动三个数字,若array[ i ] != array[ i + 1]的时候那么array[ i ]一定是只出现一次的那个数字,返回即可,
- 其中,有一个关键的点在于若排序过后出现一次的数字在最后面,那么就会发生数组越界的情况,所以要先判断出现在最后的情况
详细过程见下面程序
package FindNumsAppearOnce;
import java.util.Arrays;
/**
* @author LXY
* @email [email protected]
* @date 2018/7/29 18:04
*
* 数组中唯一出现一次的数字
*
*/
public class FindNumFromAppare3 {
public int FindNumsAppearOnce(int[] array) {
Arrays.sort(array);
if(array[array.length - 1] != array[array.length - 2])
{
return array[array.length - 1];
}
for(int i = 0;i < array.length;i = i + 3)
{
if(array[i] != array[i + 1])
{
return array[i];
}
}
return -1;
}
public static void main(String[] args) {
FindNumFromAppare3 findNumFromAppare3 = new FindNumFromAppare3();
int[] array = {1,9,9,5,6,2,5,2,9,7,5,2,7,6,7,6};
System.out.println(findNumFromAppare3.FindNumsAppearOnce(array));
}
}
方法二:《剑指Offer》上的方法
一个数字出现了三次,那么他的二进制上表示的每一位也都出现了三次,所以,将数组各数的二进制的每一位相加,除以3,若能够整除表示出现一次的数字的二进制在该位置上位0,否则表示数字的二进制在该位置为1
下面是一个栗子:
以一个数组{1,3,3,1,1,3}为例,该数组中1和3全部都出现了3次
1:0 0 1
3:0 1 1
3:0 1 1
1:0 0 1
1:0 0 1
3:0 1 1
根据上面的二进制可以看出,最后一个位置上1的个数为6,可以被3整除,倒数第二个位置上1的个数为3,可以被3整除,所以,要是在数组中(任意位置)加入一个2(0 1 0),之后,数组变为{1,3,2,3,1,1,3},二进制最终最后一位上1的个数为6,可以被3整除,倒数第二个位置上1的个数为4,不可以被3整除,因此,单个的数字在该位置上的二进制为1,
下面为详细代码:
package FindNumsAppearOnce;
import java.util.Arrays;
/**
* @author LXY
* @email [email protected]
* @date 2018/7/29 18:36
*/
public class FindNumFromAppare3X {
public int FindNumsAppearOnce(int[] array) {
int bits[] = new int[32];//使用bits[]数组来存储每一位上的1的个数
Arrays.fill(bits,0);//将bits[]数组中的值全部赋为1
for(int i = 0;i < array.length;i ++)
{
for(int j = 0;j < 32;j++)
{
bits[j] += ((array[i] >> j) & 1);
}
}
int res = 0;
for(int j = 0;j < 32;j++)
{
//遍历bits[]数组,若该位上的数不能整3,表示唯一出现的数的二进制在该位置上为1
if(bits[j] % 3 != 0)
{
res += (1 << j);
}
}
return res;
}
public static void main(String[] args) {
FindNumFromAppare3X findNumFromAppare3 = new FindNumFromAppare3X();
int[] array = {1,1,9,5,6,2,5,2,1,7,5,2,7,6,7,6};
System.out.println(findNumFromAppare3.FindNumsAppearOnce(array));
}
}