news 2026/9/18 23:00:27

LeetCode 3110 · Score of a String 字符串得分:单次遍历求相邻字符 ASCII 差绝对值之和(附 9+ 语言实现)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 3110 · Score of a String 字符串得分:单次遍历求相邻字符 ASCII 差绝对值之和(附 9+ 语言实现)

LeetCode 3110 · Score of a String 字符串得分:单次遍历求相邻字符 ASCII 差绝对值之和(附 9+ 语言实现)

【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode

本文基于 NeetCode 题解仓库中的 score-of-a-string.md 展开,系统讲解 LeetCode 3110「Score of a String(字符串得分)」的定义、求解思路与多语言实现:将字符串中每一对相邻字符的 ASCII 码差值取绝对值后累加。读完你将掌握这种"单次遍历 + 相邻元素处理"的基础字符串模式,理解ord/charAt/charCodeAt/asciiValue等各语言取字符码值的 API,并能在 Python、Java、C++、JavaScript、C#、Go、Kotlin、Swift、Rust 等语言中写出无边界错误、无遗漏绝对值的正确实现。

问题定义:什么是"字符串得分"

给定一个字符串s,其"得分"定义为所有相邻字符 ASCII 值之差的绝对值之和

score(s) = Σ |ascii(s[i]) - ascii(s[i+1])|, i = 0 .. n - 2

其中n为字符串长度。长度为n的字符串恰好存在n - 1对相邻字符,因此得分是这n - 1个非负差值之和。

通过两个例子可以直观验证(读者可自行用任意语言的取码函数复核):

输入字符 ASCII 序列相邻差绝对值得分
"hello"h=104, e=101, l=108, l=108, o=1113 + 7 + 0 + 313
"zaz"z=122, a=97, z=12225 + 2550
"abc"a=97, b=98, c=991 + 12
"aa"a=97, a=9700

前置知识(Prerequisites)

原文档在进入算法前列出了三个必须具备的基础概念,这里逐一补充其在本问题中的具体含义:

  1. 字符串遍历(String iteration)——使用下标i逐个访问字符串中的字符。本题需要在遍历的同时访问s[i]s[i + 1],因此必须对"当前下标 + 下一个下标"的配对方式有清晰认识。
  2. ASCII 值(ASCII values)——ASCII 将字符映射为 0~127 的整数。本题中'A'对应 65,'a'对应 97,'0'对应 48,小写字母az连续覆盖 97~122。字符参与算术运算时,各语言会自动(或需要显式)将其转换为整数码值。
  3. 绝对值(Absolute value)——由于相邻两个字符的大小顺序不定(可能是s[i] > s[i+1]),差值可能为负,必须用abs()取绝对值,保证每个差值都以非负形式累加。

各语言获取字符码值的核心 API 对比如下(这也是下文多语言实现的关键差异点):

语言取码方式说明
Pythonord(s[i])显式调用内建函数
Java / C#s.charAt(i)/s[i]char参与算术运算自动提升为int
C++s[i]char直接相减,结果提升为int
JavaScripts.charCodeAt(i)返回 Unicode 码点
Goint(s[i])索引得到byte,需显式转换为int再相减
Kotlins[i].codeChar.code扩展属性
Swiftchars[i].asciiValue!返回UInt8?,需解包
Rustbytes[i] as i32先取字节再转有符号整数

算法思路(Intuition)

原文档给出的核心直觉非常简洁:既然要比较每个字符与其邻居,只需完整遍历一次字符串,每走到位置i,就计算s[i]s[i + 1]的 ASCII 差值,取绝对值后累加到结果变量res。整个过程不需要任何额外数据结构,也不需要预排序或预处理——问题的结构决定了它就是一次朴素的线性扫描。

算法步骤(Algorithm)

原文档将算法归纳为三步:

  1. 初始化一个变量res用于保存累加结果(初值为 0)。
  2. 从下标0遍历到n - 2(含):
    • 对每个位置i,计算|ascii(s[i]) - ascii(s[i + 1])|
    • 将该差值加到res上。
  3. 返回res作为最终答案。

这里有一个值得强调的细节:循环上界是n - 2而非n - 1。因为当i = n - 2时访问的已经是最后一对(s[n-2], s[n-1]),处理完这对后所有相邻关系都已覆盖。

多语言实现(继承原文档全部代码)

以下 9 种语言的实现完整继承自原文档,每段代码都保持O(n)时间、O(1)空间的复杂度:

Python

class Solution: def scoreOfString(self, s: str) -> int: res = 0 for i in range(len(s) - 1): res += abs(ord(s[i]) - ord(s[i + 1])) return res

ord()将单个字符转为 Unicode 码点(本题输入均为 ASCII 字符,等价于 ASCII 值);range(len(s) - 1)恰好生成0 .. n-2,不会越界。

Java

public class Solution { public int scoreOfString(String s) { int res = 0; for (int i = 0; i < s.length() - 1; i++) { res += Math.abs(s.charAt(i) - s.charAt(i + 1)); } return res; } }

charAt返回char,相减时自动提升为intMath.abs作用于两个int的差。

C++

class Solution { public: int scoreOfString(string s) { int res = 0; for (int i = 0; i < s.length() - 1; i++) { res += abs(s[i] - s[i + 1]); } return res; } };

C++ 中std::absint重载,char相减先提升为int,逻辑与 Java 一致。

JavaScript

class Solution { /** * @param {string} s * @return {number} */ scoreOfString(s) { let res = 0; for (let i = 0; i < s.length - 1; i++) { res += Math.abs(s.charCodeAt(i) - s.charCodeAt(i + 1)); } return res; } }

JavaScript 字符串不能按下标直接得到码值,必须通过charCodeAt(i)取字符的 Unicode 码点。

C#

public class Solution { public int ScoreOfString(string s) { int res = 0; for (int i = 0; i < s.Length - 1; i++) { res += Math.Abs(s[i] - s[i + 1]); } return res; } }

C# 的string索引器返回char,与 Java 类似会自动提升为int参与减法。

Go

func scoreOfString(s string) int { res := 0 for i := 0; i < len(s)-1; i++ { diff := int(s[i]) - int(s[i+1]) if diff < 0 { diff = -diff } res += diff } return res }

Go 的math包只提供浮点类型的Abs,对int需要手动判断符号——原文档的这一写法正是利用了"负数取反即绝对值"的性质,避免引入额外的类型转换开销。

Kotlin

class Solution { fun scoreOfString(s: String): Int { var res = 0 for (i in 0 until s.length - 1) { res += kotlin.math.abs(s[i].code - s[i + 1].code) } return res } }

Char.code是 Kotlin 中获取字符码值的标准方式;0 until s.length - 1生成左闭右开区间[0, n-2],与循环需求完全吻合。

Swift

class Solution { func scoreOfString(_ s: String) -> Int { let chars = Array(s) var res = 0 for i in 0..<chars.count - 1 { res += abs(Int(chars[i].asciiValue!) - Int(chars[i + 1].asciiValue!)) } return res } }

Swift 中String不能按下标直接访问,需先转为Array(s)asciiValue返回UInt8?(可选值),这里用!强制解包,前提是输入保证为 ASCII 字符。这也是"前置知识"部分强调 ASCII 范畴的原因之一。

Rust

impl Solution { pub fn score_of_string(s: String) -> i32 { let bytes = s.as_bytes(); let mut res = 0; for i in 0..bytes.len() - 1 { res += (bytes[i] as i32 - bytes[i + 1] as i32).abs(); } res } }

as_bytes()将字符串转为&[u8],逐字节访问天然适合 ASCII 处理;转换为i32后再调.abs(),避免无符号类型相减可能出现的下溢问题。

复杂度分析

原文档给出的结论如下,这里补充推导依据:

  • 时间复杂度:O(n)——对长度为n的字符串恰好遍历一次,每个位置执行常数次算术与比较操作,共处理n - 1对相邻字符。
  • 空间复杂度:O(1)——除输入字符串自身外,仅使用一个累加变量res(及循环变量),不随输入规模增长分配额外内存。Swift 版本额外创建了Array(s)副本,属于实现层面的常数级(实为 O(n))辅助空间,其余语言均为严格 O(1)。

常见陷阱(Common Pitfalls)

原文档重点提示了两个高频错误,这里结合实际编码场景展开,并补充第三类语言特有细节。

陷阱一:循环边界的 Off-by-One 错误

错误写法:把循环上界写成n,导致在i = n - 1时访问s[i + 1]越界(IndexOutOfBoundsException/IndexError/ 未定义行为)。

原因分析:长度为n的字符串只有n - 1对相邻字符,最后一对是(s[n-2], s[n-1])。因此循环应从0n - 2(含),共迭代n - 1次。

修正对照

  • Python:range(len(s) - 1)
  • Java/C++/C#/JS:i < s.length() - 1(或i <= s.length() - 2);
  • Kotlin:0 until s.length - 1
  • Swift/Rust:0..<count - 1

陷阱二:忘记取绝对值

错误写法:直接res += s[i] - s[i + 1]而不包abs()

后果:当s[i] > s[i + 1](例如"zab"'z''a'相邻)时,差值为负,累加到res后会使得分被"抵消",结果偏小甚至为负。题目要求的是绝对值之和,因此无论两个字符谁大谁小,差值都必须以非负形式进入累加。

修正方式:在所有语言中将相减结果用abs()/Math.abs/kotlin.math.abs/.abs()包裹;Go 语言无intabs,需手动判断符号(如原文档 Go 实现所示)。

陷阱三:语言特有条件与类型细节

  • Go 的无符号/有符号转换s[i]的类型是byte(即uint8),两个byte相减可能为负,必须显式转int后运算,否则可能发生无符号下溢。
  • Swift 的可选值解包asciiValue返回可选类型,遇到非 ASCII 字符(如中文、emoji)会得到nil,此时!强解包会触发运行时崩溃;若输入不保证为纯 ASCII,应改为安全解包或使用unicodeScalars方案。
  • Java/C# 的 char 算术char相减结果自动为int,无需显式转换,但要注意Math.abs接收的参数类型为int,与返回值一致。

边界情况与扩展思考

在提交或本地自测时,建议覆盖以下边界输入:

  • 空字符串""len(s) - 1 = -1,循环体不执行,返回 0(各语言对负长度的处理方式不同,注意 Python 的range(-1)为空、Kotlin 的0 until -1为空区间,均安全)。
  • 单字符"a":没有相邻对,得分恒为 0。
  • 大小写混合"aZ"|97 - 90| = 7,验证了 ASCII 表中小写字母与大写字母之间存在固定间隔('a' - 'A' = 32)。
  • 数字与符号"12"|49 - 50| = 1,说明该算法对任意 ASCII 字符均成立。
  • 重复字符"aa"→ 0,差值对得分无贡献。

延伸思考:本题是"相邻元素差值求和"这一模式的入门题。同一模式稍作变形即可演变为更复杂的问题,例如对差值做符号判断、只统计正向差值(单调性)、或配合前缀和做区间查询。仓库中与"字符串遍历 + 字符比较"密切相关的题解文章还包括:

  • is-anagram.md(字符计数与 ASCII 运算),对应解法文件见 python/0242-valid-anagram.py;
  • longest-common-prefix.md(逐字符比较多个字符串);
  • valid-palindrome-ii.md(字符级比较与双指针);
  • longest-substring-without-duplicates.md(窗口内字符状态维护);
  • permutation-string.md(字符频次统计)。

建议按"先掌握单次遍历相邻元素 → 再引入双指针/窗口/计数"的顺序学习,逐步建立字符串类题目的解题框架。

仓库中的相关资源与写作规范

  • 本文主体对应仓库中的 score-of-a-string.md,其中包含前置知识、算法步骤、9 种语言的参考实现、复杂度分析与常见陷阱,是 NeetCode 题解文章的标准结构模板。
  • 仓库根目录的 README.md 说明了该仓库的定位:为 NeetCode 平台提供多语言题解,覆盖 Python、Java、JavaScript、C++、Go、Swift、C#、TypeScript、Rust、Kotlin、Ruby、C、Scala 与 Dart 共 14 种语言,并维护了各题在各语言下的完成情况索引表。
  • articles/README.md 给出了题解文章的撰写规范:每篇需包含与 NeetCode 视频一致的至少一种解法、标注时间与空间复杂度、并尽可能覆盖全部相关解法。本文所述问题即遵循该规范组织内容。

从当前仓库结构看,该题目前以题解文章形式存在于articles/目录,尚未包含对应编号的源码解法文件;读者可参考本文的 9 种语言实现,自行补全其他语言的等价代码(Ruby、Scala、Dart、TypeScript、C 的写法差异主要在于取字符码值的 API 与abs函数名,思路完全一致)。

【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode

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

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

CUDA-Samples cuBLAS 示例实践:矩阵乘法 GPU 性能的 3 个决策点

CUDA-Samples cuBLAS 示例实践&#xff1a;矩阵乘法 GPU 性能的 3 个决策点 【免费下载链接】cuda-samples Samples for CUDA Developers which demonstrates features in CUDA Toolkit 项目地址: https://gitcode.com/GitHub_Trending/cu/cuda-samples 场景切入&#x…

作者头像 李华
网站建设 2026/9/18 22:57:16

YOLOv11端到端部署:人脸识别与异常行为检测实战

简介&#xff1a;这是一份面向安防领域算法工程师与部署人员的YOLOv11实战技术手册&#xff0c;聚焦人脸识别与异常行为检测的完整落地路径。手册从YOLOv11基础讲起&#xff0c;涵盖算法原理、骨干网络与检测头结构&#xff0c;并详细展开人脸检测、特征提取及匹配识别同YOLOv1…

作者头像 李华
网站建设 2026/9/18 22:54:14

LoRA从原理到实战:加载、训练、提示词与显存优化指南

去年帮朋友调一个素描风格的LoRA&#xff0c;他前后下了三个版本&#xff0c;权重一路拉到1.2&#xff0c;出图还是那张熟悉的脸&#xff0c;一点素描味都没有。我让他把提示词里的触发词删掉再试一次&#xff0c;画面立刻变成了炭笔素描的质感——问题从头到尾都不在模型文件&…

作者头像 李华
网站建设 2026/9/18 22:53:48

AI转介时代,医疗客服如何接住“做过功课”的患者?

从客服视角切入这个场景&#xff0c;可能很多机构还没有意识到&#xff1a;患者做医疗决策的路径&#xff0c;已经被AI悄悄改写了。以前是“搜索关键词-翻排名-看官网-打电话”&#xff0c;现在变成了“问AI-拿结论-带着结论来对话”。这两个路径对客服的要求完全不同。我带客服…

作者头像 李华