Files
leetcode-master/problems/kamacoder/0110.字符串接龙.md
2025-03-17 15:52:23 +08:00

363 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<p align="center"><strong><a href="./qita/join.md">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!</strong></p>
# 110. 字符串接龙
[卡码网题目链接ACM模式](https://kamacoder.com/problempage.php?pid=1183)
题目描述
字典 strList 中从字符串 beginStr 和 endStr 的转换序列是一个按下述规格形成的序列:
1. 序列中第一个字符串是 beginStr。
2. 序列中最后一个字符串是 endStr。
3. **每次转换只能改变一个位置的字符**(例如 ftr 可以转化 fty ,但 ftr 不能转化 frx
4. 转换过程中的中间字符串必须是字典 strList 中的字符串。
5. beginStr 和 endStr 不在 字典 strList 中
6. 字符串中只有小写的26个字母
给你两个字符串 beginStr 和 endStr 和一个字典 strList找到从 beginStr 到 endStr 的最短转换序列中的字符串数目。如果不存在这样的转换序列,返回 0。
输入描述
第一行包含一个整数 N表示字典 strList 中的字符串数量。 第二行包含两个字符串,用空格隔开,分别代表 beginStr 和 endStr。 后续 N 行,每行一个字符串,代表 strList 中的字符串。
输出描述
输出一个整数,代表从 beginStr 转换到 endStr 需要的最短转换序列中的字符串数量。如果不存在这样的转换序列,则输出 0。
输入示例
```
6
abc def
efc
dbc
ebc
dec
dfc
yhn
```
输出示例
4
提示信息
从 startStr 到 endStr在 strList 中最短的路径为 abc -> dbc -> dec -> def所以输出结果为 4
数据范围:
2 <= N <= 500
<p>
<img src="https://file.kamacoder.com/pics/20250317105155.png" alt="" width="50%" />
</p>
## 思路
以示例1为例从这个图中可以看出 abc 到 def的路线 不止一条但最短的一条路径上是4个节点。
![](https://file.kamacoder.com/pics/20250317105155.png)
本题只需要求出最短路径的长度就可以了,不用找出具体路径。
所以这道题要解决两个问题:
1、图中的线是如何连在一起的
在搜索的过程中我们可以枚举用26个字母替换当前字符串的每一个字符在看替换后 是否在 strList里出现过就可以判断 两个字符串 是否是链接的。
2、起点和终点的最短路径长度
首先题目中并没有给出点与点之间的连线,而是要我们自己去连,条件是字符只能差一个。
所以判断点与点之间的关系,需要判断是不是差一个字符,**如果差一个字符,那就是有链接**。
然后就是求起点和终点的最短路径长度,在无权图中,求最短路,用深搜或者广搜就行,没必要用最短路算法。
**在无权图中,用广搜求最短路最为合适,广搜只要搜到了终点,那么一定是最短的路径**。因为广搜就是以起点中心向四周扩散的搜索。
**本题如果用深搜,会比较麻烦,要在到达终点的不同路径中选则一条最短路**
而广搜只要达到终点,一定是最短路。
另外需要有一个注意点:
* 本题是一个无向图,需要用标记位,标记着节点是否走过,否则就会死循环!
* 使用set来检查字符串是否出现在字符串集合里更快一些
C++代码如下:(详细注释)
```CPP
#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <queue>
using namespace std;
int main() {
string beginStr, endStr, str;
int n;
cin >> n;
unordered_set<string> strSet;
cin >> beginStr >> endStr;
for (int i = 0; i < n; i++) {
cin >> str;
strSet.insert(str);
}
// 记录strSet里的字符串是否被访问过同时记录路径长度
unordered_map<string, int> visitMap; // <记录的字符串,路径长度>
// 初始化队列
queue<string> que;
que.push(beginStr);
// 初始化visitMap
visitMap.insert(pair<string, int>(beginStr, 1));
while(!que.empty()) {
string word = que.front();
que.pop();
int path = visitMap[word]; // 这个字符串在路径中的长度
// 开始在这个str中挨个字符去替换
for (int i = 0; i < word.size(); i++) {
string newWord = word; // 用一个新字符串替换str因为每次要置换一个字符
// 遍历26的字母
for (int j = 0 ; j < 26; j++) {
newWord[i] = j + 'a';
if (newWord == endStr) { // 发现替换字母后,字符串与终点字符串相同
cout << path + 1 << endl; // 找到了路径
return 0;
}
// 字符串集合里出现了newWord并且newWord没有被访问过
if (strSet.find(newWord) != strSet.end()
&& visitMap.find(newWord) == visitMap.end()) {
// 添加访问信息,并将新字符串放到队列中
visitMap.insert(pair<string, int>(newWord, path + 1));
que.push(newWord);
}
}
}
}
// 没找到输出0
cout << 0 << endl;
}
```
当然本题也可以用双向BFS就是从头尾两端进行搜索大家感兴趣可以自己去实现这里就不再做详细讲解了。
## 其他语言版本
### Java
```Java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
scanner.nextLine();
String beginStr = scanner.next();
String endStr = scanner.next();
scanner.nextLine();
List<String> wordList = new ArrayList<>();
wordList.add(beginStr);
wordList.add(endStr);
for (int i = 0; i < n; i++) {
wordList.add(scanner.nextLine());
}
int count = bfs(beginStr, endStr, wordList);
System.out.println(count);
}
/**
* 广度优先搜索-寻找最短路径
*/
public static int bfs(String beginStr, String endStr, List<String> wordList) {
int len = 1;
Set<String> set = new HashSet<>(wordList);
Set<String> visited = new HashSet<>();
Queue<String> q = new LinkedList<>();
visited.add(beginStr);
q.add(beginStr);
q.add(null);
while (!q.isEmpty()) {
String node = q.remove();
//上一层结束,若下一层还有节点进入下一层
if (node == null) {
if (!q.isEmpty()) {
len++;
q.add(null);
}
continue;
}
char[] charArray = node.toCharArray();
//寻找邻接节点
for (int i = 0; i < charArray.length; i++) {
//记录旧值,用于回滚修改
char old = charArray[i];
for (char j = 'a'; j <= 'z'; j++) {
charArray[i] = j;
String newWord = new String(charArray);
if (set.contains(newWord) && !visited.contains(newWord)) {
q.add(newWord);
visited.add(newWord);
//找到结尾
if (newWord.equals(endStr)) return len + 1;
}
}
charArray[i] = old;
}
}
return 0;
}
}
```
### Python
```Python
def judge(s1,s2):
count=0
for i in range(len(s1)):
if s1[i]!=s2[i]:
count+=1
return count==1
if __name__=='__main__':
n=int(input())
beginstr,endstr=map(str,input().split())
if beginstr==endstr:
print(0)
exit()
strlist=[]
for i in range(n):
strlist.append(input())
# use bfs
visit=[False for i in range(n)]
queue=[[beginstr,1]]
while queue:
str,step=queue.pop(0)
if judge(str,endstr):
print(step+1)
exit()
for i in range(n):
if visit[i]==False and judge(strlist[i],str):
visit[i]=True
queue.append([strlist[i],step+1])
print(0)
```
### Go
### Rust
### JavaScript
```javascript
const r1 = require('readline').createInterface({ input: process.stdin });
// 创建readline接口
let iter = r1[Symbol.asyncIterator]();
// 创建异步迭代器
const readline = async () => (await iter.next()).value;
let N //输入字符串个数
let beginStr //开始字符串
let endStr //结束字符串
let strSet = new Set() //字符串集合
let visitedMap = new Map() //访问过的字符串
// 读取输入,初始化地图
const init = async () => {
let line = await readline();
line = line.trim()
N = Number(line);
line = await readline();
line = line.trim().split(' ')
beginStr = line[0]
endStr = line[1]
for (let i = 0; i < N; i++) {
line = await readline()
line = line.trim()
strSet.add(line.trim())
}
}
(async function () {
// 读取输入,初始化数据
await init()
// 初始化队列
let queue = []
queue.push(beginStr)
// 初始化visitMap
visitedMap.set(beginStr, 1)
while (queue.length) {
let word = queue.shift()
let path = visitedMap.get(word)
// 遍历26个字母
for (let i = 0; i < word.length; i++) {
let newWord = word.split('') // 用一个新字符串替换str因为每次要置换一个字符
for (let j = 0; j < 26; j++) {
newWord[i] = String.fromCharCode('a'.charCodeAt() + j)
// 发现替换字母后,字符串与终点字符串相同
if (newWord.join('') === endStr) {
console.log(path + 1);
return 0; // 找到了路径
}
// 字符串集合里出现了newWord并且newWord没有被访问过
if (strSet.has(newWord.join('')) && !visitedMap.has(newWord.join(''))) {
// 添加访问信息,并将新字符串放到队列中
queue.push(newWord.join(''))
visitedMap.set(newWord.join(''), path + 1)
}
}
}
}
console.log(0);
})()
```
### TypeScript
### PhP
### Swift
### Scala
### C#
### Dart
### C