12
返回列表 发新帖
楼主: Andrew007
跳转到指定楼层
上一主题 下一主题
收起左侧

[数组] 请教一道算法题

🔗
 楼主| Andrew007 2021-3-19 04:38:38 | 只看该作者
全局:

是的, 取完的位置要重复使用。
回复

使用道具 举报

🔗
funmastermike 2021-3-19 05:04:05 | 只看该作者
全局:
本帖最后由 wengjn 于 2021-3-18 13:20 编辑
Andrew007 发表于 2021-3-18 12:38
是的, 取完的位置要重复使用。

谢谢
那算常规medium题吧

应该是时刻注意track front, rear pointer 看看什么时候full拉啥得
  1. class HelloWorld {
  2.     static void Main() {
  3.         CircularQueue q = new CircularQueue(3);
  4.         q.Enqueue(11);
  5.         q.Enqueue(12);
  6.         q.Enqueue(13);
  7.         
  8.         var test = q.Dequeue();
  9.         Console.WriteLine(test);
  10.         
  11.         Console.WriteLine(q.Dequeue());
  12.         
  13.         q.Enqueue(14);
  14.         q.Enqueue(15);
  15.         q.Enqueue(16);
  16.         Console.WriteLine(q.Dequeue());
  17.         Console.WriteLine(q.Dequeue());
  18.         Console.WriteLine(q.Dequeue());
  19.         Console.WriteLine(q.Dequeue());
  20.     }
  21. }

  22. public class CircularQueue {
  23.     private int size;
  24.     private int front;
  25.     private int rear;
  26.    
  27.     private int[] data;
  28.    
  29.     public CircularQueue(int capacity) {
  30.         size = capacity;
  31.         front = -1;
  32.         rear = -1;
  33.         data = new int[size];
  34.     }
  35.    
  36.     public void Enqueue(int val) {
  37.         // if the queue is full
  38.         if ((front == 0 && rear == size-1) || (rear == front-1)) {
  39.             Console.WriteLine("queue full, add failed.");
  40.         } else if (front == -1) {
  41.             // empty queue
  42.             front = 0;
  43.             rear = 0;
  44.             data[rear] = val;
  45.         } else if (rear == size-1) {
  46.             rear = 0;
  47.             data[rear] = val;
  48.         } else {
  49.             rear++;
  50.             data[rear] = val;
  51.         }
  52.     }
  53.    
  54.     public int Dequeue() {
  55.         if (front == -1) {
  56.             // empty queue
  57.             Console.WriteLine("queue empty, dequeue failed.");
  58.             return -1;
  59.         }
  60.         int val = data[front];
  61.         if (front == rear) {
  62.             // only one item in the queue
  63.             front = rear = -1;
  64.         } else if (front == size-1) {
  65.             front = 0;
  66.         } else {
  67.             front++;
  68.         }
  69.         return val;
  70.     }
  71. }
复制代码


评分

参与人数 1大米 +1 收起 理由
Andrew007 + 1 给你点个赞!但是是2个queue

查看全部评分

回复

使用道具 举报

🔗
MareBone 2021-3-19 06:03:58 | 只看该作者
全局:
wengjn 发表于 2021-3-19 05:04
谢谢
那算常规medium题吧

这样只有一个queue。题目要求是两个queue
回复

使用道具 举报

🔗
magicsets 2021-3-19 14:36:33 | 只看该作者
全局:
可以把4MB分成1024个4kB的内存块,然后以4kB内存块为单位进行动态分配与释放。

那么对于任一个Queue来说,它最“浪费”的情况就是首尾两个内存块为空,也就是8kB,只有 4MB的 0.2% —— 这种情况下我们可以认为内存是被高效利用的。

另一方面的考虑是动态内存分配的额外开销,注意到一个int类型是4 bytes,那么当我们每处理4kB / 4 bytes = 1024个元素的时候才会有一次内存分配(内存分配其实本来就是O(1)) 的额外开销,那么平摊后可以忽略不计 —— 也就是说,使用4kB内存块的方法性能应该是和一开始就开个足够大的数组不需要resize的性能没什么差别

参考代码:
  1. #include <cstdint>
  2. #include <iostream>
  3. #include <memory>
  4. #include <stack>
  5. #include <stdexcept>
  6. #include <utility>

  7. // CPU分支预测hint
  8. #define UNLIKELY(x) __builtin_expect(!!(x), 0)

  9. #define CHECK(cond) \
  10.   if (UNLIKELY(!(cond))) { throw std::runtime_error("CHECK failed: " #cond); }

  11. // 设置一个数据块为4kB
  12. static constexpr int64_t kBlockSize = 4096;

  13. // 用于管理内存池的抽象
  14. class Arena {
  15. public:
  16.   explicit Arena(int64_t size)
  17.       : memory_buffer_(std::make_unique<char[]>(size)) {
  18.     // size要求是4kB的整数倍
  19.     CHECK(size % kBlockSize == 0);
  20.     // 如果size是4MB的话,那么就有4MB / 4kB = 1024个内存块
  21.     // 初始化只有一次,并且时间可以忽略不计
  22.     // 更理想的是bitmap来记录可用块信息,那么只需要初始化1024 bits = 128 bytes
  23.     for (int64_t i = size / kBlockSize - 1; i >= 0; --i) {
  24.       available_blocks_.push(i);
  25.     }
  26.   }

  27.   // 分配一个数据块
  28.   template <typename BlockType>
  29.   std::unique_ptr<BlockType> Allocate() {
  30.     CHECK(!available_blocks_.empty());
  31.     const int64_t block_id = available_blocks_.top();
  32.     std::cout << "Allocate block " << block_id << "\n";
  33.     available_blocks_.pop();
  34.     return std::make_unique<BlockType>(
  35.         memory_buffer_.get() + block_id * kBlockSize, block_id, this);
  36.   }

  37.   // 回收一个数据块
  38.   void Release(int64_t block_id) {
  39.     std::cout << "Release block " << block_id << "\n";
  40.     available_blocks_.push(block_id);
  41.   }

  42. private:
  43.   // 总体的内存块 (例如4MB)
  44.   const std::unique_ptr<char[]> memory_buffer_;

  45.   // 理想的实现是用bitmap来维护可用内存块,不过代码量比较大
  46.   std::stack<int64_t> available_blocks_;
  47. };

  48. // 4kB数据块的封装
  49. class Block {
  50. public:
  51.   Block(void* block_data, int64_t block_id, Arena* arena)
  52.       : block_data_(block_data), block_id_(block_id), arena_(arena) {}

  53.   // RAII机制回收数据块
  54.   ~Block() { arena_->Release(block_id_); }

  55.   // 读取某个位置的数据
  56.   template <typename T>
  57.   T Get(int64_t index) const {
  58.     return static_cast<const T*>(block_data_)[index];
  59.   }

  60.   // 设置某个位置的数据
  61.   template <typename T>
  62.   void Set(int64_t index, T value) const {
  63.     static_cast<T*>(block_data_)[index] = value;
  64.   }

  65.   std::unique_ptr<Block>& next_block() { return next_block_; }

  66. private:
  67.   // 每个数据块需要额外32 bytes的内存指针 + block id + next pointer
  68.   // 这点开销比起4kB不到1%所以可以忽略不计
  69.   void* block_data_;
  70.   int64_t block_id_;
  71.   Arena* arena_;
  72.   std::unique_ptr<Block> next_block_;
  73. };

  74. // Queue的实现
  75. template <typename T>
  76. class Queue {
  77. public:
  78.   explicit Queue(Arena* arena)
  79.       : arena_(arena),
  80.         block_capacity_(kBlockSize / sizeof(T)) {
  81.     CHECK(block_capacity_ > 0);
  82.   }

  83.   // 往队列头添加一个新元素
  84.   void PushFront(T value) {
  85.     if (UNLIKELY(front_ == nullptr)) {
  86.       // Block链表为空
  87.       back_ = arena_->Allocate<Block>();
  88.       front_ = back_.get();
  89.     } else if (UNLIKELY(head_exclusive_ >= block_capacity_)) {
  90.       // front_已经满了
  91.       front_->next_block() = arena_->Allocate<Block>();
  92.       front_ = front_->next_block().get();
  93.       head_exclusive_ = 0;
  94.     }
  95.     ++size_;
  96.     front_->Set(head_exclusive_++, value);
  97.   }

  98.   // 从队列尾取出一个元素
  99.   T PopBack() {
  100.     if (UNLIKELY(back_.get() == front_)) {
  101.       // 链表里只有一个数据块的情况
  102.       CHECK(tail_inclusive_ < head_exclusive_);
  103.     } else if (UNLIKELY(tail_inclusive_ >= block_capacity_)) {
  104.       // back_没有剩余元素了,对其回收并移动到下一个数据块
  105.       back_ = std::move(back_->next_block());
  106.       tail_inclusive_ = 0;
  107.       CHECK(back_ != nullptr);
  108.     }
  109.     --size_;
  110.     return back_->Get<T>(tail_inclusive_++);
  111.   }

  112.   size_t size() const { return size_; }
  113.   bool IsEmpty() const { return size_ == 0; }

  114. private:
  115.   Arena* const arena_;
  116.   const int64_t block_capacity_;
  117.   int64_t size_ = 0;

  118.   // 这里back_是链表头,但是是队列尾
  119.   std::unique_ptr<Block> back_;
  120.   int64_t tail_inclusive_ = 0;

  121.   // front_是链表尾,但是是队列头
  122.   Block* front_ = nullptr;
  123.   int64_t head_exclusive_ = 0;
  124. };

  125. // 测试代码
  126. int main(int argc, char* argv[]) {
  127.   Arena arena(/*size=*/4 * 1024 * 1024);

  128.   Queue<int> a(&arena);
  129.   Queue<int64_t> b(&arena);

  130.   for (int i = 1; i <= 10000; ++i) {
  131.     a.PushFront(i);
  132.     b.PushFront(i);
  133.   }
  134.   while (!a.IsEmpty()) {
  135.     b.PushFront(a.PopBack() * 10);
  136.   }

  137.   int64_t b_sum = 0;
  138.   while (!b.IsEmpty()) {
  139.     b_sum += b.PopBack();
  140.   }
  141.   std::cout << "b sum = " << b_sum << "\n";

  142.   return 0;
  143. }
复制代码

评分

参与人数 2大米 +5 收起 理由
Andrew007 + 3 感谢大神
funmastermike + 2 给你点个赞!

查看全部评分

回复

使用道具 举报

🔗
funmastermike 2021-3-20 04:18:38 | 只看该作者
全局:
magicsets 发表于 2021-3-18 22:36
可以把4MB分成1024个4kB的内存块,然后以4kB内存块为单位进行动态分配与释放。

那么对于任一个Queue来说 ...

高手啊 赞 谢谢了
回复

使用道具 举报

🔗
1m3fdstring 2021-3-23 00:06:10 | 只看该作者
全局:
JAVA还没碰到要我们处理内存的问题,因为有垃圾回收器。

果然是c++和c#才会有的问题,长见识了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册账号
隐私提醒:
  • ☑ 禁止发布广告,拉群,贴个人联系方式:找人请去🔗同学同事飞友,拉群请去🔗拉群结伴,广告请去🔗跳蚤市场,和 🔗租房广告|找室友
  • ☑ 论坛内容在发帖 30 分钟内可以编辑,过后则不能删帖。为防止被骚扰甚至人肉,不要公开留微信等联系方式,如有需求请以论坛私信方式发送。
  • ☑ 干货版块可免费使用 🔗超级匿名:面经(美国面经、中国面经、数科面经、PM面经),抖包袱(美国、中国)和录取汇报、定位选校版
  • ☑ 查阅全站 🔗各种匿名方法

本版积分规则

>
快速回复 返回顶部 返回列表