[LeetCode#68] Text Justification
Problem:
Given an array of words and a length L, format the text such that each line has exactly L characters and is fully (left and right) justified.
You should pack your words in a greedy approach; that is, pack as many words as you can in each line. Pad extra spaces ' '
when necessary so that each line has exactly Lcharacters.
Extra spaces between words should be distributed as evenly as possible. If the number of spaces on a line do not divide evenly between words, the empty slots on the left will be assigned more spaces than the slots on the right.
For the last line of text, it should be left justified and no extra space is inserted between words.
For example,
words: ["This", "is", "an", "example", "of", "text", "justification."]
L: 16
.
Return the formatted lines as:
[ "This is an", "example of text", "justification. " ]
Note: Each word is guaranteed not to exceed L in length.
Analysis:
There is no algorithm test behind this problem. It just tests your coding skills in implementation. Beief idea: Step 1. Scan the word array, and once you got enough words for the for a way, you use those words to construct a formatted row. During the scan, we only know the end word of current window after we scan the word after the window. (Which means our last row have to word to invoke the condition). Step 1.1 To convert an array of words into a a valid row, we need to take care following conditions, c1. there must be at least one space between two consecutive words. c2. If we have extra place, we should divide those extra place among first words. Wrong solution 1: public class Solution { public List<String> fullJustify(String[] words, int maxWidth) { List<String> ret = new ArrayList<String> (); if (words == null || words.length == 0) return ret; Queue<String> queue = new LinkedList<String> (); int word_count = 0; int len = 0; for (String word : words) { if (queue.isEmpty()) { queue.offer(word); word_count++; len += word.length(); } else{ if (len + 1 + word.length() <= maxWidth) { len++; word_count++; len += word.length(); } else{ adjustRow(queue, maxWidth, word_count, len, ret); len = 0; word_count = 0; queue.offer(word); } } } String last_row = ""; while (!queue.isEmpty()) { String word = queue.poll(); if (!last_row.equals("")) last_row += " "; last_row += word; } return ret; } private void adjustRow(Queue<String> queue, int maxWidth, int word_count, int len, List<String> ret) { if (queue.isEmpty()) return; String row_str = queue.poll(); if (word_count == 1) { ret.add(row_str); return; } //len should be the total length of word, not include " " string int avg_space = (maxWidth - len) / (word_count - 1); int extra_space = (maxWidth - len) % (word_count - 1); while (!queue.isEmpty()) { String word = queue.poll(); int count = avg_space; while (avg_space > count) { row_str += " "; count++; } if (extra_space != 0) { row_str += " "; extra_space--; } row_str += word; } ret.add(row_str); } } Input: [""] 0 Output: [] Expected: [""] Mistake analysis: Even though the basic idea is right. The above implementation is really really ugly. The most problemtic part comes out from trying to record the word's number and word's length in a window, which would definitely incure a lot of problems during those "if - else codition". ----------------------------------------------------------- int word_count = 0; int len = 0; for (String word : words) { if (queue.isEmpty()) { queue.offer(word); word_count++; len += word.length(); } else{ if (len + 1 + word.length() <= maxWidth) { len++; word_count++; len += word.length(); } else{ adjustRow(queue, maxWidth, word_count, len, ret); len = 0; word_count = 0; queue.offer(word); } } } ------------------------------------------------------------ For interview, you should never try to implement such complex mechanism. If you just redo the caculation of "word_count" and "len" at the adjust Row. The code could be much elegant! And thus save you a lot of energy. Don't worry about a little increse in time complexity. Just blindly save and improvement could trap you into a pitfall. Wrong solution 2: public class Solution { public List<String> fullJustify(String[] words, int maxWidth) { List<String> ret = new ArrayList<String> (); if (words == null || words.length == 0) return ret; Queue<String> queue = new LinkedList<String> (); int len = 0; for (String word : words) { if (queue.isEmpty()) { queue.offer(word); len += word.length(); } else{ if (len + 1 + word.length() <= maxWidth) { len++; len += word.length(); } else{ adjustRow(queue, maxWidth, ret); len = 0; } queue.offer(word); } } String last_row = ""; while (!queue.isEmpty()) { String word = queue.poll(); if (!last_row.equals("")) last_row += " "; last_row += word; } while (last_row.length() < maxWidth) { last_row += " "; } ret.add(last_row); return ret; } private void adjustRow(Queue<String> queue, int maxWidth, List<String> ret) { if (queue.isEmpty()) return; int len = 0; int word_count = queue.size(); for (String word : queue) { len += word.length(); } String row_str = queue.poll(); if (word_count == 1) { ret.add(row_str); return; } int avg_space = (maxWidth - len) / (word_count - 1); int extra_space = (maxWidth - len) % (word_count - 1); while (!queue.isEmpty()) { String word = queue.poll(); int count = 0; while (avg_space > count) { row_str += " "; count++; } if (extra_space != 0) { row_str += " "; extra_space--; } row_str += word; } ret.add(row_str); } } Input: ["Listen","to","many,","speak","to","a","few."] 6 Output: ["Listen","tomany,","speaktoa","few. "] Expected: ["Listen","to ","many, ","speak ","to a","few. "] Mistake analysis: Even we have managed to to change to code into a more elegant way. I still have made a big misktake in to above code. Which is quite common when use "after-ward" identification of a window. for (String word : words) { if (queue.isEmpty()) { queue.offer(word); len += word.length(); } else{ if (len + 1 + word.length() <= maxWidth) { len++; len += word.length(); } else{ adjustRow(queue, maxWidth, ret); len = 0; } queue.offer(word); } } Note: "after-ward detection" is very very dangerous!!! Especially you even matain a window for it, which means the stop condition is not the last element. You should pay close attention for following things: 1. We only know the end of a window at the next element after the window. You should do a good design over : "When is the window valid? " For this problem: the words in the window does not exceed maxWidth, including the concern of "at least one space between two word". if (queue.isEmpty()) { //the first word does not need a space before it. queue.offer(word); len += word.length(); } else{ if (len + 1 + word.length() <= maxWidth) { //test if the window was exceeded? len++; queue.offer(word); len += word.length(); } else{ //if exceeded, we apparently need to open a new window adjustRow(queue, maxWidth, ret); queue.offer(word); len = word.length(); } } 2. Most dangerous part: Iff a window was exceeded, we should prepare new window!!!! But don't forget to include current word into the new window, and all its information!!! Buggy codes: if (len + 1 + word.length() <= maxWidth) { len++; len += word.length(); } else{ adjustRow(queue, maxWidth, ret); len = 0; } queue.offer(word); The above code has following problems: mistake 1: forget to include the violated word's length into next window. mistake 2: try to extract common code "queue.offer(word)" out!!! Which is very dangerous for initial implementation. 3. Don't forget the last window for the last row. String last_row = ""; while (!queue.isEmpty()) { String word = queue.poll(); if (!last_row.equals("")) last_row += " "; last_row += word; } while (last_row.length() < maxWidth) { //don't forget to add space after the last word(when there is only one word) last_row += " "; } ret.add(last_row);
Solution:
public class Solution { public List<String> fullJustify(String[] words, int maxWidth) { List<String> ret = new ArrayList<String> (); if (words == null || words.length == 0) return ret; Queue<String> queue = new LinkedList<String> (); int len = 0; for (String word : words) { if (queue.isEmpty()) { queue.offer(word); len += word.length(); } else{ if (len + 1 + word.length() <= maxWidth) { len++; queue.offer(word); len += word.length(); } else{ adjustRow(queue, maxWidth, ret); queue.offer(word); len = word.length(); } } } String last_row = ""; while (!queue.isEmpty()) { String word = queue.poll(); if (!last_row.equals("")) last_row += " "; last_row += word; } while (last_row.length() < maxWidth) { last_row += " "; } ret.add(last_row); return ret; } private void adjustRow(Queue<String> queue, int maxWidth, List<String> ret) { if (queue.isEmpty()) return; int len = 0; int word_count = queue.size(); for (String word : queue) { len += word.length(); } String row_str = queue.poll(); int avg_space = 0; int extra_space = 0; if (word_count > 1) { avg_space = (maxWidth - len) / (word_count - 1); extra_space = (maxWidth - len) % (word_count - 1); } while (!queue.isEmpty()) { String word = queue.poll(); int count = 0; while (avg_space > count) { row_str += " "; count++; } if (extra_space != 0) { row_str += " "; extra_space--; } row_str += word; } while (row_str.length() < maxWidth) row_str += " "; ret.add(row_str); } }