本文对和 字符串 有关的常见算法基础题思路分类进行分析和总结,并以 Java 为例,适当指出需要注意的编程细节
- 相关题目和代码在 GitHub: https://github.com/brianway/algorithms-learning
- 题目见
com.brianway.learning.algorithms.lectures.string
包
最长无重复字符子串
题目见 DistinctSubstring
思路:对于每个字符,向左延伸找无重复子串,要知道该字符(记为 s[i]
)上次在哪个位置出现过,还要知道之前其他字符中出现过重复且最近一次出现重复的那个位置 pre,则以 s[i]
结尾的向左最长无重复的长度为: s[i]
上次出现位置和 pre 中较大者到现在 s[i]
的距离。
1 | //例子1 |
- 使用一个哈希表
map[256]
来记录每种字符之前最新出现过的位置。 - 使用一个整型变量 pre 来记录以
s[i-1]
结尾的字符串向左延伸且无重复字符时的最左下标(不包括该下标)。
对字符串中每个字符 s[i]
进行如下操作:
- 对字符串中每个字符,找出
pre
和map[s[i]]
中的较大值并更新为pre
- 以当前字符
s[i]
结尾向左无重复的长度则为cur = i - pre
- 根据这个 cur 和历史最大长度比较,更新最大长度
- 更新
map[s[i]]
为 i
注意:
- map 中每个元素和 pre 均初始值为 -1
- 有个现象:当未出现过某字符且字符均未出现过重复时,
pre
和map[s[i]]
均为 -1,cur = i+1
,cur 随 i 递增
拓扑结构相同子树
题目见 IdenticalTree
思路:
- 使用特殊格式打印的二叉树中序遍历结果时唯一的,打印两个树的中序遍历字符串
- 使用 KMP 算法进行子串匹配,能匹配则
注意:
- 中序遍历时,空节点也要打印,所有值要以特殊字符结尾以免歧义
- KMP 的原理、实现以及小优化
合法括号序列
题目见 Parenthesis
思路:每个右括号出现时必已有一个左括号与之对应,计数即可,正负抵消。对每个元素进行如下操作:
- 遇到非括号字符返回 false
- 遇到左括号则计数器加一
- 遇到右括号则计数器减一,计数器为负值则返回 false
- 循环结束计数器值为零则 true,否则 false
拼接最小字典序
题目见 Prior
思路:排序不是以单个字符串来比较,而是把待比较的两个字符串(记为 sa 和 sb)拼接起来进行比较,即比较 sa+sb 和 sb+sa 的字典序
注意:
- 由于两个串无论什么顺序拼接,拼接后字符串长度相同,所以无论谁在前,拼接后的串对其他字符串影响是一致的
空格替换
题目见 Replacement
思路:
- 先统计下空格数量,分配空间
- 直接逐个字符复制,遇到空格就替换即可
注意:
- 新的字符串长度为
length + 2 * count
,length 为原字符串长度, count 为空格个数,%20
比空格多了两个字符
句子逆序
题目见 Reverse
思路:
- 先对整个字符串反转
- 再对以空格分隔的每个单词反转
注意:
- 能做到 in place 反转,无需额外空间,有点像“负负得正”
- 每个单词反转是以其末尾的空格触发的,所以对最后一个单词,需要单独判断处理
两串旋转
题目见 Rotation
思路:
- 判断两串是否长度相等
- 将原串与自身拼接,看另一字符串是否是拼接串的子串即可
词语变形
题目见 Transform
思路:
- 判断两串长度是否相等
- 遍历 A 串,统计 A 串中每种字符出现的次数(计数累加)
- 遍历 B 串,做 2 中的统计结果做减法,若 B 中出现了 A 中未出现的字符或者 B 中某种字符出现次数比 A 多(表现形式为:减 1 前
counts[b[i]] == 0
),返回 false,否则 true
注意:
- 遍历 B 串不是计数累加而是利用 A 的结果
字符串移位问题
题目见 Translation
思路:以移位长度为分界点,分别将各部分对其中轴线做镜像反转,再对整个字符串以其中轴线做镜像反转。
- 将下标
0 ~ len-1
的字符反转 - 将下标
len ~ n-1
的字符反转 - 将整个字符串
0 ~ n-1
反转
注意:
- 无需申请额外空间
- 各部分做了两次镜像反转,所以内部保持相对顺序不变