【Python刷题】最少拐弯路线问题

在这里插入图片描述

题目描述

洛谷P1649
在这里插入图片描述

一、题目理解

首先,我们来看一下这道题目的要求。题目给定了一个 N×N(1≤N≤100) 的方格,方格中的每个格子有不同的状态,用 . 表示可以行走的格子,x 表示不能行走的格子,并且有起始点 A 和终点 B。我们要控制一个角色(比如题中的卡门或者贝西)从 A 点走到 B 点,这个角色因为自身特点不好转弯,只能沿着方格的边平行移动(也就是上下左右四个方向),然后求出从 AB 最少要转 90 度弯的次数。如果无法到达终点,那就输出 -1

二、整体解题思路

方案一

(一)初始化相关数据结构

  1. 记录方格状态
    我们首先创建一个字典 vis,用于记录方格中每个位置是否被访问过以及是否是障碍物。遍历整个 N×N 的方格,如果某个位置对应的字符是 x,那就把该位置在 vis 字典中标记为 1(表示不可访问,相当于障碍物),如果是 . 则标记为 0(表示可访问)。例如,对于下面这样一个简单的 3×3 方格示例:
. x A
. . .
B x .

我们会根据其内容初始化 vis 字典,像 (0, 0) 位置对应 .vis[(0, 0)] 就会被初始化为 0,而 (0, 1) 位置对应 xvis[(0, 1)] 就会被初始化为 1
2. 定义移动方向
定义一个列表 move,里面包含四个元组 [(1, 0), (0, -1), (-1, 0), (0, 1)],分别对应向下、向左、向上、向右这四个方向的坐标偏移量。例如,当前位置是 (i, j),如果按照 (1, 0) 这个方向移动,下一个位置就是 (i + 1, j),也就是向下移动一格。

(二)使用队列进行 BFS

  1. 队列初始化及起始点入队
    创建一个队列 q(这里使用 Python 的 queue.Queue),然后把起始点相关的信息放入队列。起始点信息包含起始位置坐标(也就是 start,它是一个二元组,比如 (i, j) 形式表示在方格中的位置),还有当前转弯次数(初始化为 0)以及当前的方向(初始化为 None,因为刚开始出发方向还不确定),整体就是 start + (0, None) 这样的形式放入队列 q 中,同时把起始点在 vis 字典中标记为已访问(vis[start] = 1)。
  2. 循环进行搜索
    只要队列 q 不为空,就持续进行循环搜索。每次从队列中取出一个元素,这个元素包含当前位置、转弯次数以及当前方向等信息,我们记为 now,从中提取出当前位置 cur_pos 和当前方向 cur_dir
  3. 判断是否到达终点
    如果当前位置 cur_pos 就是终点 end,那就意味着找到了一条从起点到终点的路径,把当前的转弯次数记录下来(也就是 now[2])作为答案,然后跳出循环。
  4. 探索相邻位置
    如果还没到达终点,那就遍历所有可能的移动方向(也就是前面定义的 move 列表中的四个方向)。对于每个方向 dir,计算按照这个方向移动后的新位置 new_pos,同时计算新的转弯次数 newstep。如果当前方向 cur_dirNone(也就是刚开始出发,还没确定方向)或者新方向 dir 和当前方向 cur_dir 不一样,那就意味着要转弯了,需要把转弯次数 newstep1
    然后,只要新位置 new_pos 在方格范围内(也就是 0 <= new_pos[0] < n0 <= new_pos[1] < n)并且该位置不是障碍物(vis.get(new_pos, 0) == 0),就把这个新位置标记为已访问(vis[new_pos] = 1),然后把新位置以及更新后的转弯次数、新方向这些信息作为一个整体(也就是 new_pos + (newstep, dir))放入队列 q 中,接着继续沿着这个方向探索下一个位置(也就是更新 new_pos,继续 new_pos = (new_pos[0] + dir[0], new_pos[1] + dir[1]) 这样移动),直到遇到边界或者障碍物为止。

(三)处理最终结果

最后,如果找到了从起点到终点的路径(也就是 ans 不等于 -1),按照题目要求需要把记录的转弯次数减 1(因为在放入队列时,初始的转弯次数是 0,而第一次移动时如果转弯了才开始计数,所以最后要减 1)返回;如果没有找到路径,那就直接返回 -1

代码实现

import queuedef find_min_turns(n, start, end, board):vis = {}for i in range(n):for j in range(n):if board[i][j] == 'x':vis[(i, j)] = 1else:vis[(i, j)] = 0move = [(1, 0), (0, -1), (-1, 0), (0, 1)]q = queue.Queue()q.put(start + (0, None))vis[start] = 1  ans = -1while not q.empty():now = q.get()cur_pos = now[:2]if cur_pos == end:ans = now[2]breakelse:cur_dir = now[3]for dir in move:new_pos = (cur_pos[0] + dir[0], cur_pos[1] + dir[1])newstep = now[2]if cur_dir is None or dir!= cur_dir:newstep += 1while 0 <= new_pos[0] < n and 0 <= new_pos[1] < n and vis.get(new_pos, 0) == 0:vis[new_pos] = 1  q.put(new_pos + (newstep, dir))new_pos = (new_pos[0] + dir[0], new_pos[1] + dir[1])return ans - 1 if ans!= -1 else ansn = int(input())
board = [list(input().split()) for _ in range(n)]
start = None
end = None
for i in range(n):for j in range(n):if board[i][j] == 'A':start = (i, j)elif board[i][j] == 'B':end = (i, j)
result = find_min_turns(n, start, end, board)
print(result)

方案二

上述的方法虽然过了这个题目,但是我手搓了一个样例他没有过

7
x x x x x x x
x x . A x x x
x x . . . x x
x x . . x x x
x x . x x x x
x x . B x x x
x x x x x x x

对于这个样例用bfs就存在问题,他不是
→ \rightarrow → \rightarrow 右,
而是下 → \rightarrow → \rightarrow → \rightarrow
这样一来就不符合题意了,因为没有找到最小的转弯次数的路线。

代码

C++

#include <bits/stdc++.h>
using namespace std;int n, x0, y0, xn, yn, ans = 0x7fffffff / 2, bj;
char l;
int a[105][105];
int dx[4] = {1, 0, -1, 0};
int dy[4] = {0, -1, 0, 1};void Read()
{scanf("%d", &n);for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++){cin >> l;if (l == 'A')x0 = i, y0 = j, a[i][j] = -1;if (l == 'B')xn = i, yn = j, a[i][j] = 0;if (l == 'x')a[i][j] = -1;}
}void dfs(int x, int y, int t, int k)
{if (k >= ans)return;if (x == xn && y == yn){ans = min(ans, k);bj = 1;return;}for (int i = 0; i < 4; i++){int nx = dx[i] + x;int ny = dy[i] + y;if (nx <= 0 || nx > n || ny <= 0 || ny > n)continue;if (!a[nx][ny]){a[nx][ny] = -1;int f = 0;if (i != t)f = 1;if (t == -1)f = 0;dfs(nx, ny, i, k + f);a[nx][ny] = 0;}}
}int main()
{Read();dfs(x0, y0, -1, 0);if (bj)printf("%d", ans);elseprintf("-1");return 0;
}

Python

def find_min_turns(matrix, start, end, n):directions = [(1, 0), (0, -1), (-1, 0), (0, 1)]  # 右 下 左 上,分别对应方向索引0、1、2、3ans = float('inf')  # 初始化最少转弯次数为正无穷,后续取最小值来更新bj = False  # 标记是否找到终点def dfs(x, y, prev_direction, turn_count):nonlocal ans, bjif turn_count >= ans:# 如果当前转弯次数已经大于等于已知的最少转弯次数,直接返回,进行剪枝returnif (x, y) == end:# 如果到达终点,更新最少转弯次数,并标记已找到终点ans = min(ans, turn_count)bj = Truereturnfor i in range(4):new_x, new_y = x + directions[i][0], y + directions[i][1]if not (0 <= new_x < n and 0 <= new_y < n):# 判断新位置是否在棋盘范围内,如果超出范围则跳过该方向continueif matrix[new_x][new_y]!= 'x':# 如果新位置不是障碍物,则可以尝试往这个方向移动original_value = matrix[new_x][new_y]matrix[new_x][new_y] = 'x'  # 暂时标记为已访问(等同于障碍物),避免重复访问# 判断是否转弯,根据当前方向和上一步方向是否不同来确定additional_turn = 1 if i!= prev_direction and prev_direction!= -1 else 0dfs(new_x, new_y, i, turn_count + additional_turn)matrix[new_x][new_y] = original_value  # 回溯,恢复该位置原来的值dfs(start[0], start[1], -1, 0)return ans if bj else -1n = int(input())
matrix = [input().split() for _ in range(n)]
for i in range(n):for j in range(n):if matrix[i][j] == 'A':start = (i, j)elif matrix[i][j] == 'B':end = (i, j)
result = find_min_turns(matrix, start, end, n)
print(result)

一、主要变量说明

  1. 主要变量
    • directions:一个包含四个元组的列表,[(1, 0), (0, -1), (-1, 0), (0, 1)],分别对应向右、向下、向左、向上四个方向上坐标的变化量,用于在棋盘上计算移动后的新位置。例如,(1, 0)表示在x(行)方向增加1,y(列)方向不变,即向右移动一格。
    • ans:初始化为正无穷大(float('inf')),用于记录在搜索过程中发现的从起点到终点的最少转弯次数,后续会不断更新取最小值。
    • bj:布尔类型变量,初始化为False,用于标记是否已经找到终点,当找到终点后将其置为True

三、核心算法逻辑(dfs函数部分)

dfs函数是基于深度优先搜索(DFS)的递归函数,用于遍历从起点开始的所有可能路径,并计算转弯次数,其详细逻辑如下:

  1. 剪枝操作(提前返回情况)
    dfs函数开头,有如下判断语句:
if turn_count >= ans:return

这是一种剪枝操作,如果当前路径已经走过的转弯次数(turn_count)大于等于已经记录的最少转弯次数(ans),那么就没必要再继续沿着这条路径探索下去了,直接返回,避免不必要的计算,提高搜索效率。

  1. 到达终点情况判断
if (x, y) == end:ans = min(ans, turn_count)bj = Truereturn

当当前位置((x, y))与终点坐标(end)相等时,说明找到了一条从起点到终点的路径。此时,更新最少转弯次数(取当前记录的最少转弯次数和这条路径的转弯次数中的较小值),并将bj标记置为True表示已经找到终点,然后返回结束当前递归分支的探索。

  1. 遍历四个方向探索新位置
    通过以下循环遍历四个方向:
for i in range(4):new_x, new_y = x + directions[i][0], y + directions[i][1]if not (0 <= new_x < n and 0 <= new_y < n):continueif matrix[new_x][new_y]!= 'x':...
  • 首先,根据directions数组中定义的方向变化量,计算在当前位置((x, y))向第i个方向移动后的新位置坐标(new_xnew_y)。
  • 接着,判断新位置是否在棋盘范围内(通过判断行列索引是否满足0 <= new_x < n0 <= new_y < n),如果超出范围则跳过该方向,继续尝试下一个方向。
  • 如果新位置不是障碍物(即matrix[new_x][new_y]!= 'x'),则可以尝试往这个方向移动。
  1. 标记、转弯判断及递归调用
    当确定可以往某个方向移动时,执行以下操作:
original_value = matrix[new_x][new_y]
matrix[new_x][new_y] = 'x'
additional_turn = 1 if i!= prev_direction and prev_direction!= -1 else 0
dfs(new_x, new_y, i, turn_count + additional_turn)
matrix[new_x][new_y] = original_value
  • 先保存新位置原本的值(通过original_value变量),然后将新位置标记为'x'(等同于设置为障碍物),这样做是为了避免在同一次搜索中重复访问该位置,防止陷入死循环。
  • 通过判断当前方向(i)和上一步的方向(prev_direction)是否不同(并且上一步方向不是初始的-1情况)来确定是否需要转弯,如果不同则转弯次数加1(additional_turn为1),否则不加(additional_turn为0)。
  • 接着,以新位置(new_xnew_y)、当前方向(i)以及更新后的转弯次数(turn_count + additional_turn)为参数,递归调用dfs函数,继续探索从新位置出发的路径情况。
  • 最后,在递归调用结束后(无论是否找到终点或者探索完该分支),通过matrix[new_x][new_y] = original_value语句将新位置恢复为原来的值,进行回溯操作,以便后续其他路径探索时该位置能正常被访问。

四、主程序部分

  1. 输入棋盘大小及构建棋盘矩阵
n = int(input())
matrix = [input().split() for _ in range(n)]

首先通过input()函数获取用户输入的棋盘边长n,然后通过列表推导式构建二维的棋盘矩阵matrix,每一行的元素通过split()方法对输入的字符串进行分割得到(假设输入的每行字符串是以空格等分隔符隔开的字符元素)。

  1. 寻找起点和终点坐标
for i in range(n):for j in range(n):if matrix[i][j] == 'A':start = (i, j)elif matrix[i][j] == 'B':end = (i, j)

通过两层嵌套的循环遍历整个棋盘矩阵,找到标记为'A'的起点坐标赋值给start变量,找到标记为'B'的终点坐标赋值给end变量。

  1. 调用函数并输出结果
result = find_min_turns(matrix, start, end, n)
print(result)

最后,调用find_min_turns函数传入构建好的棋盘矩阵、起点坐标、终点坐标以及棋盘边长参数,获取从起点到终点的最少转弯次数结果,并将结果打印输出。如果无法到达终点,函数会按照逻辑返回-1并输出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/18733.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

在windows系统里面部署 Redis

在windows中下载安装REdis 1.下载mis 地址添加链接描述 然后直接下载安装然后点击你的库 2.然后选择好之后选择好路径就行了。 然后我们点击这个cli.exe文件然后双击打开输入 在命令框里输入&#xff1a; 如果显示的和图片显示的一样&#xff0c;则证明你已经在本地部署好了…

NTP博客

使用nmtui命令修改IP&#xff1a; 注意&#xff1a; 修改之后&#xff0c;要激活&#xff1a; nmcli connection up ens160 1、软件安装 #设置当前时区 [rootlocalhost ~]# timedatectl set-timezone Asia/Shanghai 1.1.配置国内阿里yum源 [rootredhat ~]# cd /etc/yum.r…

《Large-scale Multi-modal Pre-trained Models: A Comprehensive Survey》中文校对版

文章汉化系列目录 文章目录 文章汉化系列目录摘要引言2 背景2.1 传统深度学习2.2 自然语言处理中的预训练2.3 计算机视觉中的预训练2.4 音频与语音中的预训练 3 多模态预训练3.1 任务定义与关键挑战3.2 MM-PTM的优势3.3 预训练数据3.4 预训练目标3.5 预训练网络架构3.5.1 自注意…

从源码角度分析JDK动态代理

文章目录 前言一、JDK动态代理二、动态代理的生成三、invoke的运行时调用总结 前言 本篇从源码的角度&#xff0c;对JDK动态代理的实现&#xff0c;工作原理做简要分析。 一、JDK动态代理 JDK动态代理是运行时动态代理的一种实现&#xff0c;相比较于CGLIB &#xff0c;目标对象…

操作系统——计算机系统概述——1.5操作系统引导(开机过程)

操作系统引导&#xff1a; A.CPU从一个特定主存地址开始&#xff0c;取指令&#xff0c;执行ROM中的引导程序&#xff08;先进行硬件自检&#xff0c;再开机&#xff09; B.将磁盘的第一块——主引导记录读入内存&#xff0c;执行磁盘引导程序&#xff0c;扫描分区表 C.从活动分…

推荐一本python学习书:《编程不难》

推荐理由 全面&#xff1a;把零基础Python编程、可视化、数学、数据、机器学习&#xff0c;融合在一起&#xff0c;循循渐进。 开源&#xff1a;PDF、Python代码、Jupyter文档&#xff0c;在github直接免费下&#xff01; 真实&#xff1a;提供大量真实场景下的数据&#xff…

数据结构与算法分析模拟试题及答案5

模拟试题&#xff08;五&#xff09; 一、单项选择题&#xff08;每小题 2 分&#xff0c;共20分&#xff09; &#xff08;1&#xff09;队列的特点是&#xff08;   &#xff09;。 A&#xff09;先进后出 B&#xff09;先进先出 C&#xff09;任意位置进出 D&#xff0…

集群聊天服务器(9)一对一聊天功能

目录 一对一聊天离线消息服务器异常处理 一对一聊天 先新添一个消息码 在业务层增加该业务 没有绑定事件处理器的话消息会派发不出去 聊天其实是服务器做一个中转 现在同时登录两个账号 收到了聊天信息 再回复一下 离线消息 声明中提供接口和方法 张三对离线的李…

jedis基础入门

jedis采用key&#xff0c;value的形式保存数据&#xff0c;使用nosql sql和nosql的区别 一&#xff1a;入门案例 导入依赖 <dependencies><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>…

QWen2.5学习

配置环境 pip install transformers 记得更新一下&#xff1a;typing_extensions pip install --upgrade typing_extensions 安装modelscope modelscope/modelscope: ModelScope: bring the notion of Model-as-a-Service to life. 下载这个仓库的代码上传到服务器解压 推…

足球青训俱乐部管理后台系统(程序+数据库+报告)

基于SpringBoot的足球青训俱乐部管理后台系统&#xff0c;系统包含两种角色&#xff1a;管理员、用户,系统分为前台和后台两大模块 编程语言&#xff1a;Java 数据库&#xff1a;MySQL 项目管理工具&#xff1a;Maven 前端技术&#xff1a;Vue 后端技术&#xff1a;SpringBoot…

MoneyPrinterTurbo - AI自动生成高清短视频

MoneyPrinterTurbo是一款基于AI大模型的开源软件&#xff0c;旨在通过一键操作帮助用户自动生成高清短视频。只需提供一个视频 主题或 **关键词** &#xff0c;就可以全自动生成视频文案、视频素材、视频字幕、视频背景音乐&#xff0c;然后合成一个高清的短视频。 ​ ​ 主要…

Cross-Inlining Binary Function Similarity Detection

注&#xff1a;在阅读该论文时顺便参考了作者团队的分享视频&#xff1a;【ICSE 2024论文预讲会-第二期-下午-哔哩哔哩】 https://b23.tv/XUVAPy3 在这个视频的末尾最后一个 一.introducion 计算下面两个函数的相似度&#xff1a; 查询函数&#xff1a;脆弱函数&#xff0c;重…

C++:哈希拓展-位图

目录 一.问题导入 二.什么是位图? 2.1如何确定目标数在哪个比特位? 2.2如何存放高低位 2.3位图模拟代码实现 2.3.1如何标记一个数 2.3.2如何重置标记 2.3.3如何检查一个数是否被标记 整体代码实现 标准库的Bitset 库中的bitset的缺陷 简单应用 一.问题导入 这道…

GCP : Memcache backed by Cloud Datastore

Memcache backed by Cloud Datastore 的用途主要体现在以下几个方面&#xff1a; 提高性能和可扩展性&#xff1a; Memcache 是一个高性能的分布式内存对象缓存系统&#xff0c;通常用于缓存数据库查询等操作&#xff0c;以减轻数据库负载&#xff0c;加快动态Web应用的响应速度…

【Python】问题解决:yaml文件加载得到字符串而不是字典

问题描述 最近需要使用python处理yaml文件&#xff0c;但使用过程中发现只能输出字符串的格式&#xff0c;而不是想要的字典格式。 基本使用 在python中想要读写yaml文件&#xff0c;可以安装使用第三方包pyyaml来实现&#xff0c;首先安装一下&#xff1a; pip install pyya…

时钟之Canvas+JS版

写在前面 上一篇介绍使用CSSJS方式实现&#xff0c;但元素太过单一。此篇将以HTML5的canvas标签结合JS来实现。 HTML代码 <canvas id"clock" width"300" height"300"></canvas> JS代码 var canvas null; var ctx null; var int…

shell脚本_创建执行与变量的定义与使用

PS:前言本章节讲解使用的系统为linux2024.1&#xff0c;基于Debian的Linux发行版。 一、什么是shell脚本&#xff1f; 1. 定义&#xff1a; 2. 主要特点&#xff1a; 3. shell脚本的基本结构 4. Shebang 二、创建执行 1.脚本的创建 2. 脚本的执行 2.1.chmod 2.2. 使用…

CSP/信奥赛C++语法基础刷题训练(11):洛谷P5743:猴子吃桃

CSP/信奥赛C语法基础刷题训练&#xff08;11&#xff09;&#xff1a;洛谷P5743&#xff1a;猴子吃桃 题目描述 一只小猴买了若干个桃子。第一天他刚好吃了这些桃子的一半&#xff0c;又贪嘴多吃了一个&#xff1b;接下来的每一天它都会吃剩余的桃子的一半外加一个。第 n n n…

C++11(四)---可变参数模板

文章目录 可变参数模板 可变参数模板 参数包代表多个类型和参数 // Args是一个模板参数包&#xff0c;args是一个函数形参参数包 // 声明一个参数包Args...args&#xff0c;这个参数包中可以包含0到任意个模板参数。 template <class ...Args> void ShowList(Args... arg…