Java——关于在字符串,数组,字符流中寻找只出现一次的问题

面试题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}

  1. 首先先将该数组中元素逐次进行异或运算
    2 ^ 4 ^ 3 ^ 6 ^ 3 ^ 2 ^ 5 ^ 5 = 0010(二进制)

  2. 根据上面的二进制结果知道该结果的1的位置在第三位上,根据数组中所有元素的第三位为0还是1分为两组{2,3,6,3,2}和{4,5,5}

  3. 接下来再分别在两组中进行异或运算
    2 ^ 3 ^ 6 ^ 3 ^ 2 = 6
    4 ^ 5 ^ 5 = 4

  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));
    }
}

猜你喜欢

转载自blog.csdn.net/L_X_Y_HH/article/details/81252756