Leetcode 1286 Iterator for Combination

又要写 leetcode 解题思路了,实在不想写一大堆的 leetcode 放在日记里,这篇先存成草稿,考虑是不是要给 leetcode 单独一个版面,免得让我感觉每天只是刷题。


combination,看到这个词,就觉得这道题是位操作的题,想到位操作,就头疼,不擅长啊。

看了以前的提交记录,上次是先生成所有的组合,然后直接返回结果的解法,很好,空间换时间,简单直接。唯一的问题就是如果字符很长就要占用相当大的内存空间。目前这个解法还能过,不知道以后随着测试样例的增加,会不会内存不够用(MLE)。发现自己习惯用 deque,两头可进可出,想怎么进出就怎么进出,真方便啊。根据cplusplus.com的介绍,c++ 上的 stack 和 queue 都是 deque 再次封装阉割实现的,用 deque 效率也更高。

上代码,这种解法没什么可说的。

class CombinationIterator {
 public:
  CombinationIterator(string characters, int combinationLength) {
    const int n = characters.size();
    function<void(int, string)> helper = [&] (const int idx, string cur) {
      if (cur.size() == combinationLength) {
        dq_.push_back(cur);
        return;
      }
      for (int i = idx; i < n; ++i)
        helper(i + 1, cur + characters[i]);
    };
    helper(0, "");
  }

  // It's guaranteed that all calls of the function next are valid.
  string next() {
    string res = dq_.front();
    dq_.pop_front();
    return res;
  }

  bool hasNext() {
    return !dq_.empty();
  }
  deque<string> dq_;
};

真正有趣的是位操作的解法。网上有很多介绍通过位操作来快速计算的奇技淫巧,很有趣。下面这种解法来自花花

class CombinationIterator {
 public:
  CombinationIterator(string characters, int combinationLength):
      chars_ (characters.rbegin(), characters.rend()) {
    n_ = combinationLength;
    mask_ = (1 << characters.size()) - 1;
  }

  string next() {
    hasNext();
    string res;
    for (int i = chars_.size() - 1; i >= 0; --i)
      if ((mask_ >> i) & 1)
        res.push_back(chars_[i]);
    --mask_;
    return res;
  }

  bool hasNext() {
    while (mask_ >= 0 && __builtin_popcount(mask_) != n_) --mask_;
    return mask_ > 0;
  }
 private:
  int mask_, n_;
  string chars_;
};

两种解法的比较:第一种解法先生成了全部的组合,每次调用 next() 的时候直接给出答案,反应迅速但是内存消耗大。第二种方法需要在调用 next() 时计算,虽然位操作很快可是遇到 string 也会被拖累。如果系统要优秀的反应的时间,内存消耗不严格,就选用第一种,理解简单,维护方便,常数时间给出结果。如果对内存要求苛刻或者组合太大没办法全部生成,再采用第二种。


#直达链接

LeetCode 1286. Iterator for Combination