力扣1670-设计前中后队列

  |  

摘要: 基于链表的设计

【对算法,数学,计算机感兴趣的同学,欢迎关注我哈,阅读更多原创文章】
我的网站:潮汐朝夕的生活实验室
我的公众号:算法题刷刷
我的知乎:潮汐朝夕
我的github:FennelDumplings
我的leetcode:FennelDumplings


本文看一个基础数据结构的设计问题。难点在于增删的时候如何维护指向链表中间节点的指针。细节处理非常繁琐,不容易过。

题目

请你设计一个队列,支持在前,中,后三个位置的 push 和 pop 操作。

请你完成 FrontMiddleBack 类:

  • FrontMiddleBack() 初始化队列。
  • void pushFront(int val) 将 val 添加到队列的 最前面 。
  • void pushMiddle(int val) 将 val 添加到队列的 正中间 。
  • void pushBack(int val) 将 val 添加到队里的 最后面 。
  • int popFront() 将 最前面 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。
  • int popMiddle() 将 正中间 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。
  • int popBack() 将 最后面 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。

请注意当有 两个 中间位置的时候,选择靠前面的位置进行操作。比方说:

  • 将 6 添加到 [1, 2, 3, 4, 5] 的中间位置,结果数组为 [1, 2, 6, 3, 4, 5] 。
  • 从 [1, 2, 3, 4, 5, 6] 的中间位置弹出元素,返回 3 ,数组变为 [1, 2, 4, 5, 6] 。

示例 1:
输入:
[“FrontMiddleBackQueue”, “pushFront”, “pushBack”, “pushMiddle”, “pushMiddle”, “popFront”, “popMiddle”, “popMiddle”, “popBack”, “popFront”]
[[], [1], [2], [3], [4], [], [], [], [], []]
输出:
[null, null, null, null, null, 1, 3, 4, 2, -1]
解释:
FrontMiddleBackQueue q = new FrontMiddleBackQueue();
q.pushFront(1); // [1]
q.pushBack(2); // [1, 2]
q.pushMiddle(3); // [1, 3, 2]
q.pushMiddle(4); // [1, 4, 3, 2]
q.popFront(); // 返回 1 -> [4, 3, 2]
q.popMiddle(); // 返回 3 -> [4, 2]
q.popMiddle(); // 返回 4 -> [2]
q.popBack(); // 返回 2 -> []
q.popFront(); // 返回 -1 -> [] (队列为空)

提示:

1
2
1 <= val <= 1e9
最多调用 1000 次 pushFront, pushMiddle, pushBack, popFront, popMiddle 和 popBack 。

题解

算法:链表

由于需要维护频繁的插入和删除操作。主体的数据结构应该是链表。在链表的头和尾的插入和删除比较简单。但这里还需要在链表中点处进行插入删除,因此需要维护一个指针,始终指向链表的中点 it_mid

记维护数据的链表为 list<int> l,指向头和尾的指针始终是 l.begin()l.end(),我们只需要注意指向中点的指针。

若链表长度为 $n$,我们始终让 it_mid 指向第 $\lfloor\frac{n}{2}\rfloor + 1$ 个元素。也就是 it_mid 始终指向下一个从中间插入时的插入位置。

这样的话,当 $n$ 为偶数时,popMiddle 需要删除 prev(it_mid) 位置,$n$ 为奇数时,popMiddle 需要删除 it_mid 位置。操作如下:

1
2
it_mid = l.erase(prev(it_mid)); // n 为偶数
it_mid = l.erase(it_mid); // n 为奇数

在队列的前面插入删除、后面插入删除,以及中间插入删除,均会对 it_mid 造成影响。下面分别来讨论。

首先看插入:

(1) 在表头插入,会使得 it_mid 左侧变多一个元素。

  • 若长度为奇数,则插入后 it_mid 不变。
  • 若长度为偶数,则插入后 it_mid = prev(it_mid)

(2) 在中间和表尾插入,都会使得 it_mid 右侧变多一个元素。

  • 若长度为奇数,则插入后 it_mid = next(it_mid)
  • 若长度为偶数,则插入后 it_mid 不变。

下面看删除的分类讨论:

(1) 在表头删除,会使得 it_mid 左侧变少一个元素。

  • 若长度为奇数,则删除后 it_mid = next(it_mid)
  • 若长度为偶数,则删除后 it_mid 不变。

(2) 在表尾删除,会使得 it_mid 右侧变少一个元素。

  • 若长度为奇数,则删除后 it_mid 不变。
  • 若长度为偶数,则删除后 it_mid = prev(it_mid)

(3) 在中间删除,前面讨论过:

1
2
it_mid = l.erase(prev(it_mid)); // n 为偶数
it_mid = l.erase(it_mid); // n 为奇数

需要注意的特殊情况是删除的时候,it_mid 会失效的情况:

  • 若 $n = 1$ 且从表头删除,则删除后 it_mid 会失效,置其为 l.end()
  • 若 $n = 2$ 且从表尾删除,则删除后 it_mid 会失效,置其为 l.begin()

插入后,若 $n=1$,则 it_mid 置为 l.begin()

l 为空时,it_midl.end()

代码 (C++)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
class FrontMiddleBackQueue {
public:
FrontMiddleBackQueue() {
l = list<int>();
n = 0;
it_mid = l.end();
}

void pushFront(int val) {
l.push_front(val);
n++;
if(n == 1)
it_mid = l.begin();
else if(n % 2 == 1)
it_mid = prev(it_mid);
}

void pushMiddle(int val) {
n++;
it_mid = l.insert(it_mid, val);
if(n == 1)
it_mid = l.begin();
if(n % 2 == 0)
it_mid = next(it_mid);
}

void pushBack(int val) {
n++;
l.push_back(val);
if(n == 1)
it_mid = l.begin();
else if(n % 2 == 0)
it_mid = next(it_mid);
}

int popFront() {
if(l.empty())
return -1;
int ans = l.front();
l.pop_front();
n--;
if(n == 0)
it_mid = l.end();
else if(n % 2 == 0)
it_mid = next(it_mid);
return ans;
}

int popMiddle() {
if(l.empty())
return -1;
int ans;
if(n % 2 == 1)
{
ans = *it_mid;
it_mid = l.erase(it_mid);
}
else
{
ans = *(prev(it_mid));
it_mid = l.erase(prev(it_mid));
}
n--;
if(n == 0)
it_mid = l.end();
return ans;
}

int popBack() {
if(l.empty())
return -1;
int ans = l.back();
l.pop_back();
n--;
if(n == 0)
it_mid = l.end();
else if(n == 1)
it_mid = l.begin();
else if(n % 2 == 1)
it_mid = prev(it_mid);
return ans;
}

private:
list<int> l;
int n;
list<int>::iterator it_mid;
};

Share