剑指offer系列——40.数组中只出现一次的数字i-ii

Q:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
T:
1.排序后一个一个对比。
2.使用hash。

import java.util.*;
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        Queue<Integer> arr = new LinkedList<Integer>();
        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int i = 0; i < array.length; i++) {
            if (!map.containsKey(array[i])) {
                map.put(array[i], 1);
            } else {
                map.put(array[i], map.get(array[i]) + 1);
            }
        }
        for (int i = 0; i < array.length; i++) {
            if (map.get(array[i]) == 1) {
                arr.add(array[i]);
            }
        }
        num1[0] = arr.poll();
        num2[0] = arr.poll();
        System.out.println(num1[0]);
        System.out.println(num2[0]);
    }
}

3.使用set。java的话可以使用。使用Set集合存储,Set集合不存储重复值,add()方法返回值为boolean类型,这一特点可以利用。
Set集合的add方法,添加成功返回true,否则返回false,而Set集合不存储重复值,所以当要添加的数与集合中已存在的数重复时,不会再进行添加操作,返回false,这时再进行remove操作,将集合中已存在的那个与要添加的数相同的元素移除,这样将作为方法参数传递过来的整型数组遍历完后,到最后集合中就只剩下了那个只出现了一次的数字。

public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        HashSet<Integer> set = new HashSet<Integer>();
 
        for (int i = 0;i < array.length;i++){
            if(!set.add(array[i])){
                set.remove(array[i]);
            }
        }
 
        Object[] temp =set.toArray();
        num1[0] = (int) temp[0];
        num2[0] = (int) temp[1];
    }

3.使用异或运算寻找。这个方法很巧妙。(感谢@披萨大叔)
位运算中异或的性质:两个相同数字异或=0,一个数和0异或还是它本身。
当只有一个数出现一次时,我们把数组中所有的数,依次异或运算,最后剩下的就是落单的数,因为成对儿出现的都抵消了。

依照这个思路,我们来看两个数(我们假设是AB)出现一次的数组。我们首先还是先异或,剩下的数字肯定是A、B异或的结果,因为其他数字全部都与自身异或结果为0了。
这个结果的二进制中的1,表现的是A和B的不同的位。我们就取第一个1所在的位数,假设是第3位,接着把原数组分成两组,分组标准是第3位是否为1。如此,相同的数肯定在一个组,因为相同数字所有位都相同,而不同的数,肯定不在一组。然后把这两个组按照最开始的思路,依次异或,剩余的两个结果就是这两个只出现一次的数字

    void FindNumsAppearOnce(vector<int> data, int *num1, int *num2) {
        if (data.size() < 2)
            return;
        if (data.size() == 2) {
            *num1 = data[0];
            *num2 = data[1];
        }
        int bitResult = 0;
        for (int i = 0; i < data.size(); i++)
            bitResult ^= data[i];
        //bitResult为AB异或的结果
        int index = 0;
        while (true) {
            //找到第一个1所在的位数,也是区分AB的指标
            if ((bitResult & 1) != 0)
                break;
            bitResult >>= 1;
            index++;
        }
        *num1 = *num2 = 0;
        for (int i = 0; i < data.size(); i++) {
            //将AB分组,分组后异或,异或的最后结果就是AB
            if (isBit1(data[i], index))
                *num1 ^= data[i];
            else
                *num2 ^= data[i];
        }
    }

    bool isBit1(int target, int index) {
        int temp = target >> index;
        return (temp & 1) != 0;
    }

Q:现在有一个整数类型的数组,数组中只有一个元素只出现一次,其余元素都出现三次。你需要找出只出现一次的元素
注意:
你需要给出一个线性时间复杂度的算法,你能在不使用额外内存空间的情况下解决这个问题么?
A:
若一个数出现三次,则其对应的二进制数每一位相加必为3或0。
统计数组中所有元素的每一位,若为3的倍数,所求数的该二进制位对3取余为0,否则为1。
举例:{1,1,1,2,2,2,3}
01
01
01
10
10
10
11
——
04%3 = 01
40%3 = 10
——
11

    public int singleNumber(int[] A) {
        int result = 0;
        for (int i = 0; i < 32; ++i) {
            int bits = 0;
            for (int value : A) {
                bits += (value >> i) & 1;//依次获取A中每个元素的第i位,全部和1与后相加
            }
            //这里的|=和+=是一样的,因为当前只会得到在i为为0或1,右侧位全为0的值和i位为0但右侧不一定完全为0的值的和,用或和加都行,但用或会更快
            result |= (bits % 3) << i;
        }
        return result;
    }

这个题还有种解法,可看 https://www.jianshu.com/p/c8612aef41af
但实在太难了,没看懂。

posted @ 2020-02-19 16:44  Shaw_喆宇  阅读(245)  评论(0编辑  收藏  举报