68. Text Justification
Problem Description
This problem asks you to format text like a word processor would, creating fully justified text (aligned on both left and right margins) with a specific line width.
Given an array of words and a maximum width maxWidth
, you need to format the text so that:
-
Each line contains exactly
maxWidth
characters - no more, no less. -
Pack words greedily - fit as many words as possible on each line. Words must be separated by at least one space.
-
Distribute extra spaces evenly - When a line has fewer characters than
maxWidth
, add extra spaces between words to reach the exact width. If the extra spaces don't divide evenly among the gaps between words, the leftmost gaps get one more space than the rightmost gaps. -
Special case for the last line - The last line should be left-aligned (all words at the beginning with single spaces between them, and all extra spaces at the end).
-
Special case for single-word lines - If a line contains only one word, it should be left-aligned with all extra spaces at the end.
Example walkthrough:
If words = ["This", "is", "an", "example"]
and maxWidth = 16
:
- First line: "This" + "is" + "an" can fit (4 + 2 + 2 = 8 characters plus 2 spaces = 10 total)
- We need 16 - 10 = 6 more spaces to distribute between 2 gaps
- Each gap gets 3 spaces:
"This is an"
- Second line: "example" fits alone, so left-align:
"example "
The algorithm builds lines by:
- Greedily adding words until no more can fit
- Calculating how many extra spaces are needed
- Distributing those spaces evenly (with leftmost gaps getting extras when uneven)
- Handling special cases for last line and single-word lines
Intuition
The key insight is to break this problem into two main phases: line formation and space distribution.
First, we need to figure out which words belong on each line. Since we want to pack as many words as possible (greedy approach), we keep adding words to the current line until adding one more would exceed maxWidth
. We need to account for the minimum required spaces between words - if we have k
words on a line, we need at least k-1
spaces between them.
Once we know which words go on a line, we face the challenge of distributing spaces. If a line has total character count cnt
(including minimum spaces) and we need to reach maxWidth
, then we have maxWidth - cnt
extra spaces to distribute among the gaps between words.
The clever part is using division with remainder to distribute these spaces evenly. If we have gaps
number of gaps between words and extra_spaces
to distribute, then:
- Each gap gets at least
extra_spaces // gaps
spaces - The first
extra_spaces % gaps
gaps get one additional space
For example, if we need to distribute 7 extra spaces among 3 gaps:
7 // 3 = 2
spaces per gap minimum7 % 3 = 1
remaining space- So the gaps get: 3, 2, 2 spaces respectively
The special cases (last line and single-word lines) are simpler - we just left-align by joining words with single spaces and padding the right side with the remaining spaces.
This approach naturally handles all requirements: we maximize words per line through greedy packing, we achieve exact width through calculated space distribution, and we handle edge cases with conditional logic. The simulation approach works because we're essentially mimicking what a text editor would do when justifying text.
Solution Approach
The implementation follows a simulation approach where we process words sequentially and build justified lines one by one.
Main Loop Structure:
We use an index i
to traverse through the words
array. For each iteration, we build one complete line of text.
Line Formation Phase:
- Start with an empty list
t
to hold words for the current line - Initialize
cnt
with the length of the first word - Keep adding words while checking:
cnt + 1 + len(words[i]) <= maxWidth
- The
+1
accounts for the minimum space needed before the next word
- The
- Update
cnt
by adding 1 (for space) plus the length of the new word
Space Distribution Phase: Once we have all words for a line, we handle three cases:
Case 1: Last line or single-word line (i == n or len(t) == 1
):
- Simply join words with single spaces:
' '.join(t)
- Pad the right with spaces:
' ' * (maxWidth - len(left))
- Append to result
Case 2: Regular line with multiple words:
-
Calculate total extra spaces needed:
space_width = maxWidth - (cnt - len(t) + 1)
cnt
includes minimum spaces between words- We subtract
len(t) - 1
to get just the character count - The difference from
maxWidth
gives us extra spaces to distribute
-
Divide extra spaces among gaps:
w, m = divmod(space_width, len(t) - 1)
w
: base number of spaces each gap getsm
: number of leftmost gaps that get one extra space
-
Build the justified line:
for j, s in enumerate(t[:-1]): row.append(s) row.append(' ' * (w + (1 if j < m else 0))) row.append(t[-1])
- Add each word (except the last)
- Add
w
spaces, plus 1 extra if this is one of the firstm
gaps - Finally add the last word
Data Structures:
ans
: List to store the final justified linest
: Temporary list to collect words for the current linerow
: List to build the justified string with proper spacing
The algorithm runs in O(n)
time where n
is the total number of words, as each word is processed exactly once. The space complexity is O(m)
where m
is the total length of the output.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with words = ["The", "quick", "brown"]
and maxWidth = 10
.
Building Line 1:
- Start with
i = 0
, empty linet = []
- Add "The" (3 chars):
t = ["The"]
,cnt = 3
- Check next word "quick":
3 + 1 + 5 = 9 ≤ 10
✓ - Add "quick":
t = ["The", "quick"]
,cnt = 9
- Check next word "brown":
9 + 1 + 5 = 15 > 10
✗ - Stop here, line 1 contains: ["The", "quick"]
Justifying Line 1:
- We have 2 words with total
cnt = 9
(including 1 minimum space) - Character count only:
3 + 5 = 8
- Extra spaces needed:
10 - 8 = 2
- Number of gaps:
2 - 1 = 1
- Distribute:
2 // 1 = 2
spaces per gap,2 % 1 = 0
remainder - Result:
"The" + " " + "quick" = "The quick"
Building Line 2:
- Start with
i = 2
, empty linet = []
- Add "brown" (5 chars):
t = ["brown"]
,cnt = 5
- No more words,
i = 3 = n
Justifying Line 2 (Last Line):
- This is the last line, so left-align
- Join with single spaces:
"brown"
- Pad right:
10 - 5 = 5
spaces - Result:
"brown "
Final Output:
["The quick", "brown "]
Each line has exactly 10 characters. The first line distributes extra spaces evenly between words, while the last line is left-aligned with trailing spaces.
Solution Implementation
1class Solution:
2 def fullJustify(self, words: List[str], maxWidth: int) -> List[str]:
3 """
4 Formats text with full justification where each line has exactly maxWidth characters.
5
6 Args:
7 words: List of words to be justified
8 maxWidth: Maximum width of each line
9
10 Returns:
11 List of fully justified text lines
12 """
13 result = []
14 word_index = 0
15 total_words = len(words)
16
17 # Process words until all are placed in lines
18 while word_index < total_words:
19 # Collect words that fit in the current line
20 current_line_words = []
21 current_line_length = len(words[word_index])
22 current_line_words.append(words[word_index])
23 word_index += 1
24
25 # Keep adding words while they fit (accounting for minimum one space between words)
26 while word_index < total_words and current_line_length + 1 + len(words[word_index]) <= maxWidth:
27 current_line_length += 1 + len(words[word_index])
28 current_line_words.append(words[word_index])
29 word_index += 1
30
31 # Handle last line or single-word line (left-justified)
32 if word_index == total_words or len(current_line_words) == 1:
33 # Join words with single spaces and pad right with spaces
34 left_justified = ' '.join(current_line_words)
35 right_padding = ' ' * (maxWidth - len(left_justified))
36 result.append(left_justified + right_padding)
37 continue
38
39 # Calculate space distribution for full justification
40 # Total spaces needed = maxWidth - sum of word lengths
41 total_spaces = maxWidth - (current_line_length - len(current_line_words) + 1)
42
43 # Distribute spaces evenly between words
44 gaps_between_words = len(current_line_words) - 1
45 base_spaces_per_gap, extra_spaces = divmod(total_spaces, gaps_between_words)
46
47 # Build the justified line
48 justified_line = []
49 for word_position, word in enumerate(current_line_words[:-1]):
50 justified_line.append(word)
51 # Add base spaces plus one extra space for the first 'extra_spaces' gaps
52 spaces_to_add = base_spaces_per_gap + (1 if word_position < extra_spaces else 0)
53 justified_line.append(' ' * spaces_to_add)
54
55 # Add the last word (no spaces after it)
56 justified_line.append(current_line_words[-1])
57 result.append(''.join(justified_line))
58
59 return result
60
1class Solution {
2 public List<String> fullJustify(String[] words, int maxWidth) {
3 List<String> result = new ArrayList<>();
4 int wordIndex = 0;
5 int totalWords = words.length;
6
7 // Process words until all are consumed
8 while (wordIndex < totalWords) {
9 // Collect words that can fit in the current line
10 List<String> currentLineWords = new ArrayList<>();
11 currentLineWords.add(words[wordIndex]);
12 int currentLineLength = words[wordIndex].length();
13 wordIndex++;
14
15 // Add more words to current line while they fit (including minimum single space between words)
16 while (wordIndex < totalWords &&
17 currentLineLength + 1 + words[wordIndex].length() <= maxWidth) {
18 currentLineLength += 1 + words[wordIndex].length();
19 currentLineWords.add(words[wordIndex]);
20 wordIndex++;
21 }
22
23 // Handle last line or single word line (left-justified)
24 if (wordIndex == totalWords || currentLineWords.size() == 1) {
25 String leftJustifiedText = String.join(" ", currentLineWords);
26 String rightPadding = " ".repeat(maxWidth - leftJustifiedText.length());
27 result.add(leftJustifiedText + rightPadding);
28 continue;
29 }
30
31 // Calculate spaces for full justification
32 // Total spaces needed = maxWidth - sum of word lengths
33 int totalSpacesNeeded = maxWidth - (currentLineLength - currentLineWords.size() + 1);
34 // Base spaces between each pair of words
35 int baseSpacesBetweenWords = totalSpacesNeeded / (currentLineWords.size() - 1);
36 // Extra spaces to distribute from left to right
37 int extraSpaces = totalSpacesNeeded % (currentLineWords.size() - 1);
38
39 // Build the fully justified line
40 StringBuilder currentLine = new StringBuilder();
41 for (int i = 0; i < currentLineWords.size() - 1; i++) {
42 currentLine.append(currentLineWords.get(i));
43 // Add base spaces plus one extra space for the first 'extraSpaces' gaps
44 int spacesToAdd = baseSpacesBetweenWords + (i < extraSpaces ? 1 : 0);
45 currentLine.append(" ".repeat(spacesToAdd));
46 }
47 // Append the last word without trailing spaces
48 currentLine.append(currentLineWords.get(currentLineWords.size() - 1));
49
50 result.add(currentLine.toString());
51 }
52
53 return result;
54 }
55}
56
1class Solution {
2public:
3 vector<string> fullJustify(vector<string>& words, int maxWidth) {
4 vector<string> result;
5 int wordCount = words.size();
6
7 for (int i = 0; i < wordCount;) {
8 // Collect words that can fit in the current line
9 vector<string> currentLineWords = {words[i]};
10 int currentLineLength = words[i].size();
11 i++;
12
13 // Keep adding words while they fit within maxWidth (including minimum spaces)
14 while (i < wordCount && currentLineLength + 1 + words[i].size() <= maxWidth) {
15 currentLineLength += 1 + words[i].size();
16 currentLineWords.push_back(words[i]);
17 i++;
18 }
19
20 // Handle last line or single word line (left-justified)
21 if (i == wordCount || currentLineWords.size() == 1) {
22 string leftJustifiedLine = currentLineWords[0];
23 for (int j = 1; j < currentLineWords.size(); j++) {
24 leftJustifiedLine += " " + currentLineWords[j];
25 }
26 // Pad with spaces on the right to reach maxWidth
27 string rightPadding(maxWidth - leftJustifiedLine.size(), ' ');
28 result.push_back(leftJustifiedLine + rightPadding);
29 continue;
30 }
31
32 // Calculate space distribution for fully justified line
33 // Total characters used by words (excluding spaces between them)
34 int totalWordChars = currentLineLength - (currentLineWords.size() - 1);
35 // Total spaces to distribute
36 int totalSpaces = maxWidth - totalWordChars;
37 // Base spaces between each word pair
38 int baseSpaces = totalSpaces / (currentLineWords.size() - 1);
39 // Extra spaces to distribute from left to right
40 int extraSpaces = totalSpaces % (currentLineWords.size() - 1);
41
42 // Build the fully justified line
43 string justifiedLine;
44 for (int j = 0; j < currentLineWords.size() - 1; j++) {
45 justifiedLine += currentLineWords[j];
46 // Add base spaces plus one extra if within the first 'extraSpaces' gaps
47 int spacesToAdd = baseSpaces + (j < extraSpaces ? 1 : 0);
48 justifiedLine += string(spacesToAdd, ' ');
49 }
50 // Add the last word without trailing spaces
51 justifiedLine += currentLineWords.back();
52
53 result.push_back(justifiedLine);
54 }
55
56 return result;
57 }
58};
59
1/**
2 * Text justification algorithm that formats words into lines with full justification
3 * @param words - Array of words to be justified
4 * @param maxWidth - Maximum width of each line
5 * @returns Array of fully justified text lines
6 */
7function fullJustify(words: string[], maxWidth: number): string[] {
8 const result: string[] = [];
9 const totalWords = words.length;
10 let wordIndex = 0;
11
12 // Process words until all are placed in lines
13 while (wordIndex < totalWords) {
14 // Build current line by adding words that fit within maxWidth
15 const currentLineWords: string[] = [words[wordIndex]];
16 let currentLineLength = words[wordIndex].length;
17 wordIndex++;
18
19 // Keep adding words while they fit (including minimum one space between words)
20 while (wordIndex < totalWords &&
21 currentLineLength + 1 + words[wordIndex].length <= maxWidth) {
22 currentLineWords.push(words[wordIndex]);
23 currentLineLength += 1 + words[wordIndex].length;
24 wordIndex++;
25 }
26
27 // Handle last line or single word line - left justified
28 if (wordIndex === totalWords || currentLineWords.length === 1) {
29 const leftJustifiedText = currentLineWords.join(' ');
30 const rightPadding = ' '.repeat(maxWidth - leftJustifiedText.length);
31 result.push(leftJustifiedText + rightPadding);
32 continue;
33 }
34
35 // Calculate space distribution for full justification
36 // Total spaces needed minus the minimum single spaces between words
37 const totalSpacesToDistribute = maxWidth - (currentLineLength - currentLineWords.length + 1);
38 const gapsBetweenWords = currentLineWords.length - 1;
39 const baseSpacesPerGap = Math.floor(totalSpacesToDistribute / gapsBetweenWords);
40 const extraSpaces = totalSpacesToDistribute % gapsBetweenWords;
41
42 // Build the fully justified line
43 const justifiedLine: string[] = [];
44 for (let i = 0; i < currentLineWords.length - 1; i++) {
45 justifiedLine.push(currentLineWords[i]);
46 // Add base spaces plus one extra space for the first 'extraSpaces' gaps
47 const spacesToAdd = baseSpacesPerGap + (i < extraSpaces ? 1 : 0);
48 justifiedLine.push(' '.repeat(spacesToAdd));
49 }
50 // Add the last word without trailing spaces
51 justifiedLine.push(currentLineWords[currentLineWords.length - 1]);
52
53 result.push(justifiedLine.join(''));
54 }
55
56 return result;
57}
58
Time and Space Complexity
Time Complexity: O(L)
where L
is the sum of the lengths of all words.
The algorithm iterates through each word exactly once using the outer while loop with index i
. For each word, we:
- Add it to the current line (constant time operations)
- Calculate the length (accessing already known lengths)
- When a line is complete, we format it by iterating through the words in that line once
Even though there are nested loops (the inner while loop and the formatting loop), each word is processed a constant number of times:
- Once when added to a line
- Once when the line is formatted
Therefore, the total time is proportional to the number of words and their lengths, giving us O(L)
.
Space Complexity: O(L)
where L
is the sum of the lengths of all words.
The space is used for:
- The output list
ans
which stores all formatted lines. In the worst case, this contains all the input words plus the necessary spaces, which is bounded byO(L)
- The temporary list
t
which holds words for the current line being processed. This is at mostO(maxWidth)
which is typically less thanO(L)
- The
row
list used for formatting, which is also bounded by the current line's content
Since the output must contain all the input words, the space complexity is O(L)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Space Calculation Formula
One of the most common mistakes is incorrectly calculating the number of extra spaces to distribute. Many developers confuse the total character count with the space calculation.
Wrong approach:
# Incorrect: Using current_line_length directly total_spaces = maxWidth - current_line_length
Why it's wrong: The current_line_length
already includes minimum spaces between words. When we want to calculate extra spaces to distribute, we need to first remove those minimum spaces from consideration.
Correct approach:
# First, get the total character count of words only
word_chars = sum(len(word) for word in current_line_words)
# Or equivalently: current_line_length - (len(current_line_words) - 1)
# Then calculate total spaces needed
total_spaces = maxWidth - word_chars
# Finally, distribute among gaps
gaps = len(current_line_words) - 1
spaces_per_gap, extra_spaces = divmod(total_spaces, gaps)
2. Off-by-One Error in Extra Space Distribution
Another frequent mistake is incorrectly determining which gaps should receive extra spaces when they don't divide evenly.
Wrong approach:
# Incorrect: Using <= instead of < spaces_to_add = base_spaces + (1 if word_position <= extra_spaces else 0)
Why it's wrong: If we have 3 extra spaces and 4 gaps (positions 0, 1, 2, 3), using <=
would distribute extra spaces to positions 0, 1, 2, AND 3 (4 spaces total), which is one too many.
Correct approach:
# Correct: First 'extra_spaces' gaps get one additional space spaces_to_add = base_spaces + (1 if word_position < extra_spaces else 0)
3. Forgetting to Handle Edge Cases Properly
The problem has two critical edge cases that are easy to miss or handle incorrectly:
Missing edge case handling:
# Wrong: Only checking for last line if word_index == total_words: # Left justify
Why it's wrong: Single-word lines that aren't the last line also need to be left-justified, not center or right-justified.
Correct approach:
# Check for BOTH last line AND single-word lines
if word_index == total_words or len(current_line_words) == 1:
left_justified = ' '.join(current_line_words)
result.append(left_justified + ' ' * (maxWidth - len(left_justified)))
4. Division by Zero When Line Has Only One Word
If not handled properly, attempting to distribute spaces when there's only one word can cause a division by zero error.
Wrong approach:
# Dangerous: No check for single word case
gaps = len(current_line_words) - 1 # Could be 0!
spaces_per_gap = total_spaces // gaps # Division by zero!
Correct approach:
# Handle single word case before space distribution
if len(current_line_words) == 1:
# Left justify with padding
result.append(current_line_words[0] + ' ' * (maxWidth - len(current_line_words[0])))
else:
# Safe to divide by gaps now
gaps = len(current_line_words) - 1
spaces_per_gap, extra_spaces = divmod(total_spaces, gaps)
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!