设计一个可以求最大/最小元素的队列/栈

设计一个可以求最大/最小元素的队列/栈

  • 设计队列/栈, 支持出入,求最大元素; 要求全部操作O(1)
  • leetcode 155 MinStack
  • O(1)的Min/Max栈的实现非常简单; 但Min/Max队列的实现就需要稍微分析一下。

MinStack, leetCode 155

#define MAX_INT 2147483646

class MinStack {
public:
    /** initialize your data structure here. */
    MinStack():
        current_min_(MAX_INT) {
    } 
    void push(int x) {
        data_.push_back(x);
        
        // 不好的测试条件; 
        // if (x < current_min_) {
        //    current_min_ = x;
        // }
        //
        // 当min_data存在元素, 且x大于当前back()元素, 
        // 推入原有的最大元素;
        if (min_data_.size() > 0 && x > min_data_.back()) {
            min_data_.push_back(min_data_.back());
        } else {
            min_data_.push_back(x);
        }
    }
    
    void pop() {
        if (data_.size() == 0) {
            return;
        }
        data_.pop_back();
        min_data_.pop_back();
        current_min_ = min_data_.back();
    }
    
    int top() {
        return data_.back();
    }
    
    int getMin() {
        return current_min_;
    }
  private:
    vector<int> data_;
    vector<int> min_data_;
    int current_min_;
};

此题太简单了; 下面写一点扩展, 加了 一点模板技巧,并且注意MinQueue/MaxQueue的设计与stack的设计明显不同。

MaxStack

// MaxStack

#include <iostream>
#include <vector>
// #define SMALL_INT -2147483647

template <typename T>
class MaxStack {
  public:
    // explicit MaxStack():
    //    current_max(SMALL_INT) {}
    MaxStack() {}

    void push(T data);
    T pop();
    T getMax();
    inline int size() {
        return data_.size();
    }
  private:
    //T current_max;         // 当前最大值
    std::vector<T> data_;  // 记录data的栈
    std::vector<T> max_data_;  // 记录与data_中元素相对应的max元素的值的栈
    
    template <typename G>
    friend std::ostream& operator<<(std::ostream& os, const MaxStack<G>&);
};

// friend function, overload operator<<
template <typename G>
std::ostream& operator <<(std::ostream& os, const MaxStack<G>& stack) {
  auto beg = stack.data_.begin(), end = stack.data_.end();
  os << "stack data_=";
  for (; beg != end; ++beg) {
    if (beg + 1 == end) {
      os << *beg << std::endl;
    } else {
        os << *beg << ", ";
    }
  }
  auto beg2 = stack.max_data_.begin(), end2 = stack.max_data_.end();
  os << "stack max_data_=";
  for (; beg2 != end2; ++beg2) {
    if (beg2 + 1 == end2) {
      os << *beg2 << std::endl;
    } else {
      os << *beg2 << ", ";
    }
  }
  return os;
}

template <typename T>
void MaxStack<T>::push(T data) {
  data_.push_back(data);

  // if (data > current_max) {  // bad way, deprecated;
  //   current_max = data;
  // }
  // max_data_.push_back(current_max);

  if (max_data_.size() > 0 && data < max_data_.back()) {
    max_data_.push_back(max_data_.back());
  } else {
    max_data_.push_back(data);
  }
}

template <typename T>
T MaxStack<T>::pop() {
  if (data_.size() == 0) {
    throw std::runtime_error("stack is empty");
  }
  T top = data_.back();
  data_.pop_back();
  max_data_.pop_back();

  return top;
}

template <typename T>
T MaxStack<T>::getMax() {
  return max_data_.back();
}


// test
// 
int main() {
  MaxStack<int> stack;
  std::vector<int> test_data = {5, 120, 100, 900, 20, 80, 90};
  int count = 0;
  
  for (auto it : test_data) {
    stack.push(it);
    std::cout << "push " << ++count << " item=" 
        <<  it << ", max=" << stack.getMax() << std::endl;
  }
  std::cout << stack;
  int size = stack.size();
  for (int j = size - 1; j >= 0; --j) {
      std::cout << j << " item=" << stack.pop()
        << " poped, max=" << stack.getMax() << std::endl;
  }
  return 0;
}

MaxQueue

MaxQueue, 由于入队和出队的使用方式,针对getMax()对数据的组织和MaxStack,必须有所不同.

stack的处理相比之下比较简单, 由于仅仅是在单端操作, 在push()操作时, max_data_ vector的操作只是把对应的当前元素的最大值记录即可。在pop()时, 只需将current_max更新为max_data_的栈顶值; 从而这样可以支持getMax();

queue是双端操作,出队在队头, 入队在队尾。问题集中在如何放置max_data_中的元素, 使得在dequeue()操作的过程中,可以正确获得当前的max元素. 试想, 当在出队某一个元素i时, 该位置之前的元素已经被出队, 由此可能会引起队列中的最大元素的改变, 但是我们在入队的i元素的时候, 并不知道i元素之后可能入队什么元素, 而在准备出队i元素时, 当前最大的max值取决于从i到队尾的最大元素值。所以需要在这样处理入队操作:

入队

向前遍历已在队列中元素, 如果当前准备入队元素大于前面的元素,则更新max_data_中相应的最大元素值为当前将入队元素, 继续向前遍历直到队头, 或者发现某一个元素j,大于当前入队元素, 则向前遍历停止(j - i元素之间max值是刚刚的入队元素, j元素之前最大元素是j).

出队

出队操作就比较直观,即除了data_, max_data_的出队队头元素, 由于出队可能引起当前的最大值的改变, 需要判断max_data出队的元素是否>=current_max,如是则更新为max_data_的下一个元素; 这样就保证了getMax()操作的正确性;

基于踩坑的体验, 实际上可以优化掉current_max,基于上述的组织方式,实际上针对当前队列元素的max元素一直是max_data_.front();

getMax()时间复杂度O(1);
dequeue()的时间复杂度O(1);
enqueue()操作的时间复杂度取决于入队数据序列的情况, 最坏时间复杂度是O(n), 即n个入队数据最后的数字是最大的, 则会产生n次向前访问; 最好是O(1), 若数据序列前面入队的数字总是大于后面的, 则无需遍历; 因此真正的期望平均时间复杂度是考虑到入队数据样本的数据大小分布的平均值, 姑且算均摊O(1);

// MaxQueue

#include <iostream>
#include <vector>

template <typename T>
class MaxQueue {
  public:
    MaxQueue() {}

    void enqueue(T data);
    T dequeue();
    T getMax();
    inline int size() {
      return data_.size();
    }
    
  private:
    std::vector<T> data_;
    std::vector<T> max_data_;
    // T current_max;

    template <typename G>
    friend std::ostream& operator<<(std::ostream& os, const MaxQueue<G>& queue);

};

// for debug
template <typename G>
std::ostream& operator<<(std::ostream& os, const MaxQueue<G>& queue) {
    auto st = queue.data_.begin(), ed = queue.data_.end();
    os << "queue data_=";
    for (; st != ed; ++st) {
      if (st == ed - 1) {
        os << *st << std::endl;
      } else {
        os << *st << ", ";
      }
    }
    auto st2 = queue.max_data_.begin(), ed2 = queue.max_data_.end();
    os << "queue max_data_="; 
    for (; st2 != ed2; ++st2) {
      if (st2 == ed2 - 1) {
        os << *st2 << std::endl;
      } else {
        os << *st2 << ", ";
      }
    }
    return os;
}

template<typename T>
void MaxQueue<T>::enqueue(T data) {
  data_.push_back(data);
  max_data_.push_back(data);

//   if (data > current_max) {
//     current_max = data;
//   }
  int len = data_.size() - 1; // Do NOT use size_t, unsigned value smaller than 0  error;
  while (--len >= 0) {
    if (data > data_[len]) {
      max_data_[len] = data;
    } else {
      break;
    }
  }
}

template <typename T>
T MaxQueue<T>::dequeue() {
if (data_.size() == 0) {
    throw new std::runtime_error("queue is empty");
  }
  T beg = data_.front();
  data_.erase(data_.begin());
  
  T beg_max = max_data_.front();
  max_data_.erase(max_data_.begin());

//   if (beg_max >= current_max) {
//     current_max = max_data_.front();
//   }

  return beg;
}

template<typename T>
T MaxQueue<T>::getMax() {
//   return current_max;
    return max_data_.front();
}

///
// test
//
int main() {
  MaxQueue<int> queue;
  std::vector<int> test_data = {5, 120, 100, 900, 20, 80, 90};

  int count = 0;
  for (auto it : test_data) {
    queue.enqueue(it);
    std::cout << "enqueue " << ++count << " item="
        << it << ", max="
        << queue.getMax() << std::endl;
  }
  std::cout << queue;

  int size = queue.size();
  for (int j = size - 1; j >= 0; --j) {
      std::cout << j << " item=" << queue.dequeue() << " dequeued, "
      << "max=" << queue.getMax() << std::endl;
  }

  return 0;
}

/**
result:
enqueue 1 item=5, max=5
enqueue 2 item=120, max=120
enqueue 3 item=100, max=120
enqueue 4 item=900, max=900
enqueue 5 item=20, max=900
enqueue 6 item=80, max=900
enqueue 7 item=90, max=900
queue data_=5, 120, 100, 900, 20, 80, 90
queue max_data_=900, 900, 900, 900, 90, 90, 90
6 item=5 dequeued, max=900
5 item=120 dequeued, max=900
4 item=100 dequeued, max=900
3 item=900 dequeued, max=90
2 item=20 dequeued, max=90
1 item=80 dequeued, max=90
0 item=90 dequeued, max=90
*/

测试通过了, 比较简单就不贴结果了.

总结

  • 注意,设计MaxQueue的时候, 还是应该使用具体的数据样例进行手动的入队出队操作,寻找规律。从具体到抽象。 避免一开始就使用抽象的变量符号。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值