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. "

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

相关推荐
卡洛斯(编程版4 分钟前
(1) 哈希表全思路-20天刷完Leetcode Hot 100计划
python·算法·leetcode
mit6.82430 分钟前
Linux下C#项目构建
开发语言·c#
NAGNIP1 小时前
DeepSeekMoE 架构解析
算法
不喜欢学数学er1 小时前
算法第五十二天:图论part03(第十一章)
算法·深度优先·图论
养成系小王1 小时前
四大常用排序算法
数据结构·算法·排序算法
NAGNIP1 小时前
一文搞懂DeepSeek LLM
算法
已读不回1431 小时前
设计模式-策略模式
前端·算法·设计模式
Nita.2 小时前
.NET 中的延迟初始化:Lazy<T> 与LazyInitializer
c#·.net
MrZhangBaby2 小时前
SQL-leetcode—3374. 首字母大写 II
linux·sql·leetcode
CoovallyAIHub2 小时前
标注成本骤降,DINOv3炸裂发布!冻结 backbone 即拿即用,性能对标SOTA
深度学习·算法·计算机视觉