1. 问题背景与核心挑战
文本对齐是文字处理中的经典问题,LeetCode第68题要求我们实现一个模拟文本编辑器中左右对齐功能的算法。给定一个单词数组和最大行宽maxWidth,我们需要重新排版单词,使其满足以下要求:
- 每行恰好有maxWidth个字符
- 单词之间用空格分隔
- 尽可能均匀分配空格,无法均匀时左侧空格数多于右侧
- 最后一行左对齐且单词间不插入额外空格
这个看似简单的排版问题,实际上考察了多个算法核心能力:
- 字符串处理的精确控制
- 边界条件的全面考虑
- 贪心算法的实际应用
- 复杂逻辑的模块化实现
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 贪心算法策略解析
2.1 贪心选择性质
本题适合采用贪心算法的根本原因在于其具有贪心选择性质:每一行的单词选择只与当前剩余空间有关,不受后续行的影响。具体表现为:
- 局部最优导致全局最优:每行尽可能多地放置单词(在不超过maxWidth的前提下),这种局部最优选择不会影响后续行的处理
- 无后效性:当前行的单词选择完成后,后续行的处理不需要回溯或修改前面的决策
2.2 算法框架设计
基于贪心策略,我们可以将问题分解为三个子任务:
- 单词分组:确定每行包含哪些单词
- 空格分配:计算单词间需要插入的空格数
- 特殊处理:最后一行和其他行的不同处理方式
python复制def fullJustify(words, maxWidth):
result = []
current_line = []
current_length = 0
for word in words:
# 判断当前单词是否能加入当前行
if current_length + len(word) + len(current_line) <= maxWidth:
current_line.append(word)
current_length += len(word)
else:
# 处理当前行
result.append(process_line(current_line, maxWidth, False))
# 开始新行
current_line = [word]
current_length = len(word)
# 处理最后一行
result.append(process_line(current_line, maxWidth, True))
return result
3. 核心实现细节剖析
3.1 单词分组策略
单词分组是算法的基础步骤,需要精确计算单词长度和必要的分隔空格:
python复制# 示例计算过程
words = ["This", "is", "an", "example"]
maxWidth = 16
第一行计算:
"This" (4) + " " + "is" (2) + " " + "an" (2) = 9 < 16
尝试加入"example":
9 + " " + "example" (7) = 17 > 16 → 不能加入
因此第一行包含["This", "is", "an"]
关键点:
- 每个单词后至少需要一个空格分隔
- 初始计算时考虑n个单词需要(n-1)个空格
- 使用current_length跟踪当前行单词总长度
3.2 空格分配算法
非最后行的空格分配是最复杂的部分,需要处理多种情况:
python复制def process_line(line, maxWidth, is_last):
if is_last or len(line) == 1:
return left_justify(line, maxWidth)
return middle_justify(line, maxWidth)
def middle_justify(line, maxWidth):
total_spaces = maxWidth - sum(len(word) for word in line)
gaps = len(line) - 1
base_space = total_spaces // gaps
extra_space = total_spaces % gaps
justified = []
for i in range(gaps):
justified.append(line[i])
justified.append(' ' * (base_space + (1 if i < extra_space else 0)))
justified.append(line[-1])
return ''.join(justified)
处理逻辑:
- 计算总空格数:maxWidth - 所有单词长度和
- 计算基础空格数:total_spaces // gaps
- 计算额外空格数:total_spaces % gaps
- 前extra_space个间隔多分配一个空格
3.3 特殊行处理
最后一行和单单词行需要特殊处理:
python复制def left_justify(line, maxWidth):
text = ' '.join(line)
return text + ' ' * (maxWidth - len(text))
特点:
- 单词间只保留一个空格
- 剩余空间全部补在行末
- 实现简单但容易忽略边界条件
4. 完整Python实现与逐行解析
结合上述分析,以下是完整实现及详细注释:
python复制def fullJustify(words, maxWidth):
"""
主函数:文本左右对齐
:param words: 单词列表
:param maxWidth: 行最大宽度
:return: 对齐后的文本行列表
"""
result = []
current_line = [] # 当前行的单词
current_length = 0 # 当前行单词总长度(不含空格)
for word in words:
# 尝试将单词加入当前行:单词长度+空格数<=maxWidth
if current_length + len(word) + len(current_line) <= maxWidth:
current_line.append(word)
current_length += len(word)
else:
# 处理当前完整行
result.append(process_line(current_line, maxWidth, False))
# 开始新行
current_line = [word]
current_length = len(word)
# 处理最后一行(可能未满)
result.append(process_line(current_line, maxWidth, True))
return result
def process_line(line, maxWidth, is_last):
"""
处理单行文本的对齐
:param line: 当前行单词列表
:param maxWidth: 行宽
:param is_last: 是否是最后一行
:return: 对齐后的字符串
"""
if is_last or len(line) == 1:
return left_justify(line, maxWidth)
return middle_justify(line, maxWidth)
def middle_justify(line, maxWidth):
"""
中间行对齐:均匀分配空格
"""
total_spaces = maxWidth - sum(len(word) for word in line)
gaps = len(line) - 1 # 单词间隔数
base_space = total_spaces // gaps
extra_space = total_spaces % gaps
justified = []
for i in range(gaps):
justified.append(line[i])
# 前extra_space个间隔多一个空格
justified.append(' ' * (base_space + (1 if i < extra_space else 0)))
justified.append(line[-1])
return ''.join(justified)
def left_justify(line, maxWidth):
"""
最后一行或单单词行:左对齐
"""
text = ' '.join(line)
return text + ' ' * (maxWidth - len(text))
5. 复杂度分析与优化思路
5.1 时间复杂度
- 单词分组阶段:O(n),n为单词总数,每个单词只处理一次
- 行处理阶段:O(n),每行处理时间与单词数成正比
- 总体复杂度:O(n),线性时间复杂度
5.2 空间复杂度
- 结果存储:O(n),需要存储所有字符
- 临时变量:O(maxWidth),处理单行时的临时空间
- 总体复杂度:O(n)
5.3 潜在优化方向
- 预处理单词长度:提前计算所有单词长度,避免重复计算
- 字符串构建优化:使用列表代替字符串拼接
- 并行处理:对独立行可以并行处理(但实际收益有限)
6. 常见错误与调试技巧
6.1 典型错误案例
-
空格计算错误:
- 忘记考虑单词间至少一个空格
- 整数除法导致空格分配不均
python复制# 错误示例 spaces = (maxWidth - sum(len(w) for w in line)) / (len(line)-1) # 应使用// -
边界条件遗漏:
- 单单词行未特殊处理
- 最后一行处理与其他行相同
- 空输入未考虑
-
索引越界:
- 处理空行时访问line[0]
- 单词数为1时计算gaps=len(line)-1=0导致除零错误
6.2 调试建议
-
打印中间状态:
python复制print(f"Line: {current_line}, length: {current_length}") -
单元测试用例:
python复制test_cases = [ ([""], 5), # 空输入 (["a"], 1), # 单字符 (["hello", "world"], 10), # 刚好填满 (["This", "is", "an", "example"], 16) # 标准案例 ] -
可视化空格:
python复制print(repr(result[-1])) # 显示空格字符
7. 实际应用场景扩展
虽然本题源自文本编辑器,但其算法思想可应用于多种场景:
- 网页排版引擎:CSS的text-align: justify实现
- 文档生成工具:自动调整文档边距
- 报表打印系统:固定宽度列的文本对齐
- 终端显示优化:命令行工具的格式化输出
在这些场景中,我们可能需要扩展算法以处理:
- 连字符断字
- 标点符号悬挂
- 多语言混合排版
- 动态调整行宽
8. 贪心算法的通用解题模式
通过本题,我们可以总结贪心算法解决字符串问题的通用模式:
- 问题分解:将全局问题分解为局部选择
- 贪心选择:定义局部最优的标准(本题是每行尽可能多放单词)
- 正确性证明:证明局部最优能导致全局最优
- 实现优化:用指针或索引高效处理字符串
- 边界处理:单独处理首尾、空值等特殊情况
类似的问题包括:
- 字符串分割(LeetCode 763)
- 加油站问题(LeetCode 134)
- 任务调度器(LeetCode 621)
掌握这种模式可以系统性地解决一大类贪心算法问题。
