Dynamic Programming · 52 of 60
Word Wrap Problem
Break a list of words into lines of a fixed width so the empty space at the ends of the lines looks as even as possible.
- Simple way
- time O(2^n)
room O(n) - Clever way
- time O(n^2)
room O(n)
The problem, simply
You get the lengths of n words, in order, and a line width k. Put the words on lines, keeping their order. Words on the same line need one space between them, and a line may not be longer than k. Every word fits on a line by itself.
A line with e empty spaces left at its end costs e x e. The last line is free, because a short last line looks fine. Give back the smallest total cost.
A story to remember it
Picture it
Words of length 3, 2, 2, 5 on a page 6 wide. The best page leaves 3 empty boxes on line 1 (cost 9) and 1 on line 2 (cost 1). The last line is free. Total 10.
- rejected
- ignored
Filling each line as much as possible looks tidier at the top but costs more:
Greedy packing: aaa bb fills line 1 exactly (cost 0), but then cc sits alone with 4 empty boxes (cost 16). Total 16.
- rejected
- ignored
Examples
Greedy loses
- In
nums = [3, 2, 2, 5], k = 6- Out
10
Lines: [3], [2, 2], [5]. Costs 9 + 1 + 0.
Each word alone
- In
nums = [3, 2, 2], k = 4- Out
5
2 + 1 + 2 = 5 is too wide, so the lines are [3], [2], [2]: costs 1 + 4 + 0.
Everything fits
- In
nums = [2, 3], k = 10- Out
0
One line, and it is the last line, so it is free.
One word
- In
nums = [5], k = 5- Out
0
The only line is the last line.
Way 1: the simple way
f(i) is the best cost for the words from i to the end.
- If no words are left, the cost is 0.
- Try putting words
itojon the first line, forj = i, i + 1, ...as long as they fit. - That line costs
extra x extra, or 0 if it holds the last word. Addf(j + 1). - Keep the smallest.
The calls for [3, 2, 2, 5] with width 6. The number on each line is that line's cost. f2 and f3 are worked out more than once.
- checking
class Solution {
public int solveWordWrap(int[] nums, int k) {
return best(nums, k, 0);
}
// best cost for words i..n-1
private int best(int[] nums, int k, int i) {
int n = nums.length;
if (i == n) return 0;
int answer = Integer.MAX_VALUE;
int len = -1; // so the first word adds no gap
for (int j = i; j < n; j++) {
len += nums[j] + 1; // the word plus one gap
if (len > k) break; // does not fit
int extra = k - len;
int lineCost = (j == n - 1) ? 0 : extra * extra; // last line is free
answer = Math.min(answer, lineCost + best(nums, k, j + 1));
}
return answer;
}
}How long it takes: every place between two words is either a line break or not, so up to O(2^n) plans. Extra room: the call chain, O(n).
Way 2: the clever way
There are only n different questions f(0), f(1), ..., f(n - 1). Answer them from the back, in a row cost[0..n], so each one only reads answers that are already there.
cost[n] = 0.- For
ifromn - 1down to 0: grow the first line one word at a time. For each linei..jthat fits, trylineCost + cost[j + 1]. cost[i]is the smallest try. Returncost[0].
Fill cost[i] from the last word back to the first
01 / 11
Work from the end. cost[4] = 0, because with no words left there is nothing to pay. The page is 6 wide.
- looking now
- checking
- done
- answer
- rejected
- just added
- ignored
class Solution {
public int solveWordWrap(int[] nums, int k) {
int n = nums.length;
int[] cost = new int[n + 1]; // cost[n] = 0: no words left
for (int i = n - 1; i >= 0; i--) {
cost[i] = Integer.MAX_VALUE;
int len = -1;
for (int j = i; j < n; j++) {
len += nums[j] + 1; // add word j and a gap
if (len > k) break; // line too long
int extra = k - len;
int lineCost = (j == n - 1) ? 0 : extra * extra;
cost[i] = Math.min(cost[i], lineCost + cost[j + 1]);
}
}
return cost[0];
}
}How long it takes: for each start i, the line grows over at most n words, O(n^2) (and only O(n * k) when lines hold few words). Extra room: the row, O(n).
Watch out for
- The last line is free. Forgetting this makes the answer too big. But only the line that holds the last word is free.
- Count the gaps. A line with words
i..jhasj - igaps of one space. Startinglenat -1 and addingword + 1handles that neatly. - Read the cost rule. This version (GeeksforGeeks) uses squares. Some textbooks use cubes. The method is the same, only
lineCostchanges. extra * extracan overflow for very wide pages with cubes. Uselongif needed.
What interviewers ask next
- Print where the lines break. Store
brk[i] = j + 1for the best choice, then followbrkfrom 0. - Why squares and not plain extra spaces? With plain sums, one hole of 4 costs the same as two holes of 2. Squaring makes the big hole cost 16 and the two small ones only 8, so lines come out more even.
- Can it be faster than
O(n^2)? Yes, for square costs there are cleverO(n log n)methods (Hirschberg and Larmore), butO(n^2)is what interviewers expect. - Text justification (LeetCode 68) is a different question: it uses greedy packing and then spreads spaces inside each line.
Remember this
Grown-up words: the empty boxes are slack, the messy points are a squared penalty, breaking lines in order is the word wrap or line breaking problem, and solving from the back with a row is 1D dynamic programming over suffixes.