方向一:学习方法与心得
一、题目展示: 套碗游戏的取碗顺序问题
问题描述
小F正在玩一个套碗的游戏,每个碗都有一个编号,从1到n,它们从大到小被套在一根木棍上。小F只能从木棍上取最上面的碗,每次只能取一个。现在你需要判断给定的取碗顺序是否可行。如果可行,那么返回1,否则返回0。
例如,对于2个碗,取碗的顺序可以是 2 1 或 1 2,这两种顺序都是合法的。而对于3个碗,给定顺序 3 1 2 不可能通过合法操作实现,因此该顺序不可行。
测试样例
样例1:
输入:
M = 2, a = [1, 2]
输出:1
样例2:
输入:
M = 3, a = [3, 1, 2]
输出:0
样例3:
输入:
M = 4, a = [1, 3, 2, 4]
输出:1
二、问题分析
1、题目理解
- 题目描述了一个套碗游戏的场景,碗从大到小按编号 1 到 n 套在一根木棍上,且规定只能从木棍上取最上面的碗,每次取一个。
- 给出了取碗的顺序序列,需要判断该给定的顺序是否能通过合法的取碗操作实现。
2、解题思路
-
可以利用栈(Stack)的数据结构来模拟这个取碗的过程。栈的特点是后进先出(LIFO),正好符合只能取最上面碗的规则。
-
具体步骤如下:
-
创建一个辅助栈,用于模拟木棍上套着碗的情况。
-
按照碗的编号从小到大依次将碗的编号压入辅助栈,模拟初始时碗从大到小套在木棍上的状态。
-
然后遍历给定的取碗顺序数组。对于数组中的每个元素(即要取的碗的编号):
- 在辅助栈中查找该编号,如果能找到且该编号在栈顶,说明可以按照规则取到这个碗,此时将栈顶元素弹出,表示取走了这个碗。
- 如果在辅助栈中找不到该编号,或者找到了但编号不在栈顶,那么说明给定的取碗顺序是不可行的,直接返回 0。
-
如果遍历完取碗顺序数组后,辅助栈为空,说明所有碗都按照给定顺序成功取走了,返回 1;否则返回 0。
-
3、示例分析
-
样例 1:
-
输入:M = 2,a = [1, 2]
-
解题过程:
- 首先创建辅助栈,将碗编号 1 和 2 依次压入栈(因为要模拟从大到小套在木棍上,所以先压入 1 再压入 2),此时栈顶元素为 2。
- 开始遍历取碗顺序数组,先取 1,在辅助栈中能找到 1 且 1 在栈顶,将栈顶元素 1 弹出。
- 接着取 2,在辅助栈中能找到 2 且 2 在栈顶(此时栈中只有 2 了),将栈顶元素 2 弹出。
- 此时辅助栈为空,说明所有碗都按照给定顺序成功取走了,所以返回 1。
-
-
样例 2:
-
输入:M = 3,a = [3, 1, 2]
-
解题过程:
- 创建辅助栈,将碗编号 1、2、3 依次压入栈(先压入 1,再压入 2,最后压入 3),此时栈顶元素为 3。
- 先取 3,在辅助栈中能找到 3 且 3 在栈顶,将栈顶元素 3 弹出。
- 接着取 1,在辅助栈中能找到 1 但 1 不在栈顶(此时栈顶元素是 2),说明无法按照给定顺序合法取到 1,所以返回 0。
-
-
样例 3:
-
输入:M = 4,a = [1, 3, 2, 4]
-
解题过程:
-
创建辅助栈,将碗编号 1、2、3、4 依次压入栈,此时栈顶元素为 4。
-
先取 1,在辅助栈中能找到 1 且 1 在栈顶,将栈顶元素 1 弹出。
-
接着取 3,在辅助栈中能找到 3 且 3 在栈顶(此时栈顶元素为 3),将栈顶元素 3 弹出。
-
再取 2,在辅助栈中能找到 2 且 2 在栈顶(此时栈顶元素为 2),将栈顶元素 2 弹出。
-
最后取 4,在辅助栈中能找到 4 且 4 在栈顶(此时栈顶元素为 4),将栈顶元素 4 弹出。
-
此时辅助栈为空,说明所有碗都按照给定顺序成功取走了,所以返回 1。
-
-
综上所述,通过利用栈来模拟取碗过程,可以有效地判断给定的取碗顺序是否可行。
三、代码展示
def solution(M: int, a: list) -> int:
# 初始化辅助栈
stack = []
# 当前木棍上的碗编号
current = 1
for target in a:
# 如果当前木棍上的碗编号与目标编号匹配
if current == target:
current += 1
# 如果辅助栈顶部的碗编号与目标编号匹配
elif stack and stack[-1] == target:
stack.pop()
else:
# 将木棍上的碗放入辅助栈,直到找到目标碗
while current <= M and current != target:
stack.append(current)
current += 1
# 如果找到目标碗,继续下一个目标
if current == target:
current += 1
else:
# 如果无法找到目标碗,返回0
return 0
# 如果所有碗都能按照目标顺序取出,返回1
return 1
if __name__ == '__main__':
print(solution(2, [1, 2]) == 1)
print(solution(3, [3, 1, 2]) == 0)
print(solution(4, [1, 3, 2, 4]) == 1)
四、代码总结分析
1、代码功能概述
根据题目设定的规则,通过模拟取碗过程来验证输入的取碗顺序能否在实际游戏场景中实现。
2、代码结构分析
(一)函数定义
-
solution(M: int, a: list) -> int:- 这是整个功能的核心函数,接受两个参数,
M表示碗的总数(编号范围从 1 到M),a是一个列表,表示给定的取碗顺序。函数最终返回一个整数,1 表示给定的取碗顺序可行,0 表示不可行。
- 这是整个功能的核心函数,接受两个参数,
(二)初始化部分
stack = []:创建一个空列表stack,用于作为辅助栈来模拟木棍上套着碗的情况。current = 1:初始化一个变量current,用于表示当前木棍上最上面的碗的编号,初始值设为 1,因为游戏中碗是从编号 1 开始的。
(三)循环遍历取碗顺序部分
-
for target in a::通过循环遍历给定的取碗顺序列表a中的每个元素,这里的target表示当前要取的碗的编号。-
条件判断分支:
-
if current == target::如果当前木棍上最上面的碗编号(由current表示)与要取的目标碗编号(target)相等,说明可以直接取走这个碗,此时将current的值加 1,以更新当前木棍上最上面的碗编号。 -
elif stack and stack[-1] == target::如果辅助栈stack不为空且栈顶元素(通过stack[-1]获取)与要取的目标碗编号相等,说明可以从辅助栈中取走这个碗(模拟从木棍上取碗的情况),此时通过stack.pop()将栈顶元素弹出。 -
else::如果前面两个条件都不满足,说明当前要取的碗既不在当前木棍最上面,也不在辅助栈顶,此时需要将木棍上的碗依次放入辅助栈,直到找到目标碗。while current <= M and current!= target::在这个循环中,只要当前碗的编号current小于等于碗的总数M且不等于目标碗编号target,就将当前碗编号放入辅助栈(通过stack.append(current)),并且将current的值加 1,继续检查下一个碗编号。if current == target::如果在上述循环结束后,找到了目标碗编号(即current等于target),说明可以取走这个碗,将current的值加 1,继续处理下一个目标碗。else::如果经过上述一系列操作后仍然无法找到目标碗,说明给定的取碗顺序不可行,直接返回 0。
-
-
(四)主程序部分
-
if __name__ == '__main__'::这是 Python 中常见的主程序入口判断语句。print(solution(2, [1, 2]) == 1):调用solution函数并传入参数2(表示碗的总数为 2)和[1, 2](表示取碗顺序),然后判断函数返回值是否等于 1,并将结果打印出来。print(solution(3, [3, 1, 2]) == 0):类似地,传入参数3和[3, 1, 2]调用solution函数,判断返回值是否等于 0 并打印结果。print(solution(4, [1, 3, 2, 4]) == 1):同样的操作,传入参数4和[1, 3, 2, 4],判断返回值是否等于 1 并打印结果。这几个语句主要用于对solution函数进行简单的测试,验证函数在不同输入情况下的正确性。
3、代码优点
- 逻辑清晰:通过多个条件判断分支和循环语句,清晰地模拟了根据给定取碗顺序从木棍上取碗的实际过程,代码的逻辑流程与题目描述的游戏规则紧密对应,易于理解。
- 功能完整:能够准确地根据输入的碗总数和取碗顺序判断该顺序是否可行,实现了题目要求的核心功能。
- 自带测试用例:在主程序部分通过简单的函数调用和结果判断,对
solution函数在几种典型输入情况下进行了测试,有助于快速验证函数的正确性,方便后续的调试和维护。
4、代码可能的改进点
- 代码注释:虽然代码逻辑相对清晰,但整体缺少详细的注释说明。添加适当的注释可以让其他开发者(包括未来可能修改代码的自己)更快速、准确地理解代码的每一步操作和意图。
- 错误处理:在当前代码中,如果输入的参数不符合预期(例如
M为负数,或者a列表中的元素不在 1 到M的范围内等),没有进行相应的错误处理机制。可以考虑添加一些条件判断来处理这些可能出现的异常情况,以提高代码的健壮性。 - 可扩展性:如果后续游戏规则有一些变化或者需要在该功能基础上进行拓展(比如添加更多关于碗的操作限制等),当前代码的结构可能需要较大幅度的调整。可以考虑采用更模块化的设计方式,将一些关键的操作步骤封装成独立的函数,以便于后续的扩展和维护。