剑指Offer:数组中只出现一次的数字

剑指Offer:数组中只出现一次的数字


题目:

在这里插入图片描述

我的解法

我的愚蠢蠢蠢蠢解法:先快速排序,然后用栈找只出现一次的数字。

class Solution {
public:
    void FindNumsAppearOnce(vector<int> data, int* num1, int *num2) {
        if (data.empty()) return;
        quickSort(data, 0, data.size() - 1);
        stack<int> stk;
        for (size_t i = 0; i < data.size(); ++i) {
            if (stk.empty()) stk.push(data[i]);
            else {
                if (stk.top() == data[i]) stk.pop();
                else stk.push(data[i]);
            }
        }
        *num2 = stk.top();
        stk.pop();
        *num1 = stk.top();
    }
    void quickSort(vector<int>& data, int left, int right) {
	    if (left >= right) return;  
 	    // 上一行是第一个出了错的地方:如果用了 left == right ->
 	  	   // 当 pivot 是最大值时,quickSort(data, p, right) 中 p = data.size(),right = data.size() - 1
 	  	   // 导致 int = data[left] 出现数组下标越界错误(left = data.size())
        
        int k = data[left], p = left + 1, q = right;
        while (p <= q) {
            while (p <= right && data[p] <= k) ++p;
            // 上一行是第二个出了错的地方:
        		// 原来写的是 while (p < data.size() && data[p] <= k)
        		// 出错的原因分析一下 (5 4 5 6) 的排序过程就知道了
            
            while (data[q] > k) --q;
            if (p - q == 1) {
                int tmp = data[q];
                data[q] = data[left];
                data[left] = tmp;
                quickSort(data, left, q - 1);
                quickSort(data, p, right);
            }
            else {
                int tmp = data[p];
                data[p] = data[q];
                data[q] = tmp;
            }
        }
    }
};

时间复杂度 O(nlgn + n)
为什么每次写快速排序都要调很久才能正确呢。。。。?

别人的解法

根据 异或 的性质:

  1. 交换律
  2. 结合律
  3. A ^ 0 = A,A ^ A = 0 (一个数和它本身异或的结果是 0,和 0 异或的结果是它本身)
  4. 自反性 A = A ^ B ^ B,由以上三点得来的一个常用性质,它表示:A 与 B 异或两次的结果还是 A
    异或的自反性经常用于 “一组数中只有一个值只出现一次,其它的都出现两次” 这样的问题。

对于这道题,遍历两遍:

  • 第一遍的异或结果是两个只出现一次的数字的异或结果。(A ^ B ^ B ^ E ^ C ^ E = A ^ C)
  • 然后找出这个异或结果的二进制表示中的第一个为 1 的位 (设为第 k 位)。
  • 再一次遍历,将所有数字按照第 k 位是 1 还是 0 分成两组,可以确定:1) 所有相同的数字一定出现在同一组中;2) 这两个只出现一次的数字一定分别出现在两组中。所以这两组中每一组都只包含一个只出现一次的数字。
  • 分别对这两组中的所有数字异或,得到的两个结果就是这两个只出现一次的数字。

代码如下:

class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
        // 1. 得到所有数字的异或结果
        int res = 0;
        for (size_t i = 0; i < data.size(); ++i) {
            res ^= data[i];
        }
        // 2. 找到异或结果二进制的第一个1
        int n = 0;
        while (res > 1) {
            res >>= 1; // 注意别光移位而忘记赋值 (一开始写成了 res >> 1,结果死循环)
            ++n;
        }
        // 3. 将第一个 1 后面的位全变成 0
        while (n > 0) {
            res <<= 1;
            --n;
        }
        // 4. 再一次遍历所有数字,将这些数字按照异或结果中第一个 1 的位置是 1 还是 0 划分为两组
        //    相同的数字一定在同一组中,两个只出现一次的数字一定分别在两个组中
        *num1 = 0; *num2 = 0;
        for (size_t i = 0; i < data.size(); ++i) {
            if ((data[i] & res) == 0) // 注意 & ^ | 的优先级比 == != < > <= >= 的低,需要加括号
                *num1 ^= data[i];
            else *num2 ^= data[i];
        }
    }
};

感觉自己真的笨呃。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值