【问题标题】:Text Justification Algorithm文本对齐算法
【发布时间】:2018-06-25 09:03:09
【问题描述】:

这是 DP 中一个非常著名的问题,有人可以帮助可视化它的递归部分。如何生成排列或组合。

问题参考。 https://www.geeksforgeeks.org/dynamic-programming-set-18-word-wrap/

【问题讨论】:

标签: java recursion dynamic-programming


【解决方案1】:

鉴于最大线宽为 L,证明文本 T 的想法是考虑文本的所有后缀(考虑单词而不是字符来形成后缀是精确的。) 动态编程不过是“小心蛮力”。 如果您考虑使用蛮力方法,则需要执行以下操作。

  1. 考虑将 1、2、..n 个单词放在第一行。
  2. 对于案例 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 => 此时已无字可写。

总结一下:

  1. 子问题:后缀、单词[:i]
  2. 猜测:从哪里开始下一行,选项数 n - i -> O(n)
  3. 重复: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();
    }
  }

【讨论】:

  • 计算文本和成本的好方法。但是这段代码的唯一问题是它运行在O(n^3) 时间而不是O(n^2)。您可以通过删除对您的 calcBadness 函数的调用将其改进为 O(n^2),因为它实际上计算了 O(n) 时间的错误。我将使用spaceOccupied += (words[j-1].length()+1) 即时计算覆盖的总长度,然后找到错误为(L-spaceOccupied) * (L-spaceOccupied) 并在spaceOccupied > L 时打破条件。在内部for 循环开始之前初始化spaceOccupied = -1。希望这会有所帮助。
猜你喜欢
  • 1970-01-01
  • 2020-07-12
  • 2019-05-04
  • 2015-02-12
  • 2020-05-23
  • 2017-02-17
  • 2021-12-20
  • 2010-10-22
  • 1970-01-01
相关资源
最近更新 更多