LeetCode 第68题:文本左右对齐
题目描述
给定一个单词数组 words 和一个长度 maxWidth,重新排版单词,使其成为每行恰好有 maxWidth 个字符,且左右两端对齐的文本。
你应该使用"贪心"的方法来放置给定的单词;也就是说,尽可能多地往每行中放置单词。必要时可用空格 ' ' 填充,使得每行恰好有 maxWidth 个字符。
要求尽可能均匀分配单词间的空格数量。如果某一行单词间的空格不能均匀分配,则左侧放置的空格数要多于右侧的空格数。
文本的最后一行应为左对齐,且单词之间不插入额外的空格。
难度
困难
题目链接
示例
示例 1:
输入:words = ["This", "is", "an", "example", "of", "text", "justification."], maxWidth = 16 输出: [ "This is an", "example of text", "justification. " ]
示例 2:
输入:words = ["What","must","be","acknowledgment","shall","be"], maxWidth = 16 输出: [ "What must be", "acknowledgment ", "shall be " ]
示例 3:
输入:words = ["Science","is","what","we","understand","well","enough","to","explain","to","a","computer.","Art","is","everything","else","we","do"], maxWidth = 20 输出: [ "Science is what we", "understand well", "enough to explain to", "a computer. Art is", "everything else we", "do " ]
提示
1 <= words.length <= 3001 <= words[i].length <= 20words[i]由小写英文字母和符号组成1 <= maxWidth <= 100
解题思路
分步处理法
这道题的关键是处理好以下几个方面:
- 确定每行能放下哪些单词
- 计算空格分配
- 特殊处理最后一行
- 处理只有一个单词的行
具体步骤:
- 遍历单词数组,确定每行的单词
- 计算单词间需要插入的空格数
- 分配空格(需要考虑均匀分配)
- 特殊处理最后一行(左对齐)
- 将处理好的每行文本添加到结果中
图解思路
算法步骤分析表
| 步骤 | 操作 | 状态 | 说明 |
|---|---|---|---|
| 初始 | 收集单词 | ["This", "is", "an"] | 确定一行的单词 |
| 计算空格 | 16-(4+2+2)=8 | 需要8个空格 | 计算总空格数 |
| 分配空格 | 8÷2=4 | 每间隔4个空格 | 均匀分配空格 |
| 生成行 | "This is an" | 完成一行 | 拼接结果 |
状态/情况分析表
| 情况 | 输入 | 输出 | 说明 |
|---|---|---|---|
| 普通行 | ["This", "is", "an"] | "This is an" | 空格均匀分配 |
| 最后一行 | ["shall", "be"] | "shall be " | 左对齐 |
| 单词行 | ["acknowledgment"] | "acknowledgment " | 右侧补空格 |
代码实现
C# 实现
public class Solution {
public IList<string> FullJustify(string[] words, int maxWidth) {
List<string> result = new List<string>();
int index = 0;
while (index < words.Length) {
// 计算当前行可以放多少个单词
int count = GetWordsCount(words, index, maxWidth);
// 处理当前行
string line = CreateLine(words, index, count, maxWidth, index + count == words.Length);
result.Add(line);
index += count;
}
return result;
}
private int GetWordsCount(string[] words, int start, int maxWidth) {
int width = 0;
int count = 0;
for (int i = start; i < words.Length; i++) {
// 加上当前单词长度和至少一个空格
width += words[i].Length + (count > 0 ? 1 : 0);
if (width > maxWidth) break;
count++;
}
return count;
}
private string CreateLine(string[] words, int start, int count, int maxWidth, bool isLastLine) {
StringBuilder sb = new StringBuilder();
// 如果是最后一行或只有一个单词,使用左对齐
if (isLastLine || count == 1) {
for (int i = 0; i < count; i++) {
if (i > 0) sb.Append(' ');
sb.Append(words[start + i]);
}
while (sb.Length < maxWidth) sb.Append(' ');
return sb.ToString();
}
// 计算空格
int totalSpaces = maxWidth;
for (int i = 0; i < count; i++) {
totalSpaces -= words[start + i].Length;
}
// 计算每个间隔的空格数
int gaps = count - 1;
int spacesPerGap = totalSpaces / gaps;
int extraSpaces = totalSpaces % gaps;
// 构建当前行
for (int i = 0; i < count; i++) {
if (i > 0) {
int spaces = spacesPerGap + (extraSpaces-- > 0 ? 1 : 0);
sb.Append(new string(' ', spaces));
}
sb.Append(words[start + i]);
}
return sb.ToString();
}
}
Python 实现
class Solution:
def fullJustify(self, words: List[str], maxWidth: int) -> List[str]:
result = []
i = 0
while i < len(words):
# 计算当前行可以放多少个单词
line_words = []
line_len = 0
while i < len(words) and line_len + len(words[i]) + len(line_words) <= maxWidth:
line_words.append(words[i])
line_len += len(words[i])
i += 1
# 处理最后一行或只有一个单词的情况
if i == len(words) or len(line_words) == 1:
line = ' '.join(line_words)
line += ' ' * (maxWidth - len(line))
result.append(line)
continue
# 计算空格
total_spaces = maxWidth - line_len
gaps = len(line_words) - 1
spaces_per_gap = total_spaces // gaps
extra_spaces = total_spaces % gaps
# 构建当前行
line = ''
for j in range(len(line_words)):
line += line_words[j]
if j < len(line_words) - 1:
spaces = spaces_per_gap + (1 if j < extra_spaces else 0)
line += ' ' * spaces
result.append(line)
return result
C++ 实现
class Solution {
public:
vector<string> fullJustify(vector<string>& words, int maxWidth) {
vector<string> result;
int i = 0;
while (i < words.size()) {
// 计算当前行可以放多少个单词
vector<string> lineWords;
int lineLen = 0;
while (i < words.size() && lineLen + words[i].length() + lineWords.size() <= maxWidth) {
lineWords.push_back(words[i]);
lineLen += words[i].length();
i++;
}
// 处理最后一行或只有一个单词的情况
if (i == words.size() || lineWords.size() == 1) {
string line = lineWords[0];
for (int j = 1; j < lineWords.size(); j++) {
line += " " + lineWords[j];
}
line += string(maxWidth - line.length(), ' ');
result.push_back(line);
continue;
}
// 计算空格
int totalSpaces = maxWidth - lineLen;
int gaps = lineWords.size() - 1;
int spacesPerGap = totalSpaces / gaps;
int extraSpaces = totalSpaces % gaps;
// 构建当前行
string line = lineWords[0];
for (int j = 1; j < lineWords.size(); j++) {
int spaces = spacesPerGap + (j <= extraSpaces ? 1 : 0);
line += string(spaces, ' ') + lineWords[j];
}
result.push_back(line);
}
return result;
}
};
执行结果
- 执行用时:96 ms
- 内存消耗:38.1 MB
代码亮点
- 🎯 清晰的分步处理逻辑
- 💡 巧妙处理空格分配
- 🔍 特殊情况处理完善
- 🎨 代码结构模块化
常见错误分析
- 🚫 空格分配计算错误
- 🚫 最后一行处理错误
- 🚫 单词行处理错误
- 🚫 未考虑边界情况
解法对比
| 解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 分步处理 | O(n) | O(maxWidth) | 逻辑清晰 | 代码较长 |
| 一次遍历 | O(n) | O(maxWidth) | 代码简短 | 难以维护 |
| 递归处理 | O(n) | O(n) | 结构清晰 | 性能较差 |