在段落中添加N个换行符以获得最窄的结果

时间:2016-06-02 15:45:39

标签: arrays string algorithm partitioning

假设我们有一个这样的段落:

  

Lorem ipsum dolor sit amet,consectetur adipiscing elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua。 Ut enim ad minim veniam,quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat。 Duis aute irure dolor in repreptderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur。 Excepteur sint occaecat cupidatat non proident,sunt in culpa qui officia deserunt mollit anim id est laborum。

假设固定宽度字体,我们想要准确添加N个换行符(仅通过替换空格字符)来生成N + 1行文本块。

以下是N = 8的输出示例,我们得到的最大线宽为51:

Lorem ipsum dolor sit amet, consectetur adipiscing 
elit, sed do eiusmod tempor incididunt ut labore 
et dolore magna aliqua. Ut enim ad minim veniam, 
quis nostrud exercitation ullamco laboris nisi ut 
aliquip ex ea commodo consequat. Duis aute irure 
dolor in reprehenderit in voluptate velit esse 
cillum dolore eu fugiat nulla pariatur. Excepteur 
sint occaecat cupidatat non proident, sunt in culpa 
qui officia deserunt mollit anim id est laborum.

我们如何找到用换行符替换哪些空格字符以获得最少尝试的最窄(最长行上的字符数最少)?

3 个答案:

答案 0 :(得分:6)

假设文本由m个单词组成,我们将从1到m进行编号。将f(i,j)定义为子问题的最佳(最小宽度)解中任何一行的最大宽度(以字符为单位),该子问题仅由前i个单词组成,在使用正好j个换行符的限制下。然后,f(m,n)给出整个问题的最佳可能中断序列的宽度。使用dynamic programming可以相当有效地解决这个问题。

令字i和字j> = i之间的片段的字符总长度为len(i,j)。 (这很容易计算:只计算m + 1值len0 [j]的数组,对于0 <= j <= m,每个给出由前j个单词组成的片段的字符总长度;然后len(i,j)只是len0 [j] - len0 [i-1] - 1,其约定为len0 [0] = -1。)

基本案例很简单:

f(i, 0) = len(0, i)   (i.e., if there are no line breaks)

递归案例是:

f(i, j) = the minimum over all 0 <= k < i of max(f(k, j-1), len(k+1, i))

也就是说,为了找到将前i个单词分成j + 1行(即使用j换行符)的最佳方法,我们可以为每个较短的k-word前缀尝试以下内容:确定打破该单词的最佳方法k字前缀为j行(即使用j-1换行符),并将我们从中获得的最大宽度与将其余的ik字放在最后一行上的宽度进行比较。每个前缀为我们提供了一个不同的候选解决方案,因此我们可以选择其中最好的一个。

构建解决方案

现在我们可以计算最佳宽度f(m,n),我们如何使用它来实际构建解决方案?幸运的是,动态编程中有一种标准技术。最快的方法是在计算f(i,j)期间记录(实际上 a ,因为通常可能存在多个最优解)k值在前趋表中产生最小值PRED [i] [j]。计算出f(m,n)并填入前一个表后,我们可以通过向前走来构造一个最优解:pred [i] [j]告诉我们一个值k,这样我们就可以通过添加来产生最优解单词k之后的换行符,所以在那里添加一个换行符,然后查看pred [k] [j-1]以找到前一个换行符的位置,继续执行直到j到达0。

时间和空间复杂性

如果递归是memoised使用动态编程,则最多有O(mn)个不同的参数组合可以调用f()(i的范围在0到m之间,j的范围在0到0之间) n),并且在递归调用之外花费的时间是O(m)(k可以在0和m之间,并且k的每个值的计算是O(1)),因此该解决方案的时间复杂度是O(nm ^ 2)空间复杂度为O(mn),但通过切换到自下而上的DP,可以很容易地将其简化为 O(m),因为在计算f(i, j)我们只需要访问第二个参数为j-1的f()值,这意味着它只需要实际存储计算值f(q,j-1)的size-(m + 1)数组就足够了对于0&lt; = q&lt; = m。

答案 1 :(得分:5)

(改编自此处,How to partition an array of integers in a way that minimizes the maximum of the sum of each partition?

如果我们将单词长度视为数字列表,我们可以二进制搜索分区。

我们的max length范围从0sum (word-length list) + (num words - 1), meaning the spacesmid = (range / 2)。我们通过在mid时间内划分为N集来检查是否可以实现O(m):遍历列表,将(word_length + 1)添加到当前部分,同时当前总和小于或等于mid。当总和通过mid时,开始一个新的部分。如果结果包含N个或更少的部分,则mid是可以实现的。

如果mid可以实现,请尝试较低的范围;否则,更高的范围。时间复杂度为O(m log num_chars)。 (你还必须考虑如何删除每个部分的空格,意味着换行的位置,计算中的特征。)

答案 2 :(得分:3)

我的尝试并错误尝试。 我不太确定你是否总是得到最短的线宽,但算法快速且易于理解/实现。我认为在大多数情况下这应该符合需求

  • 假设您有M个字符,并且您希望插入N换行符。然后,您获得最短的线宽:L_min = RoundToInfinity((M-N)/N+1)N-M,因为我们清除N个空格)
  • 使用单词填充每一行,以使线宽小于或等于L_min。这样你的最后一行将包含比其他人更多的字符。
  • 现在总是搜索最大的一行(在开始时这将是最后一行),取第一个单词并将其放在前一行的和。重复直到第一行是最长的。
  • 您应始终存储实际的最大线宽L,这样您就可以在L最小时恢复坐姿。