Facebook Pixel

1618. Maximum Font to Fit a Sentence in a Screen

MediumArrayStringBinary SearchInteractive
Leetcode Link

Problem Description

You need to find the maximum font size that can be used to display a given string text on a screen with width w and height h.

You're given:

  • A string text to display
  • Screen dimensions: width w and height h
  • An array fonts containing available font sizes in ascending order
  • A FontInfo interface with two methods:
    • getWidth(fontSize, ch): returns the width of character ch at the given font size
    • getHeight(fontSize): returns the height of any character at the given font size

The text will be displayed on a single line. For the text to fit on the screen with a particular font size:

  1. The total width (sum of widths of all characters) must be ≤ w
  2. The height must be ≤ h

The problem guarantees that:

  • Font metrics are consistent (same parameters always return same values)
  • Larger font sizes have larger or equal dimensions:
    • getHeight(fontSize) ≤ getHeight(fontSize+1)
    • getWidth(fontSize, ch) ≤ getWidth(fontSize+1, ch)

The solution uses binary search on the fonts array. The check function verifies if a given font size works by:

  1. Checking if the height fits: fontInfo.getHeight(size) ≤ h
  2. Checking if the total width fits: sum of all character widths ≤ w

Since fonts is sorted and larger fonts have larger dimensions, binary search efficiently finds the maximum valid font size. If no font size works, return -1.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing this as an optimization problem with a monotonic property. Since the fonts array is sorted in ascending order and larger font sizes produce larger dimensions (both width and height), we have a clear pattern: if a font size is too large to fit, all larger font sizes will also be too large.

This monotonic property immediately suggests binary search. Think of it this way - we're looking for a threshold: the largest font size that still fits. Everything below this threshold works, everything above doesn't work. This creates a partition in our sorted fonts array:

  • [fonts that fit] | [fonts that don't fit]
  • Or in terms of our feasible function: [false, false, ..., false, true, true, ..., true]

We define tooLarge(fontSize) to return true if the font is too large to fit. Using the standard binary search template, we find the first index where tooLarge returns true - this is the first font that doesn't fit. The answer is then the font just before this index.

Why not just check each font size from largest to smallest? While this would work, it could require checking every font size in the worst case, giving us O(n) checks where n is the number of fonts. Binary search reduces this to O(log n) checks.

The checking logic itself is straightforward: for any given font size, we need to verify two constraints:

  1. Height constraint: The font height must fit within screen height h
  2. Width constraint: The sum of all character widths must fit within screen width w

If either constraint fails, the font is too large (returns true). The height check is quick (O(1)), and if it fails, we can skip the width calculation entirely.

Learn more about Binary Search patterns.

Solution Implementation

1# """
2# This is FontInfo's API interface.
3# You should not implement it, or speculate about its implementation
4# """
5# class FontInfo(object):
6#    Return the width of char ch when fontSize is used.
7#    def getWidth(self, fontSize, ch):
8#        """
9#        :type fontSize: int
10#        :type ch: char
11#        :rtype int
12#        """
13#
14#    def getHeight(self, fontSize):
15#        """
16#        :type fontSize: int
17#        :rtype int
18#        """
19
20from typing import List
21
22
23class Solution:
24    def maxFont(
25        self, text: str, w: int, h: int, fonts: List[int], fontInfo: 'FontInfo'
26    ) -> int:
27        def too_large(font_size: int) -> bool:
28            """Check if font size is too large to fit the text."""
29            if fontInfo.getHeight(font_size) > h:
30                return True
31            total_width = sum(fontInfo.getWidth(font_size, char) for char in text)
32            return total_width > w
33
34        # Binary search to find the first font that is too large
35        left, right = 0, len(fonts) - 1
36        first_true_index = -1
37
38        while left <= right:
39            mid = (left + right) // 2
40            if too_large(fonts[mid]):
41                first_true_index = mid
42                right = mid - 1
43            else:
44                left = mid + 1
45
46        # first_true_index is the first font that doesn't fit
47        # So the answer is the font just before it
48        if first_true_index == -1:
49            # All fonts fit, return the largest
50            return fonts[-1]
51        elif first_true_index == 0:
52            # Even the smallest font doesn't fit
53            return -1
54        else:
55            return fonts[first_true_index - 1]
56
1/**
2 * // This is the FontInfo's API interface.
3 * // You should not implement it, or speculate about its implementation
4 * interface FontInfo {
5 *     // Return the width of char ch when fontSize is used.
6 *     public int getWidth(int fontSize, char ch) {}
7 *     // Return Height of any char when fontSize is used.
8 *     public int getHeight(int fontSize)
9 * }
10 */
11class Solution {
12    private String text;
13    private int w, h;
14    private FontInfo fontInfo;
15
16    public int maxFont(String text, int w, int h, int[] fonts, FontInfo fontInfo) {
17        this.text = text;
18        this.w = w;
19        this.h = h;
20        this.fontInfo = fontInfo;
21
22        // Binary search to find the first font that is too large
23        int left = 0;
24        int right = fonts.length - 1;
25        int firstTrueIndex = -1;
26
27        while (left <= right) {
28            int mid = left + (right - left) / 2;
29            if (tooLarge(fonts[mid])) {
30                firstTrueIndex = mid;
31                right = mid - 1;
32            } else {
33                left = mid + 1;
34            }
35        }
36
37        // firstTrueIndex is the first font that doesn't fit
38        // So the answer is the font just before it
39        if (firstTrueIndex == -1) {
40            // All fonts fit, return the largest
41            return fonts[fonts.length - 1];
42        } else if (firstTrueIndex == 0) {
43            // Even the smallest font doesn't fit
44            return -1;
45        } else {
46            return fonts[firstTrueIndex - 1];
47        }
48    }
49
50    private boolean tooLarge(int fontSize) {
51        if (fontInfo.getHeight(fontSize) > h) {
52            return true;
53        }
54        int totalWidth = 0;
55        for (char c : text.toCharArray()) {
56            totalWidth += fontInfo.getWidth(fontSize, c);
57        }
58        return totalWidth > w;
59    }
60}
61
1/**
2 * // This is the FontInfo's API interface.
3 * // You should not implement it, or speculate about its implementation
4 * class FontInfo {
5 *   public:
6 *     // Return the width of char ch when fontSize is used.
7 *     int getWidth(int fontSize, char ch);
8 *
9 *     // Return Height of any char when fontSize is used.
10 *     int getHeight(int fontSize)
11 * };
12 */
13class Solution {
14public:
15    int maxFont(string text, int w, int h, vector<int>& fonts, FontInfo fontInfo) {
16        // Lambda to check if font size is too large
17        auto tooLarge = [&](int fontSize) -> bool {
18            if (fontInfo.getHeight(fontSize) > h) {
19                return true;
20            }
21            int totalWidth = 0;
22            for (char& ch : text) {
23                totalWidth += fontInfo.getWidth(fontSize, ch);
24            }
25            return totalWidth > w;
26        };
27
28        // Binary search to find the first font that is too large
29        int left = 0;
30        int right = fonts.size() - 1;
31        int firstTrueIndex = -1;
32
33        while (left <= right) {
34            int mid = left + (right - left) / 2;
35            if (tooLarge(fonts[mid])) {
36                firstTrueIndex = mid;
37                right = mid - 1;
38            } else {
39                left = mid + 1;
40            }
41        }
42
43        // firstTrueIndex is the first font that doesn't fit
44        // So the answer is the font just before it
45        if (firstTrueIndex == -1) {
46            // All fonts fit, return the largest
47            return fonts.back();
48        } else if (firstTrueIndex == 0) {
49            // Even the smallest font doesn't fit
50            return -1;
51        } else {
52            return fonts[firstTrueIndex - 1];
53        }
54    }
55};
56
1/**
2 * FontInfo API interface (provided by the system)
3 * Should not be implemented or speculated about its implementation
4 */
5interface FontInfo {
6    getWidth(fontSize: number, ch: string): number;
7    getHeight(fontSize: number): number;
8}
9
10const maxFont = function(
11    text: string,
12    w: number,
13    h: number,
14    fonts: number[],
15    fontInfo: FontInfo
16): number {
17    const tooLarge = (fontSize: number): boolean => {
18        if (fontInfo.getHeight(fontSize) > h) {
19            return true;
20        }
21        let totalWidth = 0;
22        for (const ch of text) {
23            totalWidth += fontInfo.getWidth(fontSize, ch);
24        }
25        return totalWidth > w;
26    };
27
28    // Binary search to find the first font that is too large
29    let left = 0;
30    let right = fonts.length - 1;
31    let firstTrueIndex = -1;
32
33    while (left <= right) {
34        const mid = Math.floor((left + right) / 2);
35        if (tooLarge(fonts[mid])) {
36            firstTrueIndex = mid;
37            right = mid - 1;
38        } else {
39            left = mid + 1;
40        }
41    }
42
43    // firstTrueIndex is the first font that doesn't fit
44    // So the answer is the font just before it
45    if (firstTrueIndex === -1) {
46        // All fonts fit, return the largest
47        return fonts[fonts.length - 1];
48    } else if (firstTrueIndex === 0) {
49        // Even the smallest font doesn't fit
50        return -1;
51    } else {
52        return fonts[firstTrueIndex - 1];
53    }
54};
55

Solution Approach

The solution implements the standard binary search template to find the maximum valid font size. Let's walk through the implementation step by step:

1. Helper Function - tooLarge(fontSize):

def too_large(font_size: int) -> bool:
    if fontInfo.getHeight(font_size) > h:
        return True
    total_width = sum(fontInfo.getWidth(font_size, char) for char in text)
    return total_width > w

This function returns true if the font size is too large to fit, creating a monotonic pattern:

  • For small fonts: false, false, false, ... (they fit)
  • For large fonts: ..., true, true, true (they don't fit)

2. Binary Search Setup:

left, right = 0, len(fonts) - 1
first_true_index = -1
  • left and right are indices into the fonts array
  • first_true_index = -1 tracks the first font that is too large (initialized to -1 meaning "not found yet")

3. Binary Search Loop (Standard Template):

while left <= right:
    mid = (left + right) // 2
    if too_large(fonts[mid]):
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1

This implements the standard "find first true" template:

  • Use while left <= right (not left < right)
  • When tooLarge(fonts[mid]) is true, we found a font that doesn't fit - record it in first_true_index and search left for an earlier one (right = mid - 1)
  • When false, the font fits - search right for the boundary (left = mid + 1)

4. Derive the Answer:

if first_true_index == -1:
    return fonts[-1]  # All fonts fit
elif first_true_index == 0:
    return -1  # Even smallest font doesn't fit
else:
    return fonts[first_true_index - 1]  # Font just before first too-large

The answer is the font just before first_true_index:

  • If first_true_index == -1: All fonts fit, return the largest
  • If first_true_index == 0: Even the smallest font is too large, return -1
  • Otherwise: Return fonts[first_true_index - 1]

Time Complexity: O(m * log n) where m = len(text) and n = len(fonts)

Space Complexity: O(1) - only using a few variables

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to illustrate the solution approach.

Given:

  • text = "abc"
  • w = 12 (screen width)
  • h = 8 (screen height)
  • fonts = [1, 2, 3, 4, 5]
  • FontInfo returns:
    • For font size 1: height = 2, widths: 'a'=2, 'b'=2, 'c'=2 → total = 6
    • For font size 2: height = 4, widths: 'a'=3, 'b'=3, 'c'=3 → total = 9
    • For font size 3: height = 6, widths: 'a'=4, 'b'=4, 'c'=4 → total = 12
    • For font size 4: height = 8, widths: 'a'=5, 'b'=5, 'c'=5 → total = 15
    • For font size 5: height = 10, widths: 'a'=6, 'b'=6, 'c'=6 → total = 18

tooLarge pattern: [false, false, false, true, true] (fonts 1,2,3 fit; fonts 4,5 don't)

Step-by-step Binary Search:

Initial Setup:

  • left = 0, right = 4
  • first_true_index = -1

Iteration 1:

  • mid = (0 + 4) // 2 = 2
  • Check tooLarge(fonts[2]) = tooLarge(3):
    • Height: 6 ≤ 8 ✓
    • Width: 12 ≤ 12 ✓
    • Returns false (font 3 fits)
  • Since tooLarge is false, search right: left = 2 + 1 = 3

Iteration 2:

  • left = 3, right = 4
  • mid = (3 + 4) // 2 = 3
  • Check tooLarge(fonts[3]) = tooLarge(4):
    • Height: 8 ≤ 8 ✓
    • Width: 15 > 12 ✗
    • Returns true (font 4 is too large)
  • Since tooLarge is true: first_true_index = 3, right = 3 - 1 = 2

Iteration 3:

  • left = 3, right = 2
  • left > right, loop terminates

Derive Answer:

  • first_true_index = 3 (first font that doesn't fit is at index 3)
  • Answer = fonts[first_true_index - 1] = fonts[2] = 3

Result: The maximum font size that can display "abc" is 3.

This example demonstrates the standard binary search template:

  • We track first_true_index to find the boundary
  • The answer is fonts[first_true_index - 1] - the last font that fits
  • We checked only 2 font sizes (3 and 4) out of 5 possible options

Time and Space Complexity

Time Complexity: O(n * log(m)) where n is the length of the text string and m is the number of fonts in the fonts array.

  • The binary search performs O(log(m)) iterations to find the maximum valid font size
  • In each iteration of the binary search, the check function is called once
  • The check function iterates through all characters in the text string, making n API calls to getWidth() and one call to getHeight()
  • Each API call is assumed to be O(1)
  • Therefore, each check function call takes O(n) time
  • Total time complexity: O(n * log(m))

Space Complexity: O(1)

  • The algorithm uses only a constant amount of extra space for variables (left, right, ans, mid)
  • The check function uses a generator expression with sum() which processes elements one at a time without storing them
  • No additional data structures are created that scale with input size
  • The space used does not depend on the length of the text or the number of fonts

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Using the Wrong Binary Search Template

Pitfall: Using while left < right with left = mid instead of the standard template.

# Wrong variant - prone to infinite loops
while left < right:
    mid = (left + right + 1) // 2
    if canFit(fonts[mid]):
        left = mid
    else:
        right = mid - 1

Solution: Use the standard template with first_true_index:

# Correct - standard template
first_true_index = -1
while left <= right:
    mid = (left + right) // 2
    if tooLarge(fonts[mid]):
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1

2. Confusing Array Indices with Font Sizes

Pitfall: Mixing up the index in the fonts array with the actual font size value.

# Wrong - treating index as font size
if tooLarge(mid):  # mid is an index, not a font size!
    first_true_index = mid

Solution: Always use fonts[mid] when checking:

# Correct
if tooLarge(fonts[mid]):  # fonts[mid] is the actual font size
    first_true_index = mid

3. Forgetting Edge Cases When Deriving the Answer

Pitfall: Not handling all three cases for first_true_index:

# Wrong - missing edge cases
return fonts[first_true_index - 1]

Solution: Handle all three cases:

# Correct
if first_true_index == -1:
    return fonts[-1]  # All fonts fit
elif first_true_index == 0:
    return -1  # No font fits
else:
    return fonts[first_true_index - 1]

4. Integer Overflow in Width Calculation

Pitfall: For very long texts or large font sizes, the sum of character widths might overflow.

Solution: Break early if width exceeds limit:

def too_large(font_size):
    if fontInfo.getHeight(font_size) > h:
        return True
    total_width = 0
    for c in text:
        total_width += fontInfo.getWidth(font_size, c)
        if total_width > w:  # Early termination
            return True
    return False

5. Inverting the Feasible Function Logic

Pitfall: Defining feasible as "font fits" but forgetting to adjust the answer derivation.

The standard template finds the first true. If you define feasible(x) = font fits, then first_true_index is the first font that fits, and you'd need to find the last true instead.

Solution: Define tooLarge(x) = font doesn't fit. This creates the pattern [false, ..., false, true, ..., true], and first_true_index gives you the first font that doesn't fit. The answer is fonts[first_true_index - 1].

Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.

Which of the following method should we use to solve this problem?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More