LeetCode 68:文本左右对齐

LeetCode 68:文本左右对齐

问题定义与核心挑战

给定单词数组 words 和最大行宽 maxWidth,需将单词排版为每行恰好 maxWidth 个字符的文本,满足:

  1. 贪心排版:每行尽可能多放单词(优先填满)。
  2. 空格规则
    • 最后一行或单行单词:左对齐,单词间一个空格,剩余空格补在末尾。
    • 其他行:空格均匀分配 ,若无法均分,左侧间隔的空格数多于右侧

核心思路:分组 + 空格精细化分配

  1. 分组单词:逐行确定能容纳的单词(考虑单词间至少一个空格)。
  2. 空格计算
    • 对每组单词,计算总空格数,区分最后一行/单行普通行的分配逻辑。
  3. 构造行字符串 :根据空格规则拼接单词和空格,确保长度严格为 maxWidth

算法步骤详解

步骤 1:分组单词(贪心排版)

遍历单词,逐步累加,确定每行能容纳的单词:

  • 初始状态currentLine 存储当前行单词,currentLen 记录当前行已用长度(初始为 0)。
  • 遍历逻辑
    • 若当前行空,直接加入单词,currentLen 设为单词长度。
    • 否则,计算加入下一个单词后的长度(需加 1 个空格),若不超过 maxWidth 则加入,否则当前行闭合,开始新行。
java 复制代码
List<List<String>> lines = new ArrayList<>();
List<String> currentLine = new ArrayList<>();
int currentLen = 0;

for (String word : words) {
    if (currentLine.isEmpty()) {
        currentLine.add(word);
        currentLen = word.length();
    } else {
        int nextLen = currentLen + 1 + word.length(); // 加1是单词间的空格
        if (nextLen <= maxWidth) {
            currentLine.add(word);
            currentLen = nextLen;
        } else {
            lines.add(currentLine); // 闭合当前行
            currentLine = new ArrayList<>();
            currentLine.add(word);
            currentLen = word.length();
        }
    }
}
if (!currentLine.isEmpty()) { // 处理最后一行
    lines.add(currentLine);
}
步骤 2:处理每行的空格分配

对每组单词,分两种情况处理:

情况 1:最后一行 或 单行单词(左对齐)
  • 单词间用 1 个空格 连接,剩余空格补在行尾
情况 2:普通行(空格均匀分配)
  • 计算总空格数:totalSpaces = maxWidth - 单词总长度
  • 间隔数:gaps = 单词数 - 1(如 3 个单词有 2 个间隔)。
  • 基础空格:base = totalSpaces / gaps(每个间隔的基本空格数)。
  • 多余空格:extra = totalSpaces % gaps(前 extra 个间隔需多 1 个空格)。
步骤 3:构造行字符串

根据上述规则,拼接单词和空格:

java 复制代码
List<String> result = new ArrayList<>();
for (int i = 0; i < lines.size(); i++) {
    List<String> line = lines.get(i);
    int numWords = line.size();
    int totalSpaces = maxWidth;
    for (String word : line) { // 计算单词总长度,得到需分配的空格数
        totalSpaces -= word.length();
    }

    StringBuilder sb = new StringBuilder();
    if (numWords == 1 || i == lines.size() - 1) { // 最后一行或单行
        sb.append(String.join(" ", line)); // 单词间1个空格
        int remaining = maxWidth - sb.length(); // 补全行尾空格
        for (int j = 0; j < remaining; j++) {
            sb.append(" ");
        }
    } else { // 普通行,均匀分配空格
        int gaps = numWords - 1;
        int base = totalSpaces / gaps;
        int extra = totalSpaces % gaps;
        for (int j = 0; j < numWords; j++) {
            sb.append(line.get(j));
            if (j < gaps) { // 非最后一个单词,加空格
                int spaceCount = base + (j < extra ? 1 : 0); // 前extra个间隔多1个空格
                for (int k = 0; k < spaceCount; k++) {
                    sb.append(" ");
                }
            }
        }
    }
    result.add(sb.toString());
}

完整代码(Java)

java 复制代码
import java.util.ArrayList;
import java.util.List;

class Solution {
    public List<String> fullJustify(String[] words, int maxWidth) {
        // 步骤1:分组单词,确定每行的单词
        List<List<String>> lines = new ArrayList<>();
        List<String> currentLine = new ArrayList<>();
        int currentLen = 0;

        for (String word : words) {
            if (currentLine.isEmpty()) {
                currentLine.add(word);
                currentLen = word.length();
            } else {
                int nextLen = currentLen + 1 + word.length();
                if (nextLen <= maxWidth) {
                    currentLine.add(word);
                    currentLen = nextLen;
                } else {
                    lines.add(currentLine);
                    currentLine = new ArrayList<>();
                    currentLine.add(word);
                    currentLen = word.length();
                }
            }
        }
        if (!currentLine.isEmpty()) {
            lines.add(currentLine);
        }

        // 步骤2:处理每行的空格,构造结果
        List<String> result = new ArrayList<>();
        for (int i = 0; i < lines.size(); i++) {
            List<String> line = lines.get(i);
            int numWords = line.size();
            int totalSpaces = maxWidth;
            for (String word : line) {
                totalSpaces -= word.length();
            }

            StringBuilder sb = new StringBuilder();
            if (numWords == 1 || i == lines.size() - 1) {
                // 最后一行或单行:左对齐,补空格到末尾
                sb.append(String.join(" ", line));
                int remaining = maxWidth - sb.length();
                for (int j = 0; j < remaining; j++) {
                    sb.append(" ");
                }
            } else {
                // 普通行:均匀分配空格,左侧间隔多
                int gaps = numWords - 1;
                int base = totalSpaces / gaps;
                int extra = totalSpaces % gaps;
                for (int j = 0; j < numWords; j++) {
                    sb.append(line.get(j));
                    if (j < gaps) {
                        int spaceCount = base + (j < extra ? 1 : 0);
                        for (int k = 0; k < spaceCount; k++) {
                            sb.append(" ");
                        }
                    }
                }
            }
            result.add(sb.toString());
        }

        return result;
    }
}

关键逻辑解析

1. 分组的贪心策略
  • 优先填满行:每次尝试加入下一个单词,仅当超过 maxWidth 时才闭合当前行,保证每行单词数最多。
2. 空格分配的细节
  • 最后一行 :单词间仅 1 个空格,剩余空格补在末尾(如示例 1 中最后一行 "justification. ")。
  • 普通行 :通过 baseextra 确保空格均匀,左侧间隔多(如示例 1 中第一行 "This is an",两个间隔各 4 个空格)。
3. 边界处理
  • 单行单词(如 ["a"]maxWidth=5):直接左对齐,补 4 个空格到行尾("a ")。

示例验证(以示例 1 为例)

输入words = ["This","is","an","example","of","text","justification."], maxWidth=16

行索引 单词列表 处理逻辑 输出字符串
0 ["This","is","an"] 普通行,空格数 8,间隔 2 → 每个间隔 4 个空格 "This is an"
1 ["example","of","text"] 普通行,空格数 5,间隔 2 → 前 1 个间隔 3 个空格,后 1 个间隔 2 个空格 → "example of text"(实际计算:example(7)+3+of(2)+2+text(4)=7+3+2+2+4=18?不对,哦,重新计算:单词总长度7+2+4=13,maxWidth=16,空格数3。间隔数2,base=1,extra=1。所以前1个间隔2个空格,后1个间隔1个空格 → 7+2+2+1+4=16 → "example of text"
2 ["justification."] 最后一行,左对齐,补 1 个空格 → "justification. "

该方法通过贪心分组精细化空格分配,严格满足题目规则,高效处理所有边界情况,是文本排版问题的经典实现。

相关推荐
Jerry2 分钟前
LeetCode 28. 找出字符串中第一个匹配项的下标
算法
Jerry2 小时前
LeetCode 459. 重复的子字符串
算法
海石5 小时前
1500分的题目,确实有实力,不过还是我略胜一筹
算法·leetcode
海石5 小时前
【记忆化搜索】条条大路通AC,走好适合你的那一条,走到后再考虑走得快
算法·leetcode
Jerry7 小时前
LeetCode 151. 反转字符串中的单词
算法
F20226974867 小时前
西门子 PLC 与 C# 通信
开发语言·c#
a1117769 小时前
LM 算法迭代过程动画演示(SLAM)
算法
头茬韭菜9 小时前
Context 的生死抉择:四层压缩、截断算法与 Session Memory
算法·ai
Jerry9 小时前
LeetCode 541. 反转字符串 II
算法
Jerry10 小时前
LeetCode 344. 反转字符串
算法