Welcome to Subscribe On Youtube

68. Text Justification

Description

Given an array of strings words and a width maxWidth, format the text such that each line has exactly maxWidth 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 maxWidth characters.

Extra spaces between words should be distributed as evenly as possible. If the number of spaces on a line does 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.

Note:

  • A word is defined as a character sequence consisting of non-space characters only.
  • Each word's length is guaranteed to be greater than 0 and not exceed maxWidth.
  • The input array words contains at least one word.

 

Example 1:

Input: words = ["This", "is", "an", "example", "of", "text", "justification."], maxWidth = 16
Output:
[
   "This    is    an",
   "example  of text",
   "justification.  "
]

Example 2:

Input: words = ["What","must","be","acknowledgment","shall","be"], maxWidth = 16
Output:
[
  "What   must   be",
  "acknowledgment  ",
  "shall be        "
]
Explanation: Note that the last line is "shall be    " instead of "shall     be", because the last line must be left-justified instead of fully-justified.
Note that the second line is also left-justified because it contains only one word.

Example 3:

Input: words = ["Science","is","what","we","understand","well","enough","to","explain","to","a","computer.","Art","is","everything","else","we","do"], maxWidth = 20
Output:
[
  "Science  is  what we",
  "understand      well",
  "enough to explain to",
  "a  computer.  Art is",
  "everything  else  we",
  "do                  "
]

 

Constraints:

  • 1 <= words.length <= 300
  • 1 <= words[i].length <= 20
  • words[i] consists of only English letters and symbols.
  • 1 <= maxWidth <= 100
  • words[i].length <= maxWidth

Solutions

Solution 1: Simulation

We can simulate the process according to the problem’s requirements. Note that if it is the last line, or if there is only one word in the line, then we should align to the left. Otherwise, we should distribute the spaces evenly.

The time complexity is $O(L)$, and the space complexity is $O(L)$. Here, $L$ is the sum of the lengths of all words.

  • class Solution {
        public List<String> fullJustify(String[] words, int maxWidth) {
            List<String> ans = new ArrayList<>();
            for (int i = 0, n = words.length; i < n;) {
                List<String> t = new ArrayList<>();
                t.add(words[i]);
                int cnt = words[i].length();
                ++i;
                while (i < n && cnt + 1 + words[i].length() <= maxWidth) {
                    cnt += 1 + words[i].length();
                    t.add(words[i++]);
                }
                if (i == n || t.size() == 1) {
                    String left = String.join(" ", t);
                    String right = " ".repeat(maxWidth - left.length());
                    ans.add(left + right);
                    continue;
                }
                int spaceWidth = maxWidth - (cnt - t.size() + 1);
                int w = spaceWidth / (t.size() - 1);
                int m = spaceWidth % (t.size() - 1);
                StringBuilder row = new StringBuilder();
                for (int j = 0; j < t.size() - 1; ++j) {
                    row.append(t.get(j));
                    row.append(" ".repeat(w + (j < m ? 1 : 0)));
                }
                row.append(t.get(t.size() - 1));
                ans.add(row.toString());
            }
            return ans;
        }
    }
    
  • class Solution {
    public:
        vector<string> fullJustify(vector<string>& words, int maxWidth) {
            vector<string> ans;
            for (int i = 0, n = words.size(); i < n;) {
                vector<string> t = {words[i]};
                int cnt = words[i].size();
                ++i;
                while (i < n && cnt + 1 + words[i].size() <= maxWidth) {
                    cnt += 1 + words[i].size();
                    t.emplace_back(words[i++]);
                }
                if (i == n || t.size() == 1) {
                    string left = t[0];
                    for (int j = 1; j < t.size(); ++j) {
                        left += " " + t[j];
                    }
                    string right = string(maxWidth - left.size(), ' ');
                    ans.emplace_back(left + right);
                    continue;
                }
                int spaceWidth = maxWidth - (cnt - t.size() + 1);
                int w = spaceWidth / (t.size() - 1);
                int m = spaceWidth % (t.size() - 1);
                string row;
                for (int j = 0; j < t.size() - 1; ++j) {
                    row += t[j] + string(w + (j < m ? 1 : 0), ' ');
                }
                row += t.back();
                ans.emplace_back(row);
            }
            return ans;
        }
    };
    
  • '''
    Explanation:
    
    - The function `fullJustify` takes a list of words and a maximum width `maxWidth` as inputs.
    - It iterates over each word, deciding whether to add the current word to the current line (`cur`) or to start a new line.
    - If adding the current word to the line would exceed `maxWidth`, it justifies the current line by adding extra spaces between words as needed, then starts a new line.
    - Once all words are processed, it left-justifies the last line by joining the remaining words in `cur` with a single space and then using `.ljust(maxWidth)` to ensure the line is of maximum width.
    - The result is a list of strings, where each string represents a justified line of text.
    
    This solution carefully handles edge cases, such as when there's only one word in the line (avoiding division by zero) and ensuring the last line is left-justified instead of fully justified.
    '''
    
    '''
    >>> cur = [1]
    >>> len(cur) - 1
    0
    >>> ( len(cur) - 1 or 1 )
    1
    '''
    
    '''
    # api: string.ljust(width[, fillchar])
    
    >>> "Hello".ljust(10)
    'Hello     '
    >>> "Hello".ljust(10, '-')
    'Hello-----'
    >>> "Hello".ljust(2)
    'Hello'
    
    >>> "Hello World".ljust(20)
    'Hello World         '
    >>> "Hello World".ljust(20, '-')
    'Hello World---------'
    
    
    # api: string.rjust(width[, fillchar])
    >>> "Hello".rjust(10)
    '     Hello'
    >>> "Hello".rjust(10, '-')
    '-----Hello'
    >>> "Hello".rjust(2)
    'Hello'
    
    >>> "Hello World".rjust(20)
    '         Hello World'
    >>> "Hello World".rjust(20, '-')
    '---------Hello World'
    
    '''
    
    class Solution:
        def fullJustify(self, words: List[str], maxWidth: int) -> List[str]:
            res, cur, num_of_letters = [], [], 0
    
            for w in words:
                # len(cur) is the space count, from last round, right one less after adding 'w'
                if num_of_letters + len(cur) + len(w) > maxWidth:
                    for i in range(maxWidth - num_of_letters):
                        # The "or 1" part is for dealing with the edge case 'len(cur) == 1'
                        cur[i % ( len(cur) - 1 or 1 )] += ' '
                    res.append(''.join(cur))
                    cur, num_of_letters = [], 0
                cur += [w]
                num_of_letters += len(w)
    
            return res + [' '.join(cur).ljust(maxWidth)]
    
    
    ############
    
    '''
    >>> 17 % 3
    2
    >>> divmod(17,3)
    (5, 2)
    '''
    
    '''
    >>> ['This', 'is', 'an']
    ['This', 'is', 'an']
    >>> t = ['This', 'is', 'an']
    >>> spaces = ['    ', '    ']
    
    # note: here j starts from 0
    >>> for j, word in enumerate(t[1:]):
    ...     print(j)
    ...     print(word)
    ...
    0
    is
    1
    an
    '''
    
    class Solution:
        def fullJustify(self, words: List[str], maxWidth: int) -> List[str]:
            def partition(n, cnt):
                base, mod = divmod(n, cnt)
                res = [f"{' ' * (base + (i < mod))}" for i in range(cnt)]
                return res
    
            ans = []
            i, n = 0, len(words)
            while i < n: # one line per iteration
                t = [words[i]]
                cnt = len(words[i])
                i += 1 # move to next word
                while i < n and cnt + 1 + len(words[i]) <= maxWidth: # greedy search for one line
                    cnt += 1 + len(words[i])
                    t.append(words[i])
                    i += 1
                if i == n or len(t) == 1:
                    # this is the last line or only one (super-long) word in a line
                    left = ' '.join(t)
                    right = ' ' * (maxWidth - len(left))
                    ans.append(f"{left}{right}")
                    continue # so i != n , so only one word, no need to add spaces. 
                             # e.g. a word with the same length as maxWidth, 
                             # or, e.g. the 2nd word is super long and cannot fit in current line
                    # if i==n, then will not enter next while
                words_width = cnt - (len(t) - 1) # pure words total length, with no spaces
                                                 # spaces in-between these words in t[]: len(t) - 1
                space_width = maxWidth - words_width
                spaces = partition(space_width, len(t) - 1)
                sb = [t[0]] # 1st word of this line, no space before it
                for j, word in enumerate(t[1:]): # last word has no following space
                    sb.append(spaces[j]) # j starts from 0
                    sb.append(word)
                ans.append(''.join(sb))
            return ans
    
    ###########
    
    class Solution:
        def fullJustify(self, words: List[str], maxWidth: int) -> List[str]:
            left = 0
            result = []
            
            while left < len(words):
                right = self.findRight(left, words, maxWidth)
                result.append(self.justify(left, right, words, maxWidth))
                left = right + 1
            
            return result
        
        def findRight(self, left, words, maxWidth):
            right = left
            word_length = len(words[right])
            
            while (right + 1) < len(words) and (word_length + 1 + len(words[right+1])) <= maxWidth:
                right += 1
                word_length += 1 + len(words[right])
            
            return right
        
        def justify(self, left, right, words, maxWidth):
            if right - left == 0:
                return self.padResult(words[left], maxWidth)
            
            is_last_line = (right == len(words) - 1)
            num_spaces = right - left
            total_space = maxWidth - self.wordsLength(left, right, words)
            
            space = " " if is_last_line else " " * (total_space // num_spaces)
            remainder = 0 if is_last_line else total_space % num_spaces
            
            result = ""
            for i in range(left, right):
                result += words[i]
                result += space
                if remainder > 0:
                    result += " "
                    remainder -= 1
            
            result += words[right]
            result += " " * (maxWidth - len(result))
            
            return result
        
        def wordsLength(self, left, right, words):
            words_length = 0
            for i in range(left, right+1):
                words_length += len(words[i])
            return words_length
        
        def padResult(self, result, maxWidth):
            return result + " " * (maxWidth - len(result))
    
    
    
  • func fullJustify(words []string, maxWidth int) (ans []string) {
    	for i, n := 0, len(words); i < n; {
    		t := []string{words[i]}
    		cnt := len(words[i])
    		i++
    		for i < n && cnt+1+len(words[i]) <= maxWidth {
    			cnt += 1 + len(words[i])
    			t = append(t, words[i])
    			i++
    		}
    		if i == n || len(t) == 1 {
    			left := strings.Join(t, " ")
    			right := strings.Repeat(" ", maxWidth-len(left))
    			ans = append(ans, left+right)
    			continue
    		}
    		spaceWidth := maxWidth - (cnt - len(t) + 1)
    		w := spaceWidth / (len(t) - 1)
    		m := spaceWidth % (len(t) - 1)
    		row := strings.Builder{}
    		for j, s := range t[:len(t)-1] {
    			row.WriteString(s)
    			row.WriteString(strings.Repeat(" ", w))
    			if j < m {
    				row.WriteString(" ")
    			}
    		}
    		row.WriteString(t[len(t)-1])
    		ans = append(ans, row.String())
    	}
    	return
    }
    
  • function fullJustify(words: string[], maxWidth: number): string[] {
        const ans: string[] = [];
        for (let i = 0, n = words.length; i < n; ) {
            const t: string[] = [words[i]];
            let cnt = words[i++].length;
            while (i < n && cnt + 1 + words[i].length <= maxWidth) {
                t.push(words[i]);
                cnt += 1 + words[i++].length;
            }
            if (i === n || t.length === 1) {
                const left: string = t.join(' ');
                const right: string = ' '.repeat(maxWidth - left.length);
                ans.push(left + right);
                continue;
            }
            const spaceWidth: number = maxWidth - (cnt - t.length + 1);
            const w: number = Math.floor(spaceWidth / (t.length - 1));
            const m: number = spaceWidth % (t.length - 1);
            const row: string[] = [];
            for (let j = 0; j < t.length - 1; ++j) {
                row.push(t[j]);
                row.push(' '.repeat(w + (j < m ? 1 : 0)));
            }
            row.push(t[t.length - 1]);
            ans.push(row.join(''));
        }
        return ans;
    }
    
    
  • public class Solution {
        public IList<string> FullJustify(string[] words, int maxWidth) {
            var ans = new List<string>();
            for (int i = 0, n = words.Length; i < n;) {
                var t = new List<string>();
                t.Add(words[i]);
                int cnt = words[i].Length;
                ++i;
                while (i < n && cnt + 1 + words[i].Length <= maxWidth) {
                    t.Add(words[i]);
                    cnt += 1 + words[i].Length;
                    ++i;
                }
                if (i == n || t.Count == 1) {
                    string left = string.Join(" ", t);
                    string right = new string(' ', maxWidth - left.Length);
                    ans.Add(left + right);
                    continue;
                }
                int spaceWidth = maxWidth - (cnt - t.Count + 1);
                int w = spaceWidth / (t.Count - 1);
                int m = spaceWidth % (t.Count - 1);
                var row = new StringBuilder();
                for (int j = 0; j < t.Count - 1; ++j) {
                    row.Append(t[j]);
                    row.Append(new string(' ', w + (j < m ? 1 : 0)));
                }
                row.Append(t[t.Count - 1]);
                ans.Add(row.ToString());
            }
            return ans;
        }
    }
    

All Problems

All Solutions