文本左右对齐
在 LeetCode 上查看 ↗语音讲解
开车或通勤时可听,跟着思路走一遍
题目描述
给定单词数组 words 和行宽 maxWidth,重新排版使每行恰好 maxWidth 个字符,且除最后一行外左右两端对齐。
使用贪心:每行尽可能多放单词;行间用空格填充至行宽。若空格不能均分,左侧间隙比右侧多。最后一行左对齐,单词间仅一个空格,行尾用空格补齐至 maxWidth。
单词由非空格字符组成;每个单词长度在 (0, maxWidth];words 非空。
示例 1
第一行两端对齐;最后一行左对齐且末尾补空格。
示例 2
仅一个单词的行(如第二行)左对齐;最后一行单词间单空格,尾部补空格。
示例 3
模拟答题者思考
1. 暴力:枚举所有「在哪断行」的组合,对每种方案检查能否填满每行并评分——组合数指数级,不可行。
2. 题目要求贪心放词:每行在不超过 maxWidth 的前提下尽量多放单词(单词间至少 1 空格)。断行方式因此唯一确定,只需模拟排版。
3. 重复在哪里?每一行的构造逻辑相同:先贪心定 [i,j),再按「是否最后一行 / 是否单词数为 1」分支处理空格。
4. 最后一行或本行只有一个单词:左对齐,单词间单空格,行尾补空格至 maxWidth(不能两端对齐)。
5. 否则两端对齐:总空格均分到 gaps 个间隙,余数 extra 从左到右前 extra 个间隙各 +1,保证左侧空格不少于右侧。
变量语义(先读这三句再编码)
| 变量 | 类型 | 语义(三句法) |
|---|---|---|
i | int | 定义:当前待排版行的第一个单词在 words 中的下标,初始为 0维护:每输出一行后, i 跳到该行最后一个单词的下一位置更新: i = j,其中 j 为本行贪心选出的单词区间右端点(不含) |
j | int | 定义:本行单词区间的右边界(开区间),即本行单词为 words[i..j-1]维护:从 i+1 起贪心扩展:若再加入 words[j] 后「字符 + 单词间至少 1 空格」仍 ≤ maxWidth,则 j++更新:无法再放单词时停止,用 [i, j) 构造当前行 |
num_words / gaps | int | 定义:num_words = j - i;两端对齐时单词间间隙数为 gaps = num_words - 1维护:本行字符总长 total_chars = sum(len(words[k]) for k in range(i,j)),待分配空格 total_spaces = maxWidth - total_chars更新:均分 base = total_spaces // gaps,前 extra = total_spaces % gaps 个间隙各多 1 个空格(左侧优先) |
落码步骤
1. res = [],i = 0;当 i < n 时处理一行
2. 贪心:令 line_len = len(words[i]),j = i+1;当 j < n 且 line_len + 1 + len(words[j]) <= maxWidth 时更新 line_len 并 j++
3. 若 j == n(最后一行)或 j - i == 1:行串 " ".join(words[i:j]),末尾补 " " * (maxWidth - len(line))
4. 否则:计算 total_spaces 与 gaps,循环拼接单词与 base + (1 if k < extra else 0) 个空格
5. res.append(line),i = j,直至单词用完
代码实现
class Solution:
def fullJustify(self, words: list[str], maxWidth: int) -> list[str]:
res = []
i, n = 0, len(words)
while i < n:
line_len = len(words[i])
j = i + 1
while j < n and line_len + 1 + len(words[j]) <= maxWidth:
line_len += 1 + len(words[j])
j += 1
cnt = j - i
if j == n or cnt == 1:
line = " ".join(words[i:j])
line += " " * (maxWidth - len(line))
else:
chars = sum(len(w) for w in words[i:j])
spaces = maxWidth - chars
gaps = cnt - 1
base, extra = divmod(spaces, gaps)
parts = []
for k in range(cnt - 1):
parts.append(words[i + k])
parts.append(" " * (base + (1 if k < extra else 0)))
parts.append(words[j - 1])
line = "".join(parts)
res.append(line)
i = j
return res
class Solution {
public:
vector<string> fullJustify(vector<string>& words, int maxWidth) {
vector<string> res;
int n = (int)words.size();
for (int i = 0; i < n; ) {
int lineLen = (int)words[i].size();
int j = i + 1;
while (j < n && lineLen + 1 + (int)words[j].size() <= maxWidth) {
lineLen += 1 + (int)words[j].size();
++j;
}
int cnt = j - i;
string line;
if (j == n || cnt == 1) {
for (int k = i; k < j; ++k) {
if (k > i) line += ' ';
line += words[k];
}
line += string(maxWidth - (int)line.size(), ' ');
} else {
int chars = 0;
for (int k = i; k < j; ++k) chars += (int)words[k].size();
int spaces = maxWidth - chars;
int gaps = cnt - 1;
int base = spaces / gaps, extra = spaces % gaps;
for (int k = 0; k < cnt - 1; ++k) {
line += words[i + k];
int s = base + (k < extra ? 1 : 0);
line += string(s, ' ');
}
line += words[j - 1];
}
res.push_back(line);
i = j;
}
return res;
}
};
// 时间 O(n·maxWidth),空间 O(n)
复杂度分析
O(n · maxWidth)(n 为单词数,每词至多参与一行划分)
O(n)(结果行列表)
常见坑
最后一行仍做两端对齐:必须判断 j == n,最后一行只能左对齐 + 尾部补空格。
单行只有一个单词时误用均分空格:该行同样左对齐,右侧用空格填满。
贪心放词时忘记单词间至少 1 空格:扩展条件应是 line_len + 1 + len(words[j]) <= maxWidth,不是直接加单词长度。
必测边界 Case
words=["a"], maxWidth=5 → "a "
"shall be" 在行宽 16 下为 "shall be "(词间单空格,尾部补满)
"What must be":3 词 2 间隙,多余空格给左侧间隙
"acknowledgment" 在行宽 16 → 词后补 2 个空格