【问题标题】:Finding an element in an array that isn't repeated a multiple of three times?在数组中查找不重复三倍的元素?
【发布时间】:2011-11-12 09:33:57
【问题描述】:

读完this interesting question后,我想起了我曾经遇到的一个棘手的面试问题,但我一直没有得到满意的回答:

给定一个包含 n 个 32 位无符号整数的数组,其中每个元素(除了一个)重复三倍。在O(n)时间内,尽量少用辅助空间,找出数组中不出现3倍的元素。

举个例子,给定这个数组:

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) 空间)。

有人对如何解决这个问题有任何想法吗?

【问题讨论】:

  • 您的第一个链接似乎已损坏。
  • 显然我们应该异或。我们真正需要的是确定我们应该异或和用什么 :))
  • @recursive- 哎呀!感谢您指出了这一点。链接已修复。
  • 这似乎是一个糟糕的面试问题。该解决方案仅对在同一面试问题中遇到 2 的倍数的“异或技巧”的人来说是显而易见的,因此无法得到答案并不能真正告诉您有关受访者的任何信息。
  • @Nick Johnson:你可以不用异或技巧来解决这个问题,只需通过标准算法(见下面我的答案)。而且通常问这些问题不是看你是否知道神奇的解决方案,而是看你如何解决问题,以及你处理困难任务的方法是什么。

标签: arrays algorithm duplicates


【解决方案1】:

可以在 O(n) 时间和 O(1) 空间内完成。

以下是在 C# 中使用常量空间的方法。我正在使用“xor except with 3-state bits”的想法。对于每个设置位,“xor”操作都会增加相应的三态值。

最终输出将是二进制表示在最终值中的 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 以外的倍数(例如 4、5、6...),而且还可以告诉您多重性。
【解决方案2】:

在恒定空间中执行此操作的明显解决方案是使用就地算法对其进行排序,然后对数组进行一次扫描。

遗憾的是,这通常需要 O(n log n) 时间和 O(1) 空间。

但是由于条目具有有限的密钥长度(32 位),您可以将其用作排序算法基数排序(存在基数排序,它们不稳定,但这无关紧要)。你有 O(n) 时间和 O(1) 空间。

编辑:顺便说一句,您也可以使用这种方法查找所有出现不是 3 倍数的数字,以防您允许多个数字具有此属性。

【讨论】:

    【解决方案3】:

    您正在寻找一个具有非零重复计数的项目(模式 3)。我想我会递归地做:

    1. 将数组分成两半
    2. 查找每半个代表数非零(mod 3)的项目
    3. 合并两半,保留不相等键的计数,并添加相等键的计数
    4. 用 count = 0 (mod 3) 删除任何一个
    5. 将其作为输入当前部分的值集返回。

    甚至没有尝试优化基本算法之外的东西(例如,担心每个计数只存储两位),这似乎做得很好。我已经包含代码来生成一个相当大的测试用例(例如,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;
    }
    

    【讨论】:

      猜你喜欢
      • 2015-04-13
      • 1970-01-01
      • 1970-01-01
      • 2011-06-06
      • 2017-08-18
      • 2020-08-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多