#68 贪心 困难

文本左右对齐

在 LeetCode 上查看 ↗

🎧 语音讲解

开车或通勤时可听,跟着思路走一遍

速度

📋 题目描述

给定单词数组 words 和行宽 maxWidth,重新排版使每行恰好 maxWidth 个字符,且除最后一行外左右两端对齐。

使用贪心:每行尽可能多放单词;行间用空格填充至行宽。若空格不能均分,左侧间隙比右侧多。最后一行左对齐,单词间仅一个空格,行尾用空格补齐至 maxWidth。

单词由非空格字符组成;每个单词长度在 (0, maxWidth];words 非空。

示例 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
输出:6 行,每行长度均为 20(见 LeetCode 题面)。

💭 模拟答题者思考

1. 暴力:枚举所有「在哪断行」的组合,对每种方案检查能否填满每行并评分——组合数指数级,不可行。

2. 题目要求贪心放词:每行在不超过 maxWidth 的前提下尽量多放单词(单词间至少 1 空格)。断行方式因此唯一确定,只需模拟排版。

3. 重复在哪里?每一行的构造逻辑相同:先贪心定 [i,j),再按「是否最后一行 / 是否单词数为 1」分支处理空格。

4. 最后一行或本行只有一个单词:左对齐,单词间单空格,行尾补空格至 maxWidth(不能两端对齐)。

5. 否则两端对齐:总空格均分到 gaps 个间隙,余数 extra 从左到右前 extra 个间隙各 +1,保证左侧空格不少于右侧。

🧠 变量语义(先读这三句再编码)

变量类型语义(三句法)
iint定义:当前待排版行的第一个单词在 words 中的下标,初始为 0
维护:每输出一行后,i 跳到该行最后一个单词的下一位置
更新:i = j,其中 j 为本行贪心选出的单词区间右端点(不含)
jint定义:本行单词区间的右边界(开区间),即本行单词为 words[i..j-1]
维护:从 i+1 起贪心扩展:若再加入 words[j] 后「字符 + 单词间至少 1 空格」仍 ≤ maxWidth,则 j++
更新:无法再放单词时停止,用 [i, j) 构造当前行
num_words / gapsint定义: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

Case 1:仅一个词
words=["a"], maxWidth=5 → "a "
Case 2:最后一行多词
"shall be" 在行宽 16 下为 "shall be "(词间单空格,尾部补满)
Case 3:空格余数偏左
"What must be":3 词 2 间隙,多余空格给左侧间隙
Case 4:整行一词占满
"acknowledgment" 在行宽 16 → 词后补 2 个空格
Case 5:行宽等于单词长
每行只能放一个单词,全部左对齐补尾空格