在阅读this interesting question之后,我想起了曾经有过一次我从未满意地回答的棘手面试问题:
给出一个n个32位无符号整数的数组,其中每个元素(一个除外)重复三次。在O(n)时间内并使用尽可能少的辅助空间,找到不会出现三次倍数的数组元素。
举个例子,给定这个数组:
1 1 2 2 2 3 3 3 3 3 3
我们输出1,同时给出数组
3 2 1 3 2 1 2 3 1 4 4 4 4
我们输出4。
这可以通过使用哈希表计算每个元素的频率在O(n)时间和O(n)空间中轻松解决,但我强烈怀疑,因为问题陈述明确提到数组包含32-位无符号整数,有一个更好的解决方案(我猜O(1)空间)。
有没有人对如何解决这个问题有任何想法?
答案 0 :(得分:16)
可以在O(n)时间和O(1)空间中完成。
以下是在C#中使用常量空间的方法。我正在使用“xor除了3状态位”之外的想法。对于每个设置位,“xor”操作会递增相应的3态值。
最终输出将是其二进制表示在最终值中为1或2的位置具有1的数字。
void Main() {
Console.WriteLine (FindNonTriple(new uint[]
{1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3} ));
// 1
Console.WriteLine (FindNonTriple(new uint[]
{3, 2, 1, 3, 2, 1, 3, 2, 1, 4, 4, 4, 4} ));
// 4
}
uint FindNonTriple(uint[] args) {
byte[] occurred = new byte[32];
foreach (uint val in args) {
for (int i = 0; i < 32; i++) {
occurred[i] = (byte)((occurred[i] + (val >> i & 1)) % 3);
}
}
uint result = 0;
for (int i = 0; i < 32; i++) {
if (occurred[i] != 0) result |= 1u << i;
}
return result;
}
答案 1 :(得分:3)
在恒定空间中执行此操作的明显解决方案是使用就地算法对其进行排序,然后在阵列上扫描一次。
可悲的是,这通常需要O(n log n)时间和O(1)空间。
但由于条目具有有限的密钥长度(32位),您可以使用排序算法基数排序(存在基数排序,它们不稳定,但这在这里无关紧要)。那里有O(n)时间和O(1)空间。
编辑:顺便说一句,您可以使用此方法查找出现次数不是3次的倍数,以防您允许多个号码具有此属性。
答案 2 :(得分:0)
您正在寻找一个重复计数为非零(项目3)的项目。我想我会递归地做:
甚至没有尝试优化基本算法之外的东西(例如,不担心每个计数只存储两位),这似乎做得很好。我已经包含了代码来生成一个相当大的测试用例(例如,1500多个项目)并打印出它正在创建的地图的大小。在任何给定的时间,它创建的地图中似乎最多有大约50个项目(即,它一次只使用两个地图,而我看到的最大的是大约25个项目)。从技术上讲,我认为目前这是O(N log N),但是如果你切换到基于散列的容器,我相信你会期望O(N)。
#include <map>
#include <iterator>
#include <iostream>
#include <algorithm>
#include <utility>
#include <vector>
#include <time.h>
class zero_mod {
unsigned base;
public:
zero_mod(unsigned b) : base(b) {}
bool operator()(std::pair<int, int> const &v) {
return v.second % base == 0;
}
};
// merge two maps together -- all keys from both maps, and the sums
// of equal values.
// Then remove any items with a value congruent to 0 (mod 3)
//
std::map<int, int>
merge(std::map<int, int> const &left, std::map<int, int> const &right) {
std::map<int, int>::const_iterator p, pos;
std::map<int, int> temp, ret;
std::copy(left.begin(), left.end(), std::inserter(temp, temp.end()));
for (p=right.begin(); p!=right.end(); ++p)
temp[p->first] += p->second;
std::remove_copy_if(temp.begin(), temp.end(),
std::inserter(ret, ret.end()),
zero_mod(3));
return ret;
}
// Recursively find items with counts != 0 (mod 3):
std::map<int, int>
do_count(std::vector<int> const &input, size_t left, size_t right) {
std::map<int, int> left_counts, right_counts, temp, ret;
if (right - left <= 2) {
for (size_t i=left; i!=right; ++i)
++ret[input[i]];
return ret;
}
size_t middle = left + (right-left)/2;
left_counts = do_count(input, left, middle);
right_counts = do_count(input, middle, right);
ret = merge(left_counts, right_counts);
// show the size of the map to get an idea of how much storage we're using.
std::cerr << "Size: " << ret.size() << "\t";
return ret;
}
std::map<int, int> count(std::vector<int> const &input) {
return do_count(input, 0, input.size());
}
namespace std {
ostream &operator<<(ostream &os, pair<int, int> const &p) {
return os << p.first;
}
}
int main() {
srand(time(NULL));
std::vector<int> test;
// generate a bunch of data by inserting packets of 3 items
for (int i=0; i<100; i++) {
int packets = std::rand() % 10;
int value = rand() % 50;
for (int i=0; i<packets * 3; i++)
test.push_back(value);
}
// then remove one item, so that value will not occur a multiple of 3 times
size_t pos = rand() % test.size();
std::cerr << "Removing: " << test[pos] << " at position: " << pos << "\n";
test.erase(test.begin()+pos);
std::cerr << "Overall size: " << test.size() << "\n";
std::random_shuffle(test.begin(), test.end());
// Note that we use a map of results, so this will work if multiple values
// occur the wrong number of times.
std::map<int, int> results = count(test);
// show the results. Should match the value shown as removed above:
std::copy(results.begin(), results.end(),
std::ostream_iterator<std::pair<int, int> >(std::cout, "\n"));
return 0;
}