
题目考点
这道题主要考的是这几个知识点:
- 双端队列
deque - 字符串输入解析
- 条件判断
if / elif - 循环处理多条命令
- 按题目要求输出结果
这道题本质上不是在考你写复杂算法,而是在考你:
能不能把一行一行命令读进来,再正确地作用到数据结构上。
所以,这题的核心不是“算”,而是“根据命令操作容器”。
审题
输入是什么
第一行是一个整数 N,表示一共有多少次操作。
接下来 N 行,每一行是一条命令,可能是下面四种之一:
append xappendleft xpoppopleft
输出是什么
最后输出双端队列 d 中剩余的所有元素,并且元素之间要用空格隔开。
题目要求我们做什么
题目要求你先准备一个空的双端队列 d,然后按顺序执行每一条命令,最后把里面的内容打印出来。
容易忽略的点
这里有一个非常重要的细节:
有些命令有参数,有些没有参数。
比如:
append 1有参数1appendleft 4有参数4pop没有参数popleft没有参数
所以你不能把所有命令都按“方法名 + 参数”来处理,否则遇到 pop 和 popleft 就会出问题。
思路提示
先不要急着写代码,先把这题脑子里变成一个流程。
你可以这样想:
第一步:准备一个空的双端队列
题目已经告诉你要对空的 deque 操作,所以先创建一个空的 d。
第二步:循环读入每一条命令
因为第一行给了 N,说明后面正好有 N 条操作,所以我们循环 N 次,每次读一行。
第三步:把每一行命令拆开
比如输入这一行:
appendleft 4
你把它 split() 之后,就会变成:
['appendleft', '4']
再比如:
pop
拆开后就是:
['pop']
所以,我们通常先取第 0 个位置,判断这条命令是什么。
第四步:根据命令执行对应方法
- 如果是
append,就把后面的值加到右边 - 如果是
appendleft,就把后面的值加到左边 - 如果是
pop,就从右边删 - 如果是
popleft,就从左边删
第五步:输出 deque 里的内容
题目要求空格分隔,所以最后把 d 里的元素按空格输出即可。
完整设计思路
现在把这题完整拆成几个明确步骤。
第一步:导入 deque
Python 里双端队列不是直接内置名字,要从 collections 里导入:
from collections import deque
然后创建空双端队列:
d = deque()
第二步:读入操作次数
第一行是 N,表示总共有多少条命令:
n = int(input())
第三步:循环读命令
每次读一行,并用 split() 分开:
command = input().split()
这样:
append 1会变成['append', '1']pop会变成['pop']
所以,command[0] 一定是方法名。
第四步:分类处理命令
这是最关键的部分。
情况 1:append
如果 command[0] == 'append',说明要往右边加元素:
d.append(command[1])
情况 2:appendleft
如果 command[0] == 'appendleft',说明要往左边加元素:
d.appendleft(command[1])
情况 3:pop
如果 command[0] == 'pop',说明删右边最后一个元素:
d.pop()
情况 4:popleft
如果 command[0] == 'popleft',说明删左边第一个元素:
d.popleft()
第五步:输出结果
最后要求按空格打印,所以最方便的写法是:
print(*d)
这个 *d 的意思可以先简单理解成:
把 deque 里的所有元素一个一个拿出来交给 print()。
所以它会自动用空格隔开输出。
代码实现
下面给你一份最基础、最适合初学者的写法。
from collections import deque
d = deque()
n = int(input())
for _ in range(n):
command = input().split()
if command[0] == 'append':
d.append(command[1])
elif command[0] == 'appendleft':
d.appendleft(command[1])
elif command[0] == 'pop':
d.pop()
elif command[0] == 'popleft':
d.popleft()
print(*d)
运行演示
我们用题目的样例手动走一遍。
输入:
6
append 1
append 2
append 3
appendleft 4
pop
popleft
初始状态
d = deque([])
第 1 条命令:append 1
右边加入 1:
d = deque(['1'])
第 2 条命令:append 2
右边加入 2:
d = deque(['1', '2'])
第 3 条命令:append 3
右边加入 3:
d = deque(['1', '2', '3'])
第 4 条命令:appendleft 4
左边加入 4:
d = deque(['4', '1', '2', '3'])
第 5 条命令:pop
删除右边最后一个元素 3:
d = deque(['4', '1', '2'])
第 6 条命令:popleft
删除左边第一个元素 4:
d = deque(['1', '2'])
最后输出
1 2
这就和题目样例一致了。
这题最核心的理解
这道题真正的关键可以压缩成一句话:
每读到一条字符串命令,就把它翻译成对 deque 的一次真实操作。
也就是说,这题不是在考你“会不会 deque”,而是在考你:
能不能把输入的命令文本,正确映射成代码行为。
例如:
| 输入命令 | 对应代码行为 |
|---|---|
append 5 | d.append('5') |
appendleft 8 | d.appendleft('8') |
pop | d.pop() |
popleft | d.popleft() |
所以以后你看到这种题,要立刻想到:
- 先读操作次数
- 每次读一条命令
split()拆开- 用
if / elif判断命令类型 - 执行对应方法
补充说明:为什么这里常写 command = input().split()
这是这类题的一个非常常见模板。
因为像下面这些输入:
append 1
remove 5
insert 0 10
本质上都是:
一行里有“命令名 + 参数们”
而 split() 最适合做的事,就是把一行按空格切开,变成列表。
比如:
command = input().split()
如果输入是:
append 1
那么:
command == ['append', '1']
于是:
command[0]是命令名command[1]是参数
这就是为什么这类“命令题”里,input().split() 往往是核心模板。
补充说明:为什么这里不一定要把数字转成 int
你可能会问:
这里的 1、2、3 看起来像数字,为什么代码里直接用 command[1],不写 int(command[1])?
原因是:
这道题最后只是要求你把元素打印出来,并没有要求拿它们做加减乘除。
所以把它们当字符串存进去也完全可以,最后照样输出正确结果。
当然,如果你愿意,也可以写成:
d.append(int(command[1]))
也是能过的。
这里只是为了让代码更简单,直接存字符串就够了。
方法总结
以后做这种题,你可以按这个模板审题:
第一步:看题目是不是“命令驱动型”
如果题目给你很多行操作,例如:
- append
- insert
- remove
- pop
- reverse
这类题往往不是数学题,而是“按命令操作数据结构”的题。
第二步:先确定要操作的数据结构
这题是 deque,有些题可能是:
listsetdict
先搞清楚你要操作谁。
第三步:每条命令先 split()
把一整行拆成列表,再取:
command[0]看命令类型command[1]、command[2]看参数
第四步:用 if / elif 一条条映射
把“文本命令”映射成“真实代码”。
这一步是这类题的通用套路。
本节小结
这道题最应该学会的,不只是 deque 的四个方法,而是这整类题的解题方式:
- 先读操作次数
- 再逐条读取命令
- 用
split()拆分命令 - 用条件判断执行对应操作
- 最后按要求输出结果
也就是说,这类题的思维模式是:
题目给我“操作说明”,我就按顺序去驱动一个数据结构。
练习
你可以马上做一道同类型的小练习。
练习题
有一个空列表 lst,输入若干条命令,命令可能是:
append xremove xpop
请在所有命令执行结束后,输出列表中的元素,元素之间用空格隔开。
提示
你可以模仿这道题的思路:
- 先创建空列表
- 循环读取每条命令
- 用
split()拆开 - 判断命令类型并执行对应操作
- 最后输出列表内容
print(*d) 到底是什么意思
这句代码里,最核心的不是 print,而是前面的这个星号:
print(*d)
这里的 *d 不是乘法。
它在这里表示一种很重要的用法,叫做:
解包,或者你也可以先理解成:把容器里的元素一个一个拆出来。
先看 print(d) 和 print(*d) 的区别
假设有这样一个双端队列:
from collections import deque
d = deque(['1', '2', '3'])
写法 1:print(d)
print(d)
输出是:
deque(['1', '2', '3'])
这是因为你把整个 d 当成一个整体打印出来了。
写法 2:print(*d)
print(*d)
输出是:
1 2 3
这是因为 *d 把 d 里面的元素拆开了,相当于变成:
print('1', '2', '3')
而 print() 默认会在多个参数之间加空格,所以结果就是:
1 2 3
*d 可以怎么理解
你可以先把它理解成一句非常朴素的话:
把 d 这个容器打开,把里面的元素逐个拿出来。
比如:
d = deque(['1', '2', '3'])
print(*d)
可以脑子里想象成:
print('1', '2', '3')
这就是为什么这道题最后能直接这样输出。
为什么 print(*d) 正好适合这道题
因为题目要求的是:
按空格分隔输出双端队列里的元素。
而 print() 本身就有一个特点:
如果你给它多个参数,它会默认用空格连接。
比如:
print('a', 'b', 'c')
输出:
a b c
所以:
print(*d)
就是:
- 先把
d拆成多个元素 - 再交给
print() print()自动用空格隔开输出
这就刚好符合题目要求。
更具体一点:print() 本来就能接收很多参数
比如:
print(10, 20, 30)
输出:
10 20 30
说明 print() 不是只能打印一个东西,它可以打印很多个参数。
而 *d 的作用,就是把一个容器,变成“很多个参数”。
所以:
print(*d)
本质上就是把容器中的每个元素,当成 print() 的独立参数传进去。
一个非常直观的类比
假设有一个盒子:
d = ['苹果', '香蕉', '橘子']
不解包
print(d)
相当于你把整个盒子端给别人看。
别人看到的是:
['苹果', '香蕉', '橘子']
解包
print(*d)
相当于你把盒子打开,把里面的东西一个一个摆出来。
别人看到的是:
苹果 香蕉 橘子
* 只在这里有这个意思吗
不是。
* 在 Python 里有不同场景,不同含义。
场景 1:乘法
a = 3 * 5
这里的 * 是乘法。
场景 2:函数调用时解包
print(*d)
这里的 * 是解包。
场景 3:函数定义时收集参数
def func(*args):
print(args)
这里的 *args 是“把多个参数收集起来”。
你现在这题里遇到的,是第 2 种:
函数调用时的解包。
*d 适用于哪些数据类型
不只是 deque,只要是可以遍历的容器,通常都能这样用。
比如:
列表
a = [1, 2, 3]
print(*a)
输出:
1 2 3
元组
a = (1, 2, 3)
print(*a)
输出:
1 2 3
字符串
s = "abc"
print(*s)
输出:
a b c
因为字符串也可以一个字符一个字符地拆开。
deque
from collections import deque
d = deque([1, 2, 3])
print(*d)
输出:
1 2 3
为什么 deque 也能被 * 拆开
因为 deque 是一种可迭代对象。
你现在可以先把“可迭代”理解成:
可以一个一个取出里面元素的东西。
比如我们可以这样写:
for x in d:
print(x)
说明 deque 里的元素本来就能被一个一个访问。
既然它能一个一个拿出来,那么 *d 就能把它们拆开。
手动模拟一下 print(*d)
假设:
d = deque(['1', '2'])
执行:
print(*d)
你可以脑中把它理解成这两步:
第一步:解包
*d 把 d 拆成:
'1', '2'
第二步:传给 print
变成:
print('1', '2')
第三步:print() 默认用空格连接
输出:
1 2
如果写成 print(d) 为什么不行
因为题目要的是:
1 2
而不是:
deque(['1', '2'])
print(d) 打印的是这个对象本身的样子。
print(*d) 打印的是对象里面的元素。
这是本质区别。
如果 d 是空的,会怎么样
如果:
d = deque()
print(*d)
那么不会报错,只是输出一行空行。
因为“拆开后没有元素”,那 print() 就相当于没收到内容。
再补一层:print(*d, sep='-') 又是什么意思
因为 print() 默认参数之间用空格隔开,但你也可以改。
例如:
d = deque(['1', '2', '3'])
print(*d, sep='-')
输出:
1-2-3
说明:
*d负责拆开sep='-'负责指定分隔符
所以这题之所以不写 sep,是因为题目本来就要求空格分隔,而 print() 默认就是空格。
一句话总结 *d
你可以直接记住这句话:
*d 的意思就是:把容器 d 里面的元素一个一个拆出来,作为多个独立参数传给函数。
所以:
print(*d)
就等价于:
print(d里的第1个元素, d里的第2个元素, d里的第3个元素, ...)
本节小结
这句代码里:
print(*d)
d是一个双端队列*d不是乘法,而是解包- 解包就是把
d里的元素一个个拆出来 print()接收到多个参数后,会默认用空格输出- 所以它特别适合这道题的“空格分隔输出”
小练习
你先自己判断下面三句各会输出什么:
a = [10, 20, 30]
print(a)
print(*a)
print(*a, sep='-')
提示
你重点想这三个问题:
- 是打印整个列表,还是打印列表里的元素?
- 有没有解包?
- 分隔符是默认空格,还是
-?
先给结论:这两个 * 长得像,但方向正好相反
你现在看到的这两种写法:
print(*d)
和
def func(*args):
...
它们虽然都写了一个 *,但做的事情正好是反着来的。
你可以直接先记这一句:
- 函数调用里的
*:拆开 - 函数定义里的
*:收集
也就是:
- 调用时:
一个容器 -> 多个参数 - 定义时:
多个参数 -> 一个容器
这就是你说的“看起来像,但本质上方向正好相反”。
一、先看函数调用里的 *:它是在“拆开”
例如:
nums = [1, 2, 3]
print(*nums)
这里的 *nums,意思不是把 nums 当成一个整体传进去,而是把它拆开,相当于:
print(1, 2, 3)
所以这里的 * 是:
展开、解包、拆开。
这一步的数据流向
原来是一个列表:
[1, 2, 3]
经过 * 之后,变成多个独立参数:
1, 2, 3
所以这是:
一变多。
二、再看函数定义里的 *:它是在“收集”
例如:
def func(*args):
print(args)
然后你调用:
func(1, 2, 3)
输出是:
(1, 2, 3)
这里发生了什么?
调用时你给了 3 个独立参数:
1, 2, 3
但函数内部的 args 把它们收起来,变成了一个元组:
(1, 2, 3)
所以这里的 * 是:
收集、打包。
这一步的数据流向
原来是多个参数:
1, 2, 3
进入函数后,变成一个整体:
(1, 2, 3)
所以这是:
多变一。
三、为什么说它们“方向相反”
现在把两边并排看,你就会非常清楚。
1. 函数调用时的 *
nums = [1, 2, 3]
print(*nums)
含义:
把一个列表拆成多个参数
也就是:
[1, 2, 3] -> 1, 2, 3
2. 函数定义时的 *
def func(*args):
print(args)
调用:
func(1, 2, 3)
含义:
把多个参数收成一个元组
也就是:
1, 2, 3 -> (1, 2, 3)
所以你会发现:
- 调用里的
*:是把“整体”拆散 - 定义里的
*:是把“分散的”收拢
这就是“方向相反”的真正意思。
四、你可以把它理解成“门口”和“屋里”的两个动作
这是一个很适合初学者记忆的比喻。
假设函数是一间房子。
在房子外面调用函数时
你手里拿着一个箱子:
[1, 2, 3]
如果你写:
print(*nums)
就像是你在进门前,把箱子打开,把里面三个东西一个个拿出来交给函数。
所以是:
进门前拆开。
在房子里面定义函数时
你写:
def func(*args):
就像是函数说:
“你不管给我多少个东西,我都帮你装进一个袋子里,袋子名字叫 args。”
所以是:
进门后收集。
五、把这两步连起来看,会更透
下面这个例子特别值得看透。
def func(*args):
print(args)
nums = [1, 2, 3]
func(*nums)
我们一点一点看。
第一步:调用时 *nums
func(*nums)
这里是函数调用,所以 *nums 先把列表拆开:
func(1, 2, 3)
这是“解包”。
第二步:函数定义里的 *args
函数头是:
def func(*args):
意思是把传进来的多个参数收集到 args 里。
所以:
func(1, 2, 3)
进入函数后,相当于:
args = (1, 2, 3)
这是“打包”。
最终结果
所以这一整句:
func(*nums)
你可以理解成:
- 先把
nums拆开 - 再被
*args收进去
也就是:
[1, 2, 3] -> 1, 2, 3 -> (1, 2, 3)
前半段是“拆”,后半段是“收”。
这就是为什么它们看起来都是 *,但其实是在不同位置扮演反方向的角色。
六、为什么 Python 要这么设计
因为函数调用和函数定义,本来就是两个相反的过程。
函数调用
是你把数据“送进去”。
所以如果你手上拿着一个列表,Python 允许你用 * 把它拆成多个参数,更方便传参。
例如:
nums = [3, 5]
print(max(*nums))
相当于:
print(max(3, 5))
函数定义
是函数在“接收数据”。
所以 Python 允许你写 *args,表示:
“不管你给我几个位置参数,我都能统一接住。”
例如:
def total(*args):
return sum(args)
这样你可以写:
total(1, 2, 3)
total(10, 20)
total(7)
函数都能接住。
七、最容易混淆的地方
很多初学者看到这两个 *,会误以为:
“既然都是星号,那意思应该差不多吧?”
其实不对。
它们只有外形一样,但语境不同。
在函数调用里
func(*x)
这里你已经有一个容器 x 了,你要做的是:
把容器展开给函数看。
在函数定义里
def func(*x):
这里函数还没接到参数,它要做的是:
把传进来的多个参数收集到变量 x 中。
所以它们的共同点只是都和“参数处理”有关,但动作正好相反:
- 一个是把整体拆成多个
- 一个是把多个合成整体
八、用箭头记忆,会非常牢
你可以直接记下面这两条。
函数调用
func(*data)
记成:
一个容器 -> 多个参数
也就是:
[1, 2, 3] -> 1, 2, 3
函数定义
def func(*args):
记成:
多个参数 -> 一个元组
也就是:
1, 2, 3 -> (1, 2, 3)
九、顺手补一下:** 也是同样逻辑
虽然你现在重点是 *,但顺手知道一下 ** 会更完整。
调用时 **
d = {'sep': '-', 'end': '!\n'}
print(1, 2, 3, **d)
这里是把字典拆开成关键字参数。
定义时 **kwargs
def func(**kwargs):
print(kwargs)
这里是把多个关键字参数收集成字典。
所以和 * 一样,** 也是:
- 调用时:拆开
- 定义时:收集
只是 * 处理的是位置参数,** 处理的是关键字参数。
十、最后把你这次的问题压成一句真正能记住的话
你可以这样理解:
函数调用时的 *,站在“传参的人”角度,是把一个容器拆开。
函数定义时的 *,站在“收参的函数”角度,是把多个参数收起来。
也就是说:
- 调用者在“展开”
- 被调用者在“接住”
一个往外摊开,一个往里归拢,所以方向正好相反。
本节小结
最核心的结论就是这四句话:
1
print(*d)
表示把 d 里的元素拆开,传给 print()。
2
def func(*args):
表示把传进来的多个位置参数,收集到 args 这个元组里。
3
函数调用里的 * 是:
解包、展开、拆开。
4
函数定义里的 * 是:
收集、打包、归拢。
小练习
你自己先手动分析下面这段代码,不要急着运行:
def show(*args):
print(args)
a = [10, 20, 30]
show(*a)
提示
你按这两步想:
第一步,show(*a) 先会变成什么。
第二步,进入 def show(*args) 后,args 又会变成什么。



