剑指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)
为什么每次写快速排序都要调很久才能正确呢。。。。?
别人的解法
根据 异或 的性质:
- 交换律
- 结合律
- A ^ 0 = A,A ^ A = 0 (一个数和它本身异或的结果是 0,和 0 异或的结果是它本身)
- 自反性 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];
}
}
};
感觉自己真的笨呃。。。