Maximum Font to Fit a Sentence in a Screen

Patrick Leaton
Problem Description

You are given a string text. We want to display text on a screen of width w and height h. You can choose any font size from array fonts, which contains the available font sizes in ascending order.

You can use the FontInfo interface to get the width and height of any character at any available font size.

The FontInfo interface is defined as such:

interface FontInfo {
  // Returns the width of character ch on the screen using font size fontSize.
  // O(1) per call
  public int getWidth(int fontSize, char ch);

  // Returns the height of any character on the screen using font size fontSize.
  // O(1) per call
  public int getHeight(int fontSize);
}

The calculated width of text for some fontSize is the sum of every getWidth(fontSize, text[i]) call for each 0 <= i < text.length (0-indexed). The calculated height of text for some fontSize is getHeight(fontSize). Note that text is displayed on a single line.

It is guaranteed that FontInfo will return the same value if you call getHeight or getWidth with the same parameters.

It is also guaranteed that for any font size fontSize and any character ch:

  • getHeight(fontSize) <= getHeight(fontSize+1)
  • getWidth(fontSize, ch) <= getWidth(fontSize+1, ch)

Return the maximum font size you can use to display text on the screen. If text cannot fit on the display with any font size, return -1.

 

Example 1:

Input: text = "helloworld", w = 80, h = 20, fonts = [6,8,10,12,14,16,18,24,36]
Output: 6

Example 2:

Input: text = "leetcode", w = 1000, h = 50, fonts = [1,2,4]
Output: 4

Example 3:

Input: text = "easyquestion", w = 100, h = 100, fonts = [10,15,20,25]
Output: -1

 

Constraints:

  • 1 <= text.length <= 50000
  • text contains only lowercase English letters.
  • 1 <= w <= 10^7
  • 1 <= h <= 10^4
  • 1 <= fonts.length <= 10^5
  • 1 <= fonts[i] <= 10^5
  • fonts is sorted in ascending order and does not contain duplicates.

 

The description was taken from https://leetcode.com/problems/maximum-font-to-fit-a-sentence-in-a-screen/.

Problem Solution

# """
# This is FontInfo's API interface.
# You should not implement it, or speculate about its implementation
# """
#class FontInfo(object):
#    def getWidth(self, fontSize:int, ch:str) -> int:
#
#    def getHeight(self, fontSize:int) -> int:
#
#O(Log(N)) Time, O(1) Space
class Solution:
    def maxFont(self, text: str, w: int, h: int, fonts: List[int], fontInfo : 'FontInfo') -> int:
        left, right = 0, len(fonts) - 1
        output = -1
       
        def can_fit(font:int) -> bool:
            if fontInfo.getHeight(font) > h:
                return False
            remaining = w
            for char in text:
                remaining -= fontInfo.getWidth(font, char)
                if remaining < 0:
                    return False
            return True
       
        while left <= right:
            mid = (left + right) // 2
            if can_fit(fonts[mid]):
                output = fonts[mid]
                left = mid + 1
            else:
                right = mid - 1
               
        return output

Problem Explanation


A brute force approach would be to traverse the fonts list backwards and to test each font by seeing if the given text can fit through repeated calls of the length and width FontInfo interface.  The first font that passed this condition would be the greatest since we were traversing them from greatest to least.

That would require a linear time complexity, however.

We may notice that these fonts are given to us in sorted order and within this sorted order, we are looking for a specific target: the maximum font that could fit the text to the screen.

That is an indication that a binary search can be applied.  This question is similar to First Bad Version.

What we can do is create a helper function to sum the pixel sizes of each character within the text and if the cumulative pixel sum ever becomes greater than the screen size, we will return false.  Otherwise, we will return true.

Then, we will perform a binary search by passing the mean of each current search space into that helper function.  If we find that the current font can't fit, then we will move our search to the left and try a smaller font.  Otherwise, we'll try a larger font.

When our search has finished, the maximum font that can fit the sentence will be the last one that was successful.


Let's start by setting our pointers to the beginning and end of our initial search space.

        left, right = 0, len(fonts) - 1

 

We'll also need an output variable of the maximum viable font, initialized to a negative one since that is what we will return if none of the fonts could fit the text to the screen.

        output = -1


Next, let's create our helper function to see if the current font can fit the text to the screen.

        def can_fit(font:int) -> bool:

 

If the height of the font is greater than the height of the screen then we can immediately return false because this font won't fit.

            if fontInfo.getHeight(font) > h:
                return False

 

Otherwise, we will create a running counter of the remaining space on the width of the screen.

            remaining = w

 

For each character in the given text, we will decrement the remaining value by the width of the character of the given font.

            for char in text:
                remaining -= fontInfo.getWidth(font, char)

 

If we ever surpass the screen width, then we will return false.

                if remaining < 0:
                    return False

 

Otherwise, this font can fit the text so we will return true.

            return True


Now we just have a normal binary search to perform.

While the search space hasn't been reduced to zero, we will continue our search.

        while left <= right:

 

Within each iteration, we will set a middle pointer at the mean of the two current pointers.

            mid = (left + right) // 2

 

If the fonts at this mid pointer can fit, we will update the output to this font and move our search rightward to try a greater font size.

            if can_fit(fonts[mid]):
                output = fonts[mid]
                left = mid + 1

 

Otherwise, we will move our search leftward and try to fit a smaller font.

            else:
                right = mid - 1

 

After we have reduced the entire space, we will return the output font which will be a negative one if we never found a viable font that could fit the text.

        return output