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. ")。
  • 普通行 :通过 base 和 extra 确保空格均匀,左侧间隔多(如示例 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. "

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

相关推荐
凤凰院凶涛QAQ1 小时前
《Java版数据结构 & 集合类剖析》Map & Set & 反射:“从哈希碰撞到红黑树转换,反射帮你扒开 HashMap 的桶数组看个究竟”
java·数据结构·算法
w_zero_one3 小时前
算法--模拟
java·数据结构·算法
秋天的怀念_David3 小时前
算法一千零一夜 · 第十三夜 幻方,武侠世界里的数学迷宫
java·程序人生·算法
zimoyin3 小时前
在 C# 里嵌入 JavaScript:用 Jint 做可热更新的自动化脚本(从入门到踩坑)
javascript·c#·自动化
秋天的怀念_David4 小时前
算法一千零一夜 · 第十四夜 幻方与奇妙的二进制
java·程序人生·算法
薛定e的猫咪4 小时前
(ICLR 2020)MAML 靠的不是快速学习,而是特征复用
人工智能·深度学习·学习·算法·机器学习
薛定e的猫咪4 小时前
(ICLR 2026)DF-META:面向 FJSP 的无导数元学习实例自适应调度
人工智能·深度学习·学习·算法·机器学习
java_nnnn6 小时前
LeetCode刷题滑动窗口(水果成篮+找到字符串中所有字母的异位词)Java
java·开发语言·leetcode
小羊没烦恼!6 小时前
异步编程:线程概述及使用
java·开发语言·mysql·算法·c#
wanderist.6 小时前
202609CSP认证P3
算法