鉴于最大线宽为 L,证明文本 T 的想法是考虑文本的所有后缀(考虑单词而不是字符来形成后缀是精确的。)
动态编程不过是“小心蛮力”。
如果您考虑使用蛮力方法,则需要执行以下操作。
- 考虑将 1、2、..n 个单词放在第一行。
- 对于案例 1 中描述的每个案例(假设 i 个单词放在第 1 行),考虑将 1、2、.. n -i 个单词放在第二行,然后将剩余单词放在第三行等等的情况。 .
相反,让我们只考虑问题,找出将单词放在行首的成本。
一般来说,我们可以将 DP(i) 定义为将第 (i-1) 个单词视为行首的成本。
我们如何为 DP(i) 形成递归关系?
如果第 j 个单词是下一行的开头,那么当前行将包含 words[i:j)(不包括 j),并且第 j 个单词作为下一行开头的成本将为 DP(j)。
因此 DP(i) = DP(j) + 将 words[i:j) 放入当前行的成本
由于我们想最小化总成本,DP(i)可以定义如下。
重复关系:
DP(i) = min { DP(j) + 放置单词的成本[i:j 在当前行}
对于 [i+1, n] 中的所有 j
注意 j = n 表示下一行没有字可放。
基本情况:DP(n) = 0 => 此时已无字可写。
总结一下:
- 子问题:后缀、单词[:i]
- 猜测:从哪里开始下一行,选项数 n - i -> O(n)
- 重复:DP(i) = min {DP(j) + 将 words[i:j) 放入当前行的成本}
如果我们使用记忆化,大括号内的表达式应该花费 O(1) 时间,并且循环运行 O(n) 次(# of choice times)。
i 从 n 变化到 0 => 因此总复杂度降低到 O(n^2)。
现在即使我们导出了证明文本的最小成本,我们还需要通过跟踪上面表达式中选择为最小值的 j 值来解决原始问题,以便以后可以使用相同的值来打印出合理的文字。这个想法是保留父指针。
希望这有助于您了解解决方案。下面是上述思想的简单实现。
public class TextJustify {
class IntPair {
//The cost or badness
final int x;
//The index of word at the beginning of a line
final int y;
IntPair(int x, int y) {this.x=x;this.y=y;}
}
public List<String> fullJustify(String[] words, int L) {
IntPair[] memo = new IntPair[words.length + 1];
//Base case
memo[words.length] = new IntPair(0, 0);
for(int i = words.length - 1; i >= 0; i--) {
int score = Integer.MAX_VALUE;
int nextLineIndex = i + 1;
for(int j = i + 1; j <= words.length; j++) {
int badness = calcBadness(words, i, j, L);
if(badness < 0 || badness == Integer.MAX_VALUE) break;
int currScore = badness + memo[j].x;
if(currScore < 0 || currScore == Integer.MAX_VALUE) break;
if(score > currScore) {
score = currScore;
nextLineIndex = j;
}
}
memo[i] = new IntPair(score, nextLineIndex);
}
List<String> result = new ArrayList<>();
int i = 0;
while(i < words.length) {
String line = getLine(words, i, memo[i].y);
result.add(line);
i = memo[i].y;
}
return result;
}
private int calcBadness(String[] words, int start, int end, int width) {
int length = 0;
for(int i = start; i < end; i++) {
length += words[i].length();
if(length > width) return Integer.MAX_VALUE;
length++;
}
length--;
int temp = width - length;
return temp * temp;
}
private String getLine(String[] words, int start, int end) {
StringBuilder sb = new StringBuilder();
for(int i = start; i < end - 1; i++) {
sb.append(words[i] + " ");
}
sb.append(words[end - 1]);
return sb.toString();
}
}