小F的棋子移动代码
# 初始化 dp 数组,False 表示必败,True 表示必胜
dp = [[False] * m for _ in range(n)]
# 从右下角开始计算
for i in range(n - 1, -1, -1): # 从最后一行向上遍历
for j in range(m - 1, -1, -1): # 从最后一列向左遍历
# 如果已经在棋盘的边界,不能再移动,则是必败状态
if i == n - 1 and j == m - 1:
dp[i][j] = False
continue
# 检查可以移动的方向
# 向右移动:步数是奇数,1, 3, 5, ...
for step in range(1, max(n, m), 2): # 步长是奇数
# 向右移动
if j + step < m and not dp[i][j + step]:
dp[i][j] = True
break
# 向上移动
if i + step < n and not dp[i + step][j]:
dp[i][j] = True
break
# 初始位置 (0, 0) 是否是必胜状态
return "Yes" if dp[0][0] else "No"
# 测试代码
if __name__ == '__main__':
print(solution(1, 1) == "No") # 输出 No
print(solution(1, 4) == "Yes") # 输出 Yes
print(solution(4, 1) == "Yes") # 输出 Yes
print(solution(4, 4) == "No") # 输出 No
解题思路
问题分析
题目中我们可以知道棋盘的大小是 n×m,起始位置是左上角 (1,1),该位置表示为数组的索引 (0, 0)。根据规则每次小F可以选择向右或向上移动棋子,并且步数必须是奇数。此外移动到棋盘外的玩家也被判定为输掉游戏。我们需要判断小F是否能在初始位置必胜。该问题为博弈问题,那么在博弈问题中,我们需要判断每个位置是否是“必胜”状态。故可假设当前位置 (i,j):
- 假设从 (i,j)位置出发,小F有任何一种有效的移动方式能让对方进入必败状态,那么当前状态就是必胜状态
- 如果所有可能的移动都会让对方进入必胜状态,那么当前位置就是必败状态
步骤
-
初始化:创建一个大小为 n×m 的 DP 表格,用来记录每个位置的状态。
True表示必胜状态,False表示必败状态。 -
边界条件:对于棋盘的右下角位置,如果当前无法移动,则是必败状态。
-
状态转移:从右下角开始,向上和向左遍历每个位置,检查是否可以通过移动到另一个位置让对方进入必败状态。
-
判断最终结果:最终我们要关注的就是起始位置 (0, 0)的状态
代码分析
-
初始化DP数组- 初始化 DP 数组:
dp = [[False] * m for _ in range(n)]创建一个 n×mn \times mn×m 的二维数组
dp,初始化为False。表示每个位置的初始状态是必败状态
-
从右下角向上遍历:
for i in range(n - 1, -1, -1): # 从最后一行向上遍历 for j in range(m - 1, -1, -1): # 从最后一列向左遍历从棋盘的右下角开始遍历,逐步推导每个位置的状态。
-
边界条件:
if i == n - 1 and j == m - 1: dp[i][j] = False continue对于右下角的
(n-1, m-1)位置,无法再移动,因此是必败状态 -
检查可以向右和向上移动的步骤:
for step in range(1, max(n, m), 2): # 步长是奇数 # 向右移动 if j + step < m and not dp[i][j + step]: dp[i][j] = True break # 向上移动 if i + step < n and not dp[i + step][j]: dp[i][j] = True break对于当前位置
(i, j),我们检查是否能通过向右或向上移动进入对方的必败状态。步数必须是奇数 -
返回最终结果:
return "Yes" if dp[0][0] else "No"最后返回起始位置
(0, 0)的状态,若为True则说明小F可以必胜,返回"Yes",否则返回"No"
优化思考
是否可以通过优化步长的计算、空间的使用以及减少多余的计算等,在保持了代码的可读性同时,是否可以将这段代码的性能进行进一步的优化