Text Justification solution and explanation
9 more videos on this problem
Text Justification - Leetcode 68 - PythonNeetCodeIO · 17:52 · 55K views
Text Justification Algorithm (LeetCode)AlgosWithMichael · 30:45 · 34K views
Text Justification | Broken into Pieces | GOOGLE | Leetcode-68 | Explanation + Live CodingcodestorywithMIK · 35:17 · 23K views
TEXT JUSTIFICATION - LEETCODE # 68 | GOOGLE INTERVIEW QUESTION | PYTHONCracking FAANG · 22:41 · 11K views
LeetCode 68. Text Justification Explanation and Solutionhappygirlzt · 12:43 · 9K views
Text Justification | LeetCode 68 (Hard)Destination FAANG · 13:06 · 8K views
LeetCode 68: Text JustificationBeautiful TypeScript · 1:01:14 · 6K views
4 Questions That Landed Me An Offer At ByteDance Part 3 | Leetcode 68leetuition · 18:22 · 5K views
Leetcode 68 Text Justification (Java)TheAnalyticGuy · 11:07 · 3K views
LeetCode 68 Text Justification | JSer - JavaScript & AlgorithmJSer · 20:48 · 1K views
The problem
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
0and not exceedmaxWidth. - The input array
wordscontains 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 <= 3001 <= words[i].length <= 20words[i]consists of only English letters and symbols.1 <= maxWidth <= 100words[i].length <= maxWidth
Approach overview
Problem Overview: Given an array of words and a maximum line width, format the text so every line has exactly maxWidth characters. Words must stay in order, and extra spaces are distributed between words so the line becomes fully justified. The last line is left‑justified.
Approach 1: Greedy Line Construction (O(n) time, O(1) extra space)
The most practical solution processes words line by line using a greedy strategy. Iterate through the input and keep adding words to the current line while the total length plus required spaces does not exceed maxWidth. Once the next word would overflow the line, finalize the current line by distributing spaces between its words. Compute the total remaining spaces and spread them evenly across the gaps; the first gaps receive one extra space when the division is uneven. If the line has a single word, pad spaces to the right. For the final line, join words with a single space and pad the remaining characters at the end. This approach scans each word once and performs constant work per line, giving O(n) time where n is the number of words (or total characters processed). Only a few counters and temporary variables are used, so the extra space is O(1) excluding the output. The logic mainly involves sequential processing of arrays and strings, which fits well with problems under Array and String.
Approach 2: Dynamic Programming Line Breaking (O(n²) time, O(n²) space)
A more theoretical approach treats the task like classic text formatting. Use dynamic programming to evaluate every possible break point between words and choose the configuration that minimizes uneven spacing or raggedness. Precompute line lengths for word ranges, then define dp[i] as the minimum penalty for formatting words starting at index i. Try placing words i..j on the same line, calculate the cost for unused spaces, and combine it with dp[j+1]. This explores many candidate layouts and guarantees an optimal formatting score under the chosen penalty rule. However, evaluating all word pairs leads to O(n²) time and memory. It’s rarely required for this specific problem because the platform’s rules already define exact spacing behavior rather than minimizing raggedness.
Recommended for interviews: Interviewers expect the greedy line construction approach. It directly simulates the formatting process and demonstrates strong control over indexing, spacing math, and edge cases like single‑word lines or the final line. The DP approach shows deeper understanding of text layout algorithms but is usually unnecessary overhead for this problem. From a patterns perspective, the greedy method is a clean example of Simulation combined with string manipulation.
Approach 1: Greedy Line Construction
This approach involves iterating through the given words and greedily forming a line by adding as many words as possible without exceeding the maxWidth. When a line is completed, the algorithm distributes spaces evenly between words. If the number of spaces doesn’t divide evenly, more spaces are added to the left slots. The last line is treated differently by left-justifying the text.
def fullJustify(words, maxWidth):
def justify_line(line, width, maxWidth):
if len(line) == 1:
return line[0] + ' ' * (maxWidth - width)
total_spaces = maxWidth - width + len(line) - 1
space, extra = divmod(total_spaces, len(line) - 1)
for i in range(extra):
line[i] += ' '
return (' ' * space).join(line)
res, line, width = [], [], 0
for word in words:
if width + len(word) + len(line) > maxWidth:
res.append(justify_line(line, width, maxWidth))
line, width = [], 0
line.append(word)
width += len(word)
last_line = ' '.join(line)
res.append(last_line + ' ' * (maxWidth - len(last_line)))
return resimport java.util.*;
public class TextJustification {
public List<String> fullJustify(String[] words, int maxWidth) {
List<String> result = new ArrayList<>();
int index = 0;
while (index < words.length) {
int totalChars = words[index].length();
int last = index + 1;
while (last < words.length) {
if (totalChars + words[last].length() + 1 > maxWidth) break;
totalChars += words[last].length() + 1;
last++;
}
StringBuilder sb = new StringBuilder();
int diff = last - index - 1;
// if last line or number of words in the line is 1, left-justify
if (last == words.length || diff == 0) {
for (int i = index; i < last; i++) {
sb.append(words[i]).append(" ");
}
sb.deleteCharAt(sb.length() - 1);
while (sb.length() < maxWidth) {
sb.append(" ");
}
} else {
int spaces = (maxWidth - totalChars) / diff;
int remainder = (maxWidth - totalChars) % diff;
for (int i = index; i < last - 1; i++) {
sb.append(words[i]).append(" ");
for (int j = 0; j < spaces + (i - index < remainder ? 1 : 0); j++) {
sb.append(" ");
}
}
sb.append(words[last - 1]);
}
result.add(sb.toString());
index = last;
}
return result;
}
}function fullJustify(words, maxWidth) {
const result = [];
let idx = 0;
const justify = (line, maxWidth, isLastLine) => {
if (line.length === 1) return line[0].padEnd(maxWidth);
if (isLastLine) return line.join(' ').padEnd(maxWidth);
const totalSpace = maxWidth - line.join('').length;
const gaps = line.length - 1;
const spaceBetween = Math.floor(totalSpace / gaps);
const extraSpaces = totalSpace % gaps;
return line.reduce((acc, word, i) => {
if (i === gaps) return acc + word;
return acc + word + ' '.repeat(spaceBetween + (i < extraSpaces ? 1 : 0));
}, '');
};
while (idx < words.length) {
let line = [];
let lineLength = 0;
while (idx < words.length && lineLength + words[idx].length + line.length <= maxWidth) {
lineLength += words[idx].length;
line.push(words[idx++]);
}
result.push(justify(line, maxWidth, idx === words.length));
}
return result;
}The code defines a helper function, justify_line, to construct each justified line. While adding words to the current line, if adding another word exceeds maxWidth, the line is justified and appended to the result. The last line is treated separately for left-justification.
Time Complexity: O(n), where n is the total number of characters in all words.
Space Complexity: O(n), where n is the number of lines formed times maxWidth for the justified text.
Approach 2: Dynamic Programming Approach
This method leverages dynamic programming to minimize a hypothetical cost function that combines badness or uneven space distribution along with total number of lines used, aiming to determine the optimal setup of words per line.
#include <iostream>
#include <vector>
#include <string>
#include <climits>
using namespace std;
vector<string> fullJustify(vector<string>& words, int maxWidth) {
int n = words.size();
vector<int> dp(n + 1, INT_MAX);
vector<int> path(n);
dp[n] = 0;
for (int i = n - 1; i >= 0; --i) {
int len = -1;
for (int j = i; j < n; ++j) {
len += words[j].length() + 1;
if (len > maxWidth) break;
int last_cost = j == n - 1 ? 0 : maxWidth - len;
if (dp[i] > last_cost + dp[j + 1]) {
dp[i] = last_cost + dp[j + 1];
path[i] = j + 1;
}
}
}
vector<string> res;
for (int i = 0; i < n; i = path[i]) {
int j = path[i];
string line = words[i];
for (int k = i + 1; k < j; ++k) {
line += " " + words[k];
}
line += string(maxWidth - line.length(), ' ');
res.push_back(line);
}
return res;
}using System;
using System.Collections.Generic;
using System.Text;
public class Solution {
public IList<string> FullJustify(string[] words, int maxWidth) {
int n = words.Length;
int[] dp = new int[n + 1];
int[] path = new int[n];
Array.Fill(dp, int.MaxValue);
dp[n] = 0;
for (int i = n - 1; i >= 0; i--) {
int lineLength = -1;
for (int j = i; j < n && lineLength <= maxWidth; j++) {
lineLength += words[j].Length + 1;
if (lineLength - 1 <= maxWidth) {
int lastCost = j == n - 1 ? 0 : maxWidth - (lineLength - 1);
if (dp[i] > dp[j + 1] + lastCost) {
dp[i] = dp[j + 1] + lastCost;
path[i] = j + 1;
}
}
}
}
List<string> result = new List<string>();
int k = 0;
while (k < words.Length) {
int end = path[k];
StringBuilder line = new StringBuilder(words[k]);
for (int i = k + 1; i < end; i++) {
line.Append(' ').Append(words[i]);
}
line.Append(new string(' ', maxWidth - line.Length));
result.Add(line.ToString());
k = end;
}
return result;
}
}This C++ implementation uses a dynamic programming table, dp, to compute the minimum penalty of breaking words into lines. path is used to store optimal partitioning. The final step reconstructs justified text using information stored in path.
Time Complexity: O(n^2), due to potential double iterations required for exploring word positions.
Space Complexity: O(n), since it uses extra space for storing dp and path arrays.
Approach 3: 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.
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;
}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
}Complexity comparison
| Approach | Complexity |
|---|---|
| Greedy Line Construction | Time Complexity: O(n), where n is the total number of characters in all words. |
| Dynamic Programming Approach | Time Complexity: O(n^2), due to potential double iterations required for exploring word positions. |
| Simulation | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Greedy Line Construction | O(n) | O(1) | Standard interview solution. Efficient when words must be processed sequentially and spacing rules are fixed. |
| Dynamic Programming Line Breaking | O(n²) | O(n²) | Useful when optimizing text layout with scoring functions such as minimizing raggedness or uneven spacing. |
Frequently asked questions
Is Text Justification easy or hard?
Text Justification is classified as a Hard problem because the implementation has many edge cases. Correctly distributing spaces, handling single‑word lines, and formatting the final line requires careful logic even though the underlying idea is greedy.
Text Justification Python/Java solution
Python, Java, and JavaScript implementations typically follow the same greedy pattern: collect words for a line, compute the number of spaces needed, distribute them across gaps, and handle the last line separately with left justification.
How to solve Text Justification in O(n)?
Process the words sequentially and build one line at a time. Keep adding words until adding the next word would exceed maxWidth, then calculate how many spaces must be inserted between existing words. Distribute spaces evenly, with extra spaces placed in the leftmost gaps, and move to the next line.
What is the best approach for Text Justification?
Greedy line construction is the standard solution. Iterate through the words, pack as many as possible into the current line, then distribute the remaining spaces evenly between gaps. This runs in O(n) time and uses O(1) extra space, making it ideal for interview settings.
Is Text Justification asked at Google/Amazon/Meta?
Text formatting and string simulation problems like Text Justification have appeared in interviews at large companies including Google, Amazon, and Meta. The problem tests careful edge‑case handling, greedy reasoning, and string manipulation under constraints.
What data structure is used in Text Justification?
The main structures are arrays (or lists) to store words and strings to construct each line. The algorithm relies on counters, index pointers, and simple arithmetic for space distribution rather than complex data structures.
What is the time complexity of Text Justification?
The optimal greedy solution runs in O(n) time where n is the number of words or total characters processed. Each word is visited once while constructing lines and distributing spaces. Space complexity is O(1) excluding the output list of formatted lines.
