问题描述
给你一个单词数组 words
和一个长度 maxWidth
,重新排版单词,使其成为每行恰好有 maxWidth
个字符,且左右两端对齐的文本。
你应该使用"贪心算法"来放置给定的单词;也就是说,尽可能多地往每行中放置单词。必要时可用空格 ' '
填充,使得每行恰好有 maxWidth
个字符。
要求单词之间的空格应该尽可能平均分配。如果某一行单词间的空格不能平均分配,则左侧放置的空格数要多于右侧的空格数。
文本的最后一行应为左对齐,且单词之间不插入额外的空格。
示例
示例 1:
1 2 3 4 5 6 7
| 输入:words = ["This", "is", "an", "example", "of", "text", "justification."], maxWidth = 16 输出: [ "This is an", "example of text", "justification. " ]
|
示例 2:
1 2 3 4 5 6 7
| 输入:words = ["What","must","be","acknowledgment","shall","be"], maxWidth = 16 输出: [ "What must be", "acknowledgment ", "shall be " ]
|
约束条件
1 <= words.length <= 300
1 <= words[i].length <= 20
words[i]
由小写英文字母和符号组成
1 <= maxWidth <= 100
words[i].length <= maxWidth
解题思路
这道题的核心是模拟文本编辑器的左右对齐功能。我们需要:
- 贪心策略:每行尽可能多地放置单词
- 空格分配:将剩余空格均匀分布在单词间
- 特殊处理:最后一行采用左对齐
算法步骤
步骤 1:贪心填充单词
对于每一行,我们使用贪心算法尽可能多地放置单词:
1
| 当前行长度 = 单词总长度 + 单词间隔数(单词数 - 1)
|
如果添加下一个单词会超过 maxWidth
,就开始处理当前行。
步骤 2:计算空格分配
对于非最后一行,需要计算如何分配空格:
- 总空格数 =
maxWidth
- 单词字符总数
- 间隔数 = 单词数 - 1
- 每个间隔的基础空格 = 总空格数 ÷ 间隔数
- 额外空格数 = 总空格数 % 间隔数
关键策略:额外空格从左到右依次分配,确保左侧空格不少于右侧。
步骤 3:处理特殊情况
- 单个单词的行:单词后填充空格到
maxWidth
- 最后一行:左对齐,单词间只用一个空格分隔,末尾填充空格
实现细节
核心算法可视化
以 ["This", "is", "an"]
为例,maxWidth = 16
:
1 2 3 4 5 6 7 8 9 10
| 步骤 1:计算空格分配 单词: "This" "is" "an" (长度: 4 + 2 + 2 = 8) 总空格: 16 - 8 = 8 间隔数: 3 - 1 = 2 基础空格: 8 ÷ 2 = 4 额外空格: 8 % 2 = 0
步骤 2:构建结果 "This" + 4空格 + "is" + 4空格 + "an" 结果: "This is an"
|
空格分配策略
当空格无法平均分配时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 例如:["What", "must", "be"], maxWidth = 16 单词长度: 4 + 4 + 2 = 10 总空格: 16 - 10 = 6 间隔数: 2 基础空格: 6 ÷ 2 = 3 额外空格: 6 % 2 = 0
但如果是 ["What", "must", "be"], maxWidth = 17: 总空格: 17 - 10 = 7 基础空格: 7 ÷ 2 = 3 额外空格: 7 % 2 = 1 (给第一个间隔)
结果: "What must be" ↑4空格 ↑3空格
|
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| func fullJustify(words []string, maxWidth int) []string { result := []string{} line := []string{} length := 0 for _, word := range words { if length + len(word) + len(line) > maxWidth { result = append(result, justify(line, maxWidth)) line = []string{word} length = len(word) } else { line = append(line, word) length += len(word) } } if len(line) > 0 { lastLine := strings.Join(line, " ") lastLine += strings.Repeat(" ", maxWidth-len(lastLine)) result = append(result, lastLine) } return result }
func justify(words []string, maxWidth int) string { if len(words) == 1 { return words[0] + strings.Repeat(" ", maxWidth-len(words[0])) } wordChars := 0 for _, word := range words { wordChars += len(word) } totalSpaces := maxWidth - wordChars gaps := len(words) - 1 spacePerGap := totalSpaces / gaps extraSpaces := totalSpaces % gaps var result strings.Builder result.Grow(maxWidth) for i, word := range words { result.WriteString(word) if i < len(words)-1 { spaces := spacePerGap if i < extraSpaces { spaces++ } result.WriteString(strings.Repeat(" ", spaces)) } } return result.String() }
|
执行过程分析
以示例 1 为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 输入: ["This", "is", "an", "example", "of", "text", "justification."] maxWidth = 16
第一行处理: - "This" (4) + "is" (2) + "an" (2) = 8 + 2间隔 = 10 ≤ 16 ✓ - 尝试加入 "example" (7): 10 + 1 + 7 = 18 > 16 ✗ - 处理 ["This", "is", "an"]: 空格分配 8个,平均分配到2个间隔 - 结果: "This is an"
第二行处理: - "example" (7) + "of" (2) + "text" (4) = 13 + 2间隔 = 15 ≤ 16 ✓ - 尝试加入 "justification.": 超出限制 - 处理 ["example", "of", "text"]: 剩余3个空格,分配到2个间隔 - 结果: "example of text"
最后一行: - ["justification."] 左对齐,后补空格 - 结果: "justification. "
|
复杂度分析
时间复杂度
$$T(n) = O(n)$$
其中 $n$ 是单词总数。每个单词只被处理一次,字符串构建操作虽然涉及空格计算,但总的字符数是固定的($\text{maxWidth} \times \text{行数}$)。
空间复杂度
$$S(n) = O(k)$$
其中 $k$ 是结果的总字符数。除了存储结果外,只使用了常数额外空间来存储当前行的单词。
算法特点分析
方面 |
评价 |
说明 |
时间复杂度 |
$O(n)$ |
每个单词只处理一次,字符串构建时间与总输出长度成正比 |
空间复杂度 |
$O(k)$ |
主要用于存储结果,$k$ 为输出字符总数 |
代码简洁度 |
★★★★☆ |
逻辑清晰,使用 Go 标准库简化实现 |
易理解性 |
★★★★☆ |
算法思路直观,分步骤处理易于理解 |
实现难度 |
★★★☆☆ |
需要正确处理多种边界情况 |
关键收获
核心算法思想
- 贪心策略:每行尽可能多地放置单词,这是最优策略
- 数学分配:使用除法和取模运算实现空格的均匀分配
- 边界处理:区分普通行、单词行和最后一行的不同处理逻辑
常见陷阱与避免方法
-
空格分配错误:
- ❌ 错误:平均分配空格后忽略余数
- ✅ 正确:余数空格从左到右依次分配
-
最后一行处理:
- ❌ 错误:最后一行也进行左右对齐
- ✅ 正确:最后一行只需左对齐
-
单词行处理:
- ❌ 错误:单个单词也尝试进行空格分配
- ✅ 正确:单个单词直接右侧填充空格
扩展应用
这个算法的思想可以应用到:
- 文档排版系统
- 代码格式化工具
- 表格对齐算法
- UI 布局分配算法
参考链接:LeetCode 68. 文本左右对齐