LeetCode 68:文本左右对齐

问题定义与核心挑战
给定单词数组 words
和最大行宽 maxWidth
,需将单词排版为每行恰好 maxWidth
个字符的文本,满足:
- 贪心排版:每行尽可能多放单词(优先填满)。
- 空格规则 :
- 最后一行或单行单词:左对齐,单词间一个空格,剩余空格补在末尾。
- 其他行:空格均匀分配 ,若无法均分,左侧间隔的空格数多于右侧。
核心思路:分组 + 空格精细化分配
- 分组单词:逐行确定能容纳的单词(考虑单词间至少一个空格)。
- 空格计算 :
- 对每组单词,计算总空格数,区分最后一行/单行 和普通行的分配逻辑。
- 构造行字符串 :根据空格规则拼接单词和空格,确保长度严格为
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. " |
该方法通过贪心分组 和精细化空格分配,严格满足题目规则,高效处理所有边界情况,是文本排版问题的经典实现。