news 2026/10/10 1:57:00

宫水三叶的刷题日记:LeetCode 821 字符的最短距离——两次遍历模拟与多源 BFS 双解法详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
宫水三叶的刷题日记:LeetCode 821 字符的最短距离——两次遍历模拟与多源 BFS 双解法详解
  • 教程
  • 文档

【免费下载链接】LogicStack-LeetCode

公众号「宫水三叶的刷题日记」刷穿 LeetCode 系列文章源码

项目地址:https://gitcode.com/gh_mirrors/lo/LogicStack-LeetCode
点击查看免费下载

导读

本文基于「宫水三叶的刷题日记」刷穿 LeetCode 系列的第 821 篇题解(对应仓库文档 LeetCode/821-830/821. 字符的最短距离(简单).md),完整解析一道难度为「简单」的字符串距离计算问题:给定字符串s与字符c,为s中每个下标计算到最近一个c字符的绝对距离。文章将继承原题解给出的「两次遍历模拟」与「多源 BFS」两种解法思路,并补充 Java、C++、Python、TypeScript 四种语言的完整可运行代码、边界条件推演与复杂度对比。读完本文,你将掌握「分方向扫描求最近距离」这一通用模拟技巧,以及把一维数组上的最近距离问题建模为「多源 BFS」的抽象方法,为后续刷同类字符/数组距离题(如推多米诺、蜡烛之间的盘子等)打下基础。

题目背景与题意分析

本题为 LeetCode 第 821 题「字符的最短距离」(Shortest Distance to a Character),难度为简单,原题解中标注的 Tag 为「模拟」「BFS」。在仓库的 Index/模拟.md 与 Index/BFS.md 两张索引表中,本题均被收录,且推荐指数为 🤩🤩🤩🤩,属于值得反复练习的入门级双解法例题。

题目描述如下:

给你一个字符串s和一个字符c,且c是s中出现过的字符。返回一个整数数组answer,其中answer.length = s.length且answer[i]是s中从下标i到离它最近的字符c的距离。两个下标i和j之间的距离为abs(i - j),其中abs是绝对值函数。

示例推演

示例 1:

输入:s = "loveleetcode", c = "e" 输出:[3,2,1,0,1,0,0,1,2,2,1,0]

字符'e'出现在下标 3、5、6 和 11 处(下标从 0 开始计数):

  • 距下标 0 最近的'e'出现在下标 3,所以距离为abs(0 - 3) = 3;
  • 距下标 1 最近的'e'出现在下标 3,所以距离为abs(1 - 3) = 2;
  • 对于下标 4,出现在下标 3 和下标 5 处的'e'都离它最近,但距离相同:abs(4 - 3) == abs(4 - 5) = 1;
  • 距下标 8 最近的'e'出现在下标 6,所以距离为abs(8 - 6) = 2。

示例 2:

输入:s = "aaab", c = "b" 输出:[3,2,1,0]

数据范围提示

  • 1 <= s.length <= 10^4;
  • s[i]和c均为小写英文字母;
  • 题目数据保证c在s中至少出现一次。

数据范围决定了两种解法(均为 $O(n)$ 时间)都能轻松通过;而「c至少出现一次」这一保证,是两种解法在实现细节上可以简化处理的依据。

解法一:两次遍历模拟(推荐掌握)

核心思路

根据题意直接模拟:第一次从左到右遍历,找到每个下标i左边最近的c;第二次从右到左遍历,找到每个下标i右边最近的c。两者取较小值,即为该位置到最近c的距离。

这里有一个关键的实现技巧:用变量j记录当前方向上最近一次遇到c的下标,初始化为-1(表示尚未遇到)。由于题目保证c至少出现一次,正向遍历结束后所有位置都必然已被左边某个c覆盖;反向遍历则用于修正「右边的c更近」的情况。

另一个关键点是答案数组的初始值:原题解将ans全部初始化为n + 1(一个大于任何可能距离的值,因为两点间最大距离为n - 1)。这样在正向遍历中尚未被任何c覆盖的位置,其值n + 1会在反向遍历时被右侧最近的c修正;而所有位置最终都会被修正为真实距离,不会残留初始值。即使不使用n + 1而直接使用Integer.MAX_VALUE之类的极大值,效果也相同——选择n + 1是因为它足够大且计算安全。

Java 实现

class Solution { public int[] shortestToChar(String s, char c) { int n = s.length(); int[] ans = new int[n]; Arrays.fill(ans, n + 1); // 第一趟:从左到右,找每个 i 左边最近的 c for (int i = 0, j = -1; i < n; i++) { if (s.charAt(i) == c) j = i; if (j != -1) ans[i] = i - j; } // 第二趟:从右到左,找每个 i 右边最近的 c,并与左边结果取 min for (int i = n - 1, j = -1; i >= 0; i--) { if (s.charAt(i) == c) j = i; if (j != -1) ans[i] = Math.min(ans[i], j - i); } return ans; } }

C++ 实现

class Solution { public: vector<int> shortestToChar(string s, char c) { int n = s.length(); vector<int> ans(n, n + 1); for (int i = 0, j = -1; i < n; i++) { if (s[i] == c) j = i; if (j != -1) ans[i] = i - j; } for (int i = n - 1, j = -1; i >= 0; i--) { if (s[i] == c) j = i; if (j != -1) ans[i] = min(ans[i], j - i); } return ans; } };

Python 实现

class Solution: def shortestToChar(self, s: str, c: str) -> List[int]: n = len(s) ans = [n + 1] * n # 第一趟:从左到右 j = -1 for i in range(n): if s[i] == c: j = i if j != -1: ans[i] = i - j # 第二趟:从右到左 j = -1 for i in range(n - 1, -1, -1): if s[i] == c: j = i if j != -1: ans[i] = min(ans[i], j - i) return ans

TypeScript 实现

function shortestToChar(s: string, c: string): number[] { const n = s.length; const ans = new Array(n).fill(n + 1); for (let i = 0, j = -1; i < n; i++) { if (s.charAt(i) === c) j = i; if (j !== -1) ans[i] = i - j; } for (let i = n - 1, j = -1; i >= 0; i--) { if (s.charAt(i) === c) j = i; if (j !== -1) ans[i] = Math.min(ans[i], j - i); } return ans; }

边界情况推演

以s = "aaab", c = "b"为例:

  • 正向遍历后,ans = [3, 2, 1, 0](下标 3 是'b',距离为 0;其余位置记录到左边最近'b'的距离,即3 - i);
  • 反向遍历时,j从下标 3 开始一直保持为 3,j - i恒等于3 - i,与正向结果相同,min取后结果不变。

再以s = "loveleetcode", c = "e"为例,正向遍历只能保证每个位置记录的是「到左侧最近e」的距离,例如下标 7('t')正向得到7 - 6 = 1,反向时最近e仍在下标 6,得到6 - 7 = -1的绝对值为 1,min(1, 1) = 1;而下标 9('d')正向得到9 - 6 = 3,反向时最近e在下标 11,得到11 - 9 = 2,min(3, 2) = 2,正好与示例输出吻合。

复杂度

  • 时间复杂度:$O(n)$,两次线性扫描;
  • 空间复杂度:$O(1)$(不计返回答案数组本身,仅使用常数个辅助变量)。

解法二:多源 BFS(一维数组上的多起点扩散)

核心思路

把问题转化为「从多个源点出发的最短路」问题:所有出现c的下标都是源点(距离为 0),在一维数组上允许向左右两个方向移动一步,求每个位置到最近源点的距离。这正是「多源 BFS」在数组上的直接应用。

实现要点:

  1. 初始化ans全部为-1,同时把-1作为「是否已被访问」的标记;
  2. 遍历一遍字符串,把所有等于c的下标入队(Deque),并将对应ans[i]置为0;
  3. 定义方向数组dirs = {-1, 1},代表向左右各扩散一步;
  4. 从队头取出下标t,对每个方向计算出新下标ne = t + di,若ne在[0, n)范围内且ans[ne] == -1(尚未访问),则更新ans[ne] = ans[t] + 1并入队。

由于每个下标只被访问一次(以-1标记判重),BFS 天然保证首次到达某位置时经过的步数就是该位置到最近源点的距离,因此ans[ne] = ans[t] + 1即为正确答案,无需再取min。

Java 实现

class Solution { public int[] shortestToChar(String s, char c) { int n = s.length(); int[] ans = new int[n]; Arrays.fill(ans, -1); Deque<Integer> d = new ArrayDeque<>(); // 将所有 c 字符的下标作为源点入队,距离记为 0 for (int i = 0; i < n; i++) { if (s.charAt(i) == c) { d.addLast(i); ans[i] = 0; } } int[] dirs = new int[]{-1, 1}; while (!d.isEmpty()) { int t = d.pollFirst(); for (int di : dirs) { int ne = t + di; if (ne >= 0 && ne < n && ans[ne] == -1) { ans[ne] = ans[t] + 1; d.addLast(ne); } } } return ans; } }

C++ 实现

class Solution { public: vector<int> shortestToChar(string s, char c) { int n = s.length(); vector<int> ans(n, -1); deque<int> d; for (int i = 0; i < n; i++) { if (s[i] == c) { d.push_back(i); ans[i] = 0; } } vector<int> dirs = {-1, 1}; while (!d.empty()) { int t = d.front(); d.pop_front(); for (auto di : dirs) { int ne = t + di; if (ne >= 0 && ne < n && ans[ne] == -1) { ans[ne] = ans[t] + 1; d.push_back(ne); } } } return ans; } };

Python 实现

class Solution: def shortestToChar(self, s: str, c: str) -> List[int]: n = len(s) ans = [-1] * n d = deque() for i in range(n): if s[i] == c: d.append(i) ans[i] = 0 dirs = [-1, 1] while d: t = d.popleft() for di in dirs: ne = t + di if 0 <= ne < n and ans[ne] == -1: ans[ne] = ans[t] + 1 d.append(ne) return ans

TypeScript 实现

function shortestToChar(s: string, c: string): number[] { const n = s.length; const ans = new Array(n).fill(-1); const d: number[] = []; for (let i = 0; i < n; i++) { if (s.charAt(i) === c) { d.push(i); ans[i] = 0; } } const dirs = [-1, 1]; while (d.length > 0) { const t = d.shift() as number; for (const di of dirs) { const ne = t + di; if (ne >= 0 && ne < n && ans[ne] === -1) { ans[ne] = ans[t] + 1; d.push(ne); } } } return ans; }

为什么-1初始化是安全的

因为题目保证c至少出现一次,所有源点都已被标记为0并入队,队列非空时 BFS 必然从源点逐层向外扩散,最终覆盖整个数组,不会出现「某个位置永远停留在-1」的死角。-1在这里同时充当「未访问」标记与「非法距离」占位,是本题 BFS 写法简洁的关键。

复杂度

  • 时间复杂度:$O(n)$,每个下标至多入队、出队一次;
  • 空间复杂度:$O(n)$,队列在最坏情况下(如所有位置都被同一批源点扩散前入队)需要容纳 $O(n)$ 个下标。

两种解法对比与选用建议

维度两次遍历模拟多源 BFS
核心思想分左右两个方向各扫描一遍,取min所有c为源点,向左右逐层扩散
时间复杂度$O(n)$$O(n)$
空间复杂度$O(1)$(不计答案数组)$O(n)$(队列)
实现难度低,仅需两个循环与一个j指针中,需掌握队列与方向数组
判重手段无需判重,天然无重复计算以ans[i] == -1作为访问标记
可扩展性仅适用于一维「最近同类点」问题可推广到二维网格、多源最短路等场景

在仓库 Index/BFS.md 收录的题目(如 90. 子集 II、397. 整数替换、403. 青蛙过河、429. N 叉树的层序遍历等)中,BFS 通常作用于树、图或二维网格;本题的特殊之处在于把 BFS 用在一维数组上,方向数组只有{-1, 1}两个取值,是理解「多源 BFS 判重与分层扩散」的最佳入门样例。而两次遍历模拟则属于 Index/模拟.md 所归纳的字符串模拟类题目(如 38. 外观数列、58. 最后一个单词的长度、482. 密钥格式化等)中的典型套路——「正反两次扫描 + 前缀/后缀信息合并」,这一套路在「蜡烛之间的盘子」等稍复杂题目中同样适用。

刷题要点小结

  1. 看到「到最近某个位置的距离」,先想是否可以用两次扫描分别维护「左侧最近」与「右侧最近」,这通常能得到 $O(n)$ 时间、$O(1)$ 空间的简洁解法;
  2. 初始值的选取要有数学依据:本题选n + 1,是因为任意两下标间最大距离不超过n - 1,n + 1足够充当「未覆盖」占位;
  3. BFS 的-1标记法:把答案数组初始化为-1,既占位又判重,省去单独的visited数组;
  4. 方向数组的抽象:一维 BFS 用dirs = {-1, 1},二维 BFS 用四方向或八方向,本题是理解该抽象的最小规模样例。

延伸阅读

  • 本文题解原文档:LeetCode/821-830/821. 字符的最短距离(简单).md
  • 模拟类题目索引:Index/模拟.md
  • BFS 类题目索引:Index/BFS.md
  • 仓库简介与使用方式:README.md
  • 教程
  • 文档

【免费下载链接】LogicStack-LeetCode

公众号「宫水三叶的刷题日记」刷穿 LeetCode 系列文章源码

项目地址:https://gitcode.com/gh_mirrors/lo/LogicStack-LeetCode
点击查看免费下载

相关推荐

上一篇:QQ群数据采集系统完整使用手册
下一篇:10分钟搞定!Home Assistant Glow 3D打印外壳设计与组装教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 1:56:56

ssm283高校宿舍管理系统的设计与开发+jsp(文档+源码)_kaic

第5章 系统实现进入到这个环节&#xff0c;也就可以及时检查出前面设计的需求是否可靠了。一个设计良好的方案在运用于系统实现中&#xff0c;是会帮助系统编制人员节省时间&#xff0c;并提升开发效率的。所以在系统的编程阶段&#xff0c;也就是系统实现阶段&#xff0c;对于…

作者头像 李华
网站建设 2026/10/10 1:56:53

YOLOv8航拍屋顶识别实战:从数据标注到模型部署全流程

简介&#xff1a;本资源为基于YOLOv8的航拍屋顶识别目标检测项目代码包&#xff0c;面向计算机视觉学习者、深度学习课程设计者及遥感影像分析方向的开发者&#xff0c;用于解决航拍视角下屋顶目标自动检测与定位问题。压缩包共467个文件&#xff0c;约23.41MB&#xff0c;以22…

作者头像 李华
网站建设 2026/10/10 1:56:47

河北省推荐贴心月嫂,资质齐全的月嫂服务商客户真实体验口碑

衡水爱莲母婴服务有限公司是衡水地区专注母婴护理、育婴师服务及职业技能培训的专业母婴服务机构&#xff0c;业务覆盖从孕期到产后、从护理到早教的全周期母婴需求&#xff0c;为家庭提供标准化、专业化的家政服务解决方案。衡水爱莲母婴服务有限公司创立于2016年&#xff0c;…

作者头像 李华
网站建设 2026/10/10 1:56:13

无锡45号精光板精品定制厂家综合实力推荐:行业头部优选合作参考

无锡地处长三角制造业腹地&#xff0c;模具加工、自动化设备、精密机械零部件产业高度集聚&#xff0c;45号精光板作为模具底座、设备机架、精密零件的基础用材&#xff0c;市场需求常年旺盛。不少无锡采购负责人在网上搜索无锡45号精光板定制厂家无锡45号精光板靠谱供应商45号…

作者头像 李华
网站建设 2026/10/10 1:54:02

第三方计量检测专业公司实力盘点:多行业覆盖,新能源、医药、化工高壁垒赛道优势显著

在工业制造与质量管控领域&#xff0c;仪器计量校准是一项基础却常被低估的工作。无论是车间里的一把卡尺&#xff0c;还是实验室中的一台高精度分析仪&#xff0c;其测量数据的准确性都直接关系到产品质量、体系合规乃至企业信誉。本文将从行业常识、市场趋势、避坑要点、品牌…

作者头像 李华