1. 力扣字符串专题概述
字符串处理是编程面试中最基础也最常考的核心技能点。在力扣(LeetCode)平台上,字符串类题目占比超过20%,几乎每场技术面试都会涉及1-2道相关题目。这个专题之所以重要,是因为字符串操作能全面考察候选人的基础编码能力、边界条件处理意识以及算法优化思维。
我刷完力扣前300题后发现,字符串题目看似简单,实际暗藏杀机。许多题目用暴力解法都能通过,但要在面试中给出最优解,需要掌握特定的技巧和模式。比如简单的反转字符串(LeetCode 344),如果不知道双指针技巧,可能就会写出时间复杂度O(n²)的解法。
2. 字符串专题核心解题技巧
2.1 双指针法的经典应用
双指针是字符串处理中最常用的技巧之一,主要分为同向指针和相向指针两种类型。同向指针常用于滑动窗口问题,而相向指针则适用于回文判断、字符串反转等场景。
以力扣第125题"验证回文串"为例,最优解就是使用相向双指针:
python复制def isPalindrome(s):
left, right = 0, len(s)-1
while left < right:
while left < right and not s[left].isalnum():
left += 1
while left < right and not s[right].isalnum():
right -= 1
if s[left].lower() != s[right].lower():
return False
left += 1
right -= 1
return True
注意:处理边界条件时要特别注意指针越界问题。我在实际面试中就曾因为忘记检查while循环中的left<right条件而出现bug。
2.2 滑动窗口的三种变体
滑动窗口是解决子串/子数组问题的利器,根据题目特点可以分为:
- 固定窗口大小(如力扣第567题"字符串的排列")
- 可变窗口求最大值(如力扣第3题"无重复字符的最长子串")
- 可变窗口求最小值(如力扣第76题"最小覆盖子串")
以第3题为例,标准解法是:
python复制def lengthOfLongestSubstring(s):
char_index = {}
left = max_len = 0
for right, char in enumerate(s):
if char in char_index and char_index[char] >= left:
left = char_index[char] + 1
char_index[char] = right
max_len = max(max_len, right - left + 1)
return max_len
2.3 KMP算法精要
KMP算法是字符串匹配的经典算法,理解其核心在于掌握部分匹配表(PMT)的构建。以力扣第28题"实现strStr()"为例:
python复制def strStr(haystack, needle):
if not needle:
return 0
# 构建部分匹配表
lps = [0] * len(needle)
length = 0
i = 1
while i < len(needle):
if needle[i] == needle[length]:
length += 1
lps[i] = length
i += 1
else:
if length != 0:
length = lps[length-1]
else:
lps[i] = 0
i += 1
# 开始匹配
i = j = 0
while i < len(haystack):
if haystack[i] == needle[j]:
i += 1
j += 1
if j == len(needle):
return i - j
else:
if j != 0:
j = lps[j-1]
else:
i += 1
return -1
提示:KMP算法理解难度较大,建议先手动画出PMT表的构建过程。我在最初学习时花了整整3天才完全理解其精髓。
3. 高频题目分类解析
3.1 回文串相关问题
| 题目编号 | 题目名称 | 难度 | 核心技巧 |
|---|---|---|---|
| 125 | 验证回文串 | 简单 | 双指针+字符处理 |
| 5 | 最长回文子串 | 中等 | 中心扩展法 |
| 647 | 回文子串 | 中等 | 动态规划 |
以第5题为例,中心扩展法的关键在于处理奇偶长度情况:
python复制def longestPalindrome(s):
def expand(l, r):
while l >=0 and r < len(s) and s[l] == s[r]:
l -= 1
r += 1
return s[l+1:r]
res = ""
for i in range(len(s)):
odd = expand(i, i)
even = expand(i, i+1)
res = max(res, odd, even, key=len)
return res
3.2 字符串转换问题
这类题目通常涉及字符串与数字的转换、大小写转换等操作。典型题目包括:
- 第8题"字符串转换整数 (atoi)"
- 第12题"整数转罗马数字"
- 第13题"罗马数字转整数"
以第8题为例,需要注意处理各种边界条件:
python复制def myAtoi(s):
s = s.lstrip()
if not s:
return 0
sign = -1 if s[0] == '-' else 1
if s[0] in ['-', '+']:
s = s[1:]
res = 0
for ch in s:
if not ch.isdigit():
break
res = res * 10 + int(ch)
if sign * res < -2**31:
return -2**31
if sign * res > 2**31 -1:
return 2**31 -1
return sign * res
3.3 字符串排列组合
这类问题通常需要用到回溯算法,典型题目包括:
- 第17题"电话号码的字母组合"
- 第22题"括号生成"
- 第46题"全排列"
以第17题为例:
python复制def letterCombinations(digits):
if not digits:
return []
digit_map = {
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz'
}
res = []
def backtrack(index, path):
if index == len(digits):
res.append(''.join(path))
return
for letter in digit_map[digits[index]]:
path.append(letter)
backtrack(index+1, path)
path.pop()
backtrack(0, [])
return res
4. 字符串专题进阶技巧
4.1 后缀自动机的应用
后缀自动机(Suffix Automaton)是处理复杂字符串问题的强大工具,特别适合解决:
- 不同子串个数统计
- 最长公共子串
- 最长重复子串
虽然力扣中直接考察的题目不多,但掌握这一数据结构能大大提升解决字符串难题的能力。以下是一个简单的后缀自动机实现框架:
python复制class State:
def __init__(self):
self.len = 0
self.link = -1
self.next = dict()
class SuffixAutomaton:
def __init__(self):
self.size = 1
self.last = 0
self.states = [State()]
def sa_extend(self, c):
p = self.last
curr = self.size
self.size += 1
self.states.append(State())
self.states[curr].len = self.states[p].len + 1
while p >=0 and c not in self.states[p].next:
self.states[p].next[c] = curr
p = self.states[p].link
if p == -1:
self.states[curr].link = 0
else:
q = self.states[p].next[c]
if self.states[p].len +1 == self.states[q].len:
self.states[curr].link = q
else:
clone = self.size
self.size +=1
self.states.append(State())
self.states[clone].len = self.states[p].len +1
self.states[clone].next = self.states[q].next.copy()
self.states[clone].link = self.states[q].link
while p >=0 and self.states[p].next[c] == q:
self.states[p].next[c] = clone
p = self.states[p].link
self.states[q].link = clone
self.states[curr].link = clone
self.last = curr
4.2 字典树(Trie)的妙用
字典树特别适合处理前缀相关的问题,如:
- 第208题"实现 Trie (前缀树)"
- 第212题"单词搜索 II"
- 第720题"词典中最长的单词"
一个标准的Trie实现如下:
python复制class TrieNode:
def __init__(self):
self.children = {}
self.is_word = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word):
node = self.root
for ch in word:
if ch not in node.children:
node.children[ch] = TrieNode()
node = node.children[ch]
node.is_word = True
def search(self, word):
node = self.root
for ch in word:
if ch not in node.children:
return False
node = node.children[ch]
return node.is_word
def startsWith(self, prefix):
node = self.root
for ch in prefix:
if ch not in node.children:
return False
node = node.children[ch]
return True
5. 字符串专题刷题建议
5.1 刷题路线图
根据难度和技巧相关性,我建议按以下顺序刷题:
- 基础操作:344(反转)、541(反转II)、557(反转III)
- 双指针:125(回文)、680(验证回文II)
- 滑动窗口:3(无重复最长子串)、76(最小覆盖子串)
- 字符串匹配:28(实现strStr)、214(最短回文串)
- 动态规划:5(最长回文子串)、72(编辑距离)
- 高级数据结构:208(实现Trie)、336(回文对)
5.2 常见错误与调试技巧
在字符串问题中,最容易出现的错误包括:
- 索引越界:特别是在使用双指针时,忘记检查指针范围
- 边界条件:空字符串、全空格字符串等特殊情况处理
- 编码错误:混淆字符和ASCII码,大小写处理不当
- 性能问题:使用不必要的字符串拼接操作(在Python中字符串是不可变的,拼接会产生新对象)
调试时可以:
- 打印关键变量的中间状态
- 使用小规模测试用例验证边界条件
- 对于递归解法,画出调用树帮助理解
5.3 面试准备要点
在面试中遇到字符串题目时,建议:
- 先确认输入输出要求(大小写敏感?空格处理?)
- 举例说明你的解题思路
- 先给出暴力解法,再逐步优化
- 特别注意时间复杂度的分析
- 写完代码后,用测试用例验证
我在面试Google时遇到的一道字符串题目是:给定一个字符串和一个字典,找出字符串中最短的子串,使得这个子串包含字典中的所有字符。这道题就是滑动窗口的典型应用,但需要处理好字典中字符可能重复的情况。
