Facebook Pixel

800. Similar RGB Color 🔒

EasyMathStringEnumeration
Leetcode Link

Problem Description

This problem asks you to find the closest color that can be represented in shorthand format to a given color.

In RGB color notation, colors are represented as #RRGGBB where RR, GG, and BB are two-digit hexadecimal values (00 to FF) representing red, green, and blue components respectively.

A shorthand format exists where if both digits of each component are the same, the color can be written with just one digit per component. For example:

  • #AABBCC can be written as #ABC in shorthand (where AA becomes A, BB becomes B, CC becomes C)
  • #1155cc can be shortened to #15c

The similarity between two colors is calculated using the formula: -(AB - UV)² - (CD - WX)² - (EF - YZ)²

Where #ABCDEF and #UVWXYZ are the two colors being compared, and AB, CD, EF, UV, WX, YZ represent the decimal values of the respective hexadecimal pairs.

Given a color string in the format #ABCDEF, you need to find and return the color that:

  1. Can be represented in shorthand format (meaning each component must have identical digits like 00, 11, 22, ..., FF)
  2. Has the highest similarity score (least negative value) with the input color

The solution works by finding the nearest "shorthand-compatible" value for each color component independently. For each two-digit hex component, it determines which valid shorthand value (00, 11, 22, ..., FF) is closest. This is done by dividing by 17 (since valid values are multiples of 17 in decimal: 0x00=0, 0x11=17, 0x22=34, etc.) and rounding to the nearest integer.

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

Intuition

The key insight is that colors that can be written in shorthand have a special property: their hexadecimal components must have identical digits (like 00, 11, 22, ..., FF). In decimal, these values are 0, 17, 34, 51, ..., 255, which are all multiples of 17.

Since the similarity formula uses squared differences, minimizing the total distance is equivalent to minimizing the distance for each color component independently. This is because the formula is additive: -(R₁-R₂)² - (G₁-G₂)² - (B₁-B₂)². Each component contributes independently to the total, so we can optimize each one separately.

For any given color component value (0-255), we need to find the nearest multiple of 17. We can do this by:

  1. Dividing the value by 17 to see how many "steps" of 17 it contains
  2. Checking if we should round up or down based on the remainder

The clever part of the solution is recognizing that when we divide by 17 and get a remainder, if that remainder is greater than 8 (which is half of 17), we should round up to the next multiple of 17. Otherwise, we round down.

For example:

  • If we have value 128: 128 ÷ 17 = 7 with remainder 9. Since 9 > 8, we round up to 8 × 17 = 136 (which is 0x88 in hex)
  • If we have value 125: 125 ÷ 17 = 7 with remainder 6. Since 6 ≤ 8, we round down to 7 × 17 = 119 (which is 0x77 in hex)

This greedy approach of finding the nearest valid value for each component independently gives us the globally optimal solution because the components don't interact in the similarity formula.

Learn more about Math patterns.

Solution Approach

The solution implements a helper function f(x) that converts a two-digit hexadecimal string to its nearest shorthand-compatible hexadecimal value.

Here's how the implementation works:

  1. Helper Function f(x):

    • Takes a two-digit hex string as input (like "AB")
    • Converts it to decimal using int(x, 16)
    • Uses divmod to divide by 17, getting quotient y and remainder z
    • If remainder z > 8, increments y by 1 (rounding up to next multiple of 17)
    • Multiplies y by 17 to get the final value
    • Formats it back to a two-digit hex string using '{:02x}'.format(17 * y)
  2. Main Logic:

    • Extracts the three color components from the input string:
      • a = color[1:3] (red component)
      • b = color[3:5] (green component)
      • c = color[5:7] (blue component)
    • Applies the helper function f to each component independently
    • Combines the results using an f-string: f'#{f(a)}{f(b)}{f(c)}'

The algorithm leverages the mathematical property that valid shorthand values in decimal are 0, 17, 34, 51, ..., 238, 255, which correspond to hex values 00, 11, 22, ..., ee, ff.

For any input value n:

  • We calculate n ÷ 17 to find which "bucket" it falls into
  • If the remainder is more than half of 17 (i.e., > 8), we round up
  • Otherwise, we keep the current bucket

This greedy approach ensures each component is mapped to its nearest valid shorthand value, which maximizes the similarity score since the formula uses negative squared differences - smaller differences lead to larger (less negative) similarity values.

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 the solution with the input color #09f166.

Step 1: Extract the color components

  • Red component: 09 (hex) = 9 (decimal)
  • Green component: f1 (hex) = 241 (decimal)
  • Blue component: 66 (hex) = 102 (decimal)

Step 2: Find the nearest shorthand value for each component

For the red component (9):

  • Divide by 17: 9 ÷ 17 = 0 with remainder 9
  • Since remainder 9 > 8, we round up: 0 + 1 = 1
  • Multiply by 17: 1 × 17 = 17
  • Convert to hex: 17 (decimal) = 11 (hex)

For the green component (241):

  • Divide by 17: 241 ÷ 17 = 14 with remainder 3
  • Since remainder 3 ≤ 8, we keep: 14
  • Multiply by 17: 14 × 17 = 238
  • Convert to hex: 238 (decimal) = ee (hex)

For the blue component (102):

  • Divide by 17: 102 ÷ 17 = 6 with remainder 0
  • Since remainder 0 ≤ 8, we keep: 6
  • Multiply by 17: 6 × 17 = 102
  • Convert to hex: 102 (decimal) = 66 (hex)

Step 3: Combine the results The final shorthand-compatible color is #11ee66.

Let's verify this is optimal by checking the similarity score:

  • Red difference: (9 - 17)² = 64
  • Green difference: (241 - 238)² = 9
  • Blue difference: (102 - 102)² = 0
  • Total similarity: -(64 + 9 + 0) = -73

This solution minimizes each component's distance independently, ensuring the maximum possible similarity score.

Solution Implementation

1class Solution:
2    def similarRGB(self, color: str) -> str:
3        """
4        Find the closest web-safe color to the given hex color.
5        Web-safe colors have identical digits for each color component (e.g., #11, #22, #aa, #ff).
6      
7        Args:
8            color: A hex color string in format "#RRGGBB"
9      
10        Returns:
11            The closest web-safe color in hex format
12        """
13      
14        def find_closest_shorthand(hex_component: str) -> str:
15            """
16            Find the closest shorthand hex value for a given 2-digit hex component.
17            Shorthand values are: 00, 11, 22, ..., ee, ff (multiples of 17 in decimal).
18          
19            Args:
20                hex_component: A 2-character hex string (e.g., "3a")
21          
22            Returns:
23                The closest shorthand hex value as a 2-character string
24            """
25            # Convert hex string to decimal
26            decimal_value = int(hex_component, 16)
27          
28            # Find how many times 17 goes into the value and the remainder
29            # Since shorthand values are 0x00, 0x11, 0x22, ..., 0xff
30            # which are 0, 17, 34, ..., 255 in decimal (multiples of 17)
31            quotient, remainder = divmod(decimal_value, 17)
32          
33            # If remainder is greater than 8 (half of 17), round up
34            if remainder > 8:
35                quotient += 1
36          
37            # Convert back to hex format with 2 digits
38            # Multiply by 17 to get the actual shorthand value
39            return f'{17 * quotient:02x}'
40      
41        # Extract RGB components from the color string
42        red_component = color[1:3]
43        green_component = color[3:5]
44        blue_component = color[5:7]
45      
46        # Find the closest shorthand for each component and combine
47        return f'#{find_closest_shorthand(red_component)}{find_closest_shorthand(green_component)}{find_closest_shorthand(blue_component)}'
48
1class Solution {
2    /**
3     * Finds the most similar shorthand RGB color to the given color.
4     * A shorthand color has the format #XYZ where each component X, Y, Z 
5     * is a single hex digit that represents XX, YY, ZZ respectively.
6     * 
7     * @param color The input color in format #RRGGBB
8     * @return The most similar shorthand color in format #RRGGBB
9     */
10    public String similarRGB(String color) {
11        // Extract red, green, and blue components from the input color
12        String redComponent = color.substring(1, 3);
13        String greenComponent = color.substring(3, 5);
14        String blueComponent = color.substring(5, 7);
15      
16        // Convert each component to its nearest shorthand value and combine
17        return "#" + findNearestShorthandValue(redComponent) + 
18                     findNearestShorthandValue(greenComponent) + 
19                     findNearestShorthandValue(blueComponent);
20    }
21
22    /**
23     * Finds the nearest shorthand hex value for a given two-digit hex component.
24     * Shorthand values are of the form AA where A is a single hex digit (00, 11, 22, ..., FF).
25     * These values are multiples of 17 in decimal (0, 17, 34, ..., 255).
26     * 
27     * @param hexComponent A two-character hex string representing a color component
28     * @return The nearest shorthand hex value as a two-character string
29     */
30    private String findNearestShorthandValue(String hexComponent) {
31        // Convert hex string to decimal value
32        int decimalValue = Integer.parseInt(hexComponent, 16);
33      
34        // Find the nearest multiple of 17
35        // Division by 17 gives the base multiplier
36        // If remainder > 8, round up; otherwise round down
37        int nearestMultiplier = decimalValue / 17 + (decimalValue % 17 > 8 ? 1 : 0);
38      
39        // Convert back to hex format with leading zero if necessary
40        return String.format("%02x", 17 * nearestMultiplier);
41    }
42}
43
1class Solution {
2public:
3    /**
4     * Finds the most similar shorthand RGB color to the given color.
5     * A shorthand color has the format #XYZ where each component X, Y, Z 
6     * is a single hex digit that represents XX, YY, ZZ respectively.
7     * 
8     * @param color The input color in format #RRGGBB
9     * @return The most similar shorthand color in format #RRGGBB
10     */
11    string similarRGB(string color) {
12        // Extract red, green, and blue components from the input color
13        string red_component = color.substr(1, 2);
14        string green_component = color.substr(3, 2);
15        string blue_component = color.substr(5, 2);
16      
17        // Convert each component to its nearest shorthand value and combine
18        return "#" + findNearestShorthandValue(red_component) + 
19                     findNearestShorthandValue(green_component) + 
20                     findNearestShorthandValue(blue_component);
21    }
22
23private:
24    /**
25     * Finds the nearest shorthand hex value for a given two-digit hex component.
26     * Shorthand values are of the form AA where A is a single hex digit (00, 11, 22, ..., FF).
27     * These values are multiples of 17 in decimal (0, 17, 34, ..., 255).
28     * 
29     * @param hex_component A two-character hex string representing a color component
30     * @return The nearest shorthand hex value as a two-character string
31     */
32    string findNearestShorthandValue(string hex_component) {
33        // Convert hex string to decimal value
34        int decimal_value = stoi(hex_component, nullptr, 16);
35      
36        // Find the nearest multiple of 17
37        // Division by 17 gives the base multiplier
38        // If remainder > 8, round up; otherwise round down
39        int nearest_multiplier = decimal_value / 17 + (decimal_value % 17 > 8 ? 1 : 0);
40      
41        // Convert back to hex format with leading zero if necessary
42        char buffer[3];
43        sprintf(buffer, "%02x", 17 * nearest_multiplier);
44      
45        return string(buffer);
46    }
47};
48
1/**
2 * Finds the most similar shorthand RGB color to the given color.
3 * A shorthand color has the format #XYZ where each component X, Y, Z 
4 * is a single hex digit that represents XX, YY, ZZ respectively.
5 * 
6 * @param color The input color in format #RRGGBB
7 * @return The most similar shorthand color in format #RRGGBB
8 */
9function similarRGB(color: string): string {
10    // Extract red, green, and blue components from the input color
11    const redComponent: string = color.substring(1, 3);
12    const greenComponent: string = color.substring(3, 5);
13    const blueComponent: string = color.substring(5, 7);
14  
15    // Convert each component to its nearest shorthand value and combine
16    return "#" + findNearestShorthandValue(redComponent) + 
17                 findNearestShorthandValue(greenComponent) + 
18                 findNearestShorthandValue(blueComponent);
19}
20
21/**
22 * Finds the nearest shorthand hex value for a given two-digit hex component.
23 * Shorthand values are of the form AA where A is a single hex digit (00, 11, 22, ..., FF).
24 * These values are multiples of 17 in decimal (0, 17, 34, ..., 255).
25 * 
26 * @param hexComponent A two-character hex string representing a color component
27 * @return The nearest shorthand hex value as a two-character string
28 */
29function findNearestShorthandValue(hexComponent: string): string {
30    // Convert hex string to decimal value
31    const decimalValue: number = parseInt(hexComponent, 16);
32  
33    // Find the nearest multiple of 17
34    // Division by 17 gives the base multiplier
35    // If remainder > 8, round up; otherwise round down
36    const nearestMultiplier: number = Math.floor(decimalValue / 17) + (decimalValue % 17 > 8 ? 1 : 0);
37  
38    // Convert back to hex format with leading zero if necessary
39    const hexValue: number = 17 * nearestMultiplier;
40    return hexValue.toString(16).padStart(2, '0');
41}
42

Time and Space Complexity

Time Complexity: O(1)

The algorithm processes a fixed-size input (a 7-character color string in the format #RRGGBB). Breaking down the operations:

  • The helper function f(x) is called exactly 3 times (once for each color component)
  • Inside f(x):
    • int(x, 16) converts a 2-character hex string to integer - O(1) since the string length is constant (2 characters)
    • divmod operation performs integer division and modulo - O(1)
    • Comparison z > 8 - O(1)
    • format operation formats a single integer - O(1)
  • String slicing operations (color[1:3], color[3:5], color[5:7]) - each is O(1) for fixed-size slices
  • Final string formatting with f-string - O(1) since we're concatenating a fixed number of fixed-size strings

Since all operations are performed a constant number of times on fixed-size data, the overall time complexity is O(1).

Space Complexity: O(1)

The algorithm uses a constant amount of extra space:

  • Variables a, b, c store 2-character strings - O(1)
  • Inside function f(x): variables y and z store integers - O(1)
  • Temporary strings created during formatting - O(1) since their size is bounded
  • The output string has a fixed length of 7 characters - O(1)

The space usage doesn't scale with any input parameter, resulting in O(1) space complexity.

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

Common Pitfalls

1. Incorrect Rounding Logic

A common mistake is using standard rounding functions like round() instead of implementing the specific rounding logic needed for this problem.

Incorrect approach:

def find_closest_shorthand(hex_component: str) -> str:
    decimal_value = int(hex_component, 16)
    # Wrong: This doesn't guarantee multiples of 17
    rounded = round(decimal_value / 17) * 17
    return f'{rounded:02x}'

Why it fails: The built-in round() function uses banker's rounding (round half to even), which may not always give the closest shorthand value for this specific problem.

Correct approach: Use divmod and check if remainder > 8 to determine rounding direction.

2. Forgetting Hexadecimal Format Padding

When converting back to hexadecimal, forgetting to pad with zeros can cause incorrect results for small values.

Incorrect approach:

def find_closest_shorthand(hex_component: str) -> str:
    decimal_value = int(hex_component, 16)
    quotient, remainder = divmod(decimal_value, 17)
    if remainder > 8:
        quotient += 1
    # Wrong: Missing zero padding
    return f'{17 * quotient:x}'  # Should be :02x

Why it fails: For values like 0x00 or 0x11, this would return "0" or "11" instead of "00" or "11", breaking the expected format.

3. Misunderstanding the Shorthand Format

Some might think shorthand means using single characters (like #ABC), but the problem requires returning the full 6-character format.

Incorrect approach:

def similarRGB(self, color: str) -> str:
    # Wrong: Returning shorthand notation
    result = ""
    for i in range(1, 7, 2):
        component = color[i:i+2]
        decimal_value = int(component, 16)
        quotient = round(decimal_value / 17)
        result += f'{quotient:x}'  # Single character
    return f'#{result}'  # Returns #abc instead of #aabbcc

Correct approach: Always return the full format like #aabbcc, where each component has identical digits.

4. Off-by-One Error in String Slicing

Incorrectly extracting color components due to wrong indices.

Incorrect approach:

# Wrong indices
red_component = color[0:2]    # Includes the '#'
green_component = color[2:4]   # Off by one
blue_component = color[4:6]    # Off by one

Correct approach: Remember that the '#' is at index 0, so components start at index 1.

5. Not Handling Edge Cases

Failing to consider boundary values like 0x00 and 0xff.

Test these edge cases:

  • #000000 should return #000000
  • #ffffff should return #ffffff
  • #090909 should return #111111 (since 9 in decimal rounds up to 17)
  • #080808 should return #000000 (since 8 in decimal rounds down to 0)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following uses divide and conquer strategy?


Recommended Readings

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

Load More