Facebook Pixel

482. License Key Formatting

EasyString
Leetcode Link

Problem Description

You have a license key represented as a string s containing only alphanumeric characters and dashes. The string is divided into n + 1 groups separated by n dashes. You're also given an integer k.

Your task is to reformat the string according to these rules:

  • Each group should contain exactly k characters, except for the first group which can be shorter than k but must have at least one character
  • Dashes should be inserted between groups
  • All lowercase letters should be converted to uppercase

For example, if you have a license key like "5F3Z-2e-9-w" and k = 4, after reformatting you would get "5F3Z-2E9W". Notice how the characters are regrouped into groups of 4 (except potentially the first group), dashes are placed between groups, and all letters are uppercase.

The key insight is that you need to work backwards from the total number of non-dash characters to determine how many characters should be in the first group. If the total number of characters (excluding dashes) divides evenly by k, then the first group will have k characters. Otherwise, the first group will have the remainder when dividing by k.

Return the reformatted license key as a string.

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

Intuition

The main challenge is figuring out how many characters should go in the first group. Since we want all groups (except possibly the first) to have exactly k characters, we need to think about how to distribute the total characters.

Let's say we have a total of total characters (excluding dashes). If we divide these characters into groups of k, we might have some leftover characters. These leftover characters should form the first group.

For example, if we have 11 characters total and k = 4:

  • 11 divided by 4 gives us 2 complete groups with 3 characters remaining
  • Those 3 remaining characters become our first group
  • So we'd have groups of sizes: 3, 4, 4

This remainder can be calculated using the modulo operation: total % k. However, there's a special case: when total is perfectly divisible by k, the modulo gives us 0, but we actually want the first group to have k characters (not 0). So we use the expression (total % k) or k to handle this.

Once we know the first group size, we can process the string character by character:

  1. Skip all dashes
  2. Convert letters to uppercase and add them to our result
  3. Keep track of how many more characters we need for the current group using a counter cnt
  4. When cnt reaches 0, we've completed a group, so we:
    • Reset cnt to k for the next group
    • Add a dash separator (unless we're at the end)

The beauty of this approach is that we don't need to pre-process the string or calculate positions - we just iterate through once, building the result as we go.

Solution Approach

Let's walk through the implementation step by step:

Step 1: Calculate the first group size

n = len(s)
cnt = (n - s.count("-")) % k or k
  • First, we get the total length of the string n
  • We calculate the number of non-dash characters: n - s.count("-")
  • We find the remainder when dividing by k using modulo: (n - s.count("-")) % k
  • If the remainder is 0 (perfectly divisible), we want k characters in the first group, hence or k
  • This value is stored in cnt, representing how many characters we need for the current group

Step 2: Process each character

ans = []
for i, c in enumerate(s):
  • We create an empty list ans to build our result
  • We iterate through each character with its index

Step 3: Handle dashes and format characters

if c == "-":
    continue
ans.append(c.upper())
cnt -= 1
  • Skip dashes completely - they don't count toward our character groups
  • Convert the character to uppercase and add it to our answer
  • Decrement cnt since we've added one character to the current group

Step 4: Handle group boundaries

if cnt == 0:
    cnt = k
    if i != n - 1:
        ans.append("-")
  • When cnt reaches 0, we've completed the current group
  • Reset cnt to k for the next group
  • Add a dash separator, but only if we're not at the last character of the original string (to avoid trailing dash)

Step 5: Clean up and return

return "".join(ans).rstrip("-")
  • Join all characters in the list to form the final string
  • Use rstrip("-") to remove any trailing dash (as a safety measure, though the logic should prevent it)

The algorithm runs in O(n) time where n is the length of the input string, as we make a single pass through the string. The space complexity is also O(n) for storing the result.

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 a concrete example: s = "2-5g-3-J" and k = 2.

Step 1: Calculate the first group size

  • Total length: n = 8
  • Count dashes: s.count("-") = 3
  • Non-dash characters: 8 - 3 = 5
  • First group size: 5 % 2 = 1 (remainder when dividing 5 by 2)
  • So cnt = 1 (the first group will have 1 character)

Step 2: Process each character

Let's trace through each iteration:

IndexCharacterActioncntans
0'2'Add '2' to ans, decrement cnt0['2']
cnt=0, reset to k=2, add '-'2['2', '-']
1'-'Skip dash2['2', '-']
2'5'Add '5' to ans, decrement cnt1['2', '-', '5']
3'g'Add 'G' (uppercase), decrement cnt0['2', '-', '5', 'G']
cnt=0, reset to k=2, add '-'2['2', '-', '5', 'G', '-']
4'-'Skip dash2['2', '-', '5', 'G', '-']
5'3'Add '3' to ans, decrement cnt1['2', '-', '5', 'G', '-', '3']
6'-'Skip dash1['2', '-', '5', 'G', '-', '3']
7'J'Add 'J' (already uppercase), decrement cnt0['2', '-', '5', 'G', '-', '3', 'J']
cnt=0, but i=7 is last index, don't add '-'2['2', '-', '5', 'G', '-', '3', 'J']

Step 3: Join and return

  • Join the list: "2-5G-3J"
  • Apply rstrip("-"): No trailing dash to remove
  • Final result: "2-5G-3J"

The reformatted license key has groups of sizes: 1, 2, 2 (reading left to right), with the first group having fewer than k characters as expected when there's a remainder.

Solution Implementation

1class Solution:
2    def licenseKeyFormatting(self, s: str, k: int) -> str:
3        # Calculate total length and number of non-dash characters
4        total_length = len(s)
5        dash_count = s.count("-")
6        char_count = total_length - dash_count
7      
8        # Determine the size of the first group
9        # If char_count % k == 0, first group has k characters
10        # Otherwise, first group has (char_count % k) characters
11        first_group_size = char_count % k
12        if first_group_size == 0:
13            first_group_size = k
14      
15        # Build the result string
16        result = []
17        current_group_count = first_group_size
18      
19        for index, character in enumerate(s):
20            # Skip dash characters from the original string
21            if character == "-":
22                continue
23          
24            # Add the uppercase version of the character
25            result.append(character.upper())
26            current_group_count -= 1
27          
28            # Check if we've completed a group
29            if current_group_count == 0:
30                # Reset counter for the next group
31                current_group_count = k
32              
33                # Add dash separator if not at the end
34                if index != total_length - 1:
35                    result.append("-")
36      
37        # Join the result and remove any trailing dash
38        # (in case the last character in original string was a dash)
39        return "".join(result).rstrip("-")
40
1class Solution {
2    public String licenseKeyFormatting(String s, int k) {
3        // Calculate total number of alphanumeric characters
4        int totalLength = s.length();
5        int dashCount = (int) s.chars().filter(ch -> ch == '-').count();
6        int alphanumericCount = totalLength - dashCount;
7      
8        // Calculate the size of the first group
9        // If all characters divide evenly by k, first group size equals k
10        int firstGroupSize = alphanumericCount % k;
11        if (firstGroupSize == 0) {
12            firstGroupSize = k;
13        }
14      
15        // StringBuilder to construct the formatted result
16        StringBuilder result = new StringBuilder();
17        int currentGroupCount = firstGroupSize;
18      
19        // Iterate through each character in the input string
20        for (int i = 0; i < totalLength; i++) {
21            char currentChar = s.charAt(i);
22          
23            // Skip dashes in the original string
24            if (currentChar == '-') {
25                continue;
26            }
27          
28            // Append the uppercase version of the character
29            result.append(Character.toUpperCase(currentChar));
30            currentGroupCount--;
31          
32            // When current group is complete, prepare for next group
33            if (currentGroupCount == 0) {
34                currentGroupCount = k;  // Reset counter for next group
35              
36                // Add dash separator if not at the last character
37                if (i != totalLength - 1) {
38                    result.append('-');
39                }
40            }
41        }
42      
43        // Remove trailing dash if it exists
44        if (result.length() > 0 && result.charAt(result.length() - 1) == '-') {
45            result.deleteCharAt(result.length() - 1);
46        }
47      
48        return result.toString();
49    }
50}
51
1class Solution {
2public:
3    string licenseKeyFormatting(string s, int k) {
4        // Calculate total number of alphanumeric characters
5        int stringLength = s.length();
6        int alphanumericCount = stringLength - count(s.begin(), s.end(), '-');
7      
8        // Determine the size of the first group
9        // If alphanumericCount is divisible by k, first group has k characters
10        // Otherwise, first group has (alphanumericCount % k) characters
11        int firstGroupSize = alphanumericCount % k;
12        if (firstGroupSize == 0) {
13            firstGroupSize = k;
14        }
15      
16        // Build the formatted license key
17        string formattedKey;
18        int currentGroupSize = firstGroupSize;
19      
20        for (int i = 0; i < stringLength; ++i) {
21            char currentChar = s[i];
22          
23            // Skip dashes from original string
24            if (currentChar == '-') {
25                continue;
26            }
27          
28            // Add uppercase character to result
29            formattedKey += toupper(currentChar);
30            currentGroupSize--;
31          
32            // Add dash after completing a group (except at the end)
33            if (currentGroupSize == 0) {
34                currentGroupSize = k;  // Reset counter for next group
35                if (i != stringLength - 1) {
36                    formattedKey += '-';
37                }
38            }
39        }
40      
41        // Remove trailing dash if present (handles edge cases)
42        if (!formattedKey.empty() && formattedKey.back() == '-') {
43            formattedKey.pop_back();
44        }
45      
46        return formattedKey;
47    }
48};
49
1/**
2 * Formats a license key by removing dashes and regrouping characters
3 * @param s - The license key string to format
4 * @param k - The number of characters in each group (except possibly the first group)
5 * @returns The formatted license key with uppercase letters and dashes between groups
6 */
7function licenseKeyFormatting(s: string, k: number): string {
8    const stringLength: number = s.length;
9  
10    // Calculate the number of dashes in the original string
11    const dashMatches: RegExpMatchArray | null = s.match(/-/g);
12    const dashCount: number = dashMatches ? dashMatches.length : 0;
13  
14    // Calculate the size of the first group
15    // If total characters divides evenly by k, first group has k characters
16    // Otherwise, first group has the remainder
17    const totalCharacters: number = stringLength - dashCount;
18    let firstGroupSize: number = totalCharacters % k || k;
19  
20    const result: string[] = [];
21  
22    // Iterate through each character in the original string
23    for (let index: number = 0; index < stringLength; index++) {
24        const currentChar: string = s[index];
25      
26        // Skip dashes from the original string
27        if (currentChar === '-') {
28            continue;
29        }
30      
31        // Add the uppercase version of the character
32        result.push(currentChar.toUpperCase());
33      
34        // Decrement the counter for the current group
35        firstGroupSize--;
36      
37        // If we've completed a group and not at the end, add a dash
38        if (firstGroupSize === 0) {
39            firstGroupSize = k; // Reset counter for next group
40            if (index !== stringLength - 1) {
41                result.push('-');
42            }
43        }
44    }
45  
46    // Remove any trailing dashes that may have been added
47    while (result.at(-1) === '-') {
48        result.pop();
49    }
50  
51    // Join the array into a final string
52    return result.join('');
53}
54

Time and Space Complexity

The time complexity is O(n), where n is the length of the string s. The algorithm iterates through the string once with a single for loop, and each operation inside the loop (checking if character is "-", appending to list, upper() conversion) takes constant time O(1). The final join() operation also takes O(n) time, and rstrip() takes at most O(n) time. Therefore, the overall time complexity is O(n) + O(n) + O(n) = O(n).

The space complexity is O(n) for the output string stored in the ans list. However, if we ignore the space consumption of the answer string (as mentioned in the reference), the space complexity is O(1). The only additional variables used are n, cnt, i, and c, which all use constant space regardless of input size.

Common Pitfalls

1. Incorrectly Adding Trailing Dashes

The most common pitfall is adding an unnecessary dash at the end of the formatted string. This happens when the logic for determining whether to add a dash doesn't properly account for the end of the string.

Problematic approach:

# Wrong: Checking if we've completed a group without considering position
if current_group_count == 0:
    current_group_count = k
    result.append("-")  # This always adds a dash!

Why it fails: If the last character processed completes a group, this code will add a trailing dash that shouldn't be there.

Solution: Check if you're at the end of processing before adding a dash:

if current_group_count == 0:
    current_group_count = k
    # Only add dash if there are more characters to process
    if index != total_length - 1:
        result.append("-")

However, this check index != total_length - 1 can still be problematic because index refers to the position in the original string, which includes dashes. A more robust solution is to track whether there are more non-dash characters to process.

2. Mishandling Empty Input or All-Dash Input

Another pitfall is not handling edge cases where the input contains no alphanumeric characters.

Problematic scenario:

s = "---"  # Only dashes
k = 2
# The code might produce "" or fail

Solution: Add a check for empty result:

# After processing all characters
if not result:
    return ""

3. Off-by-One Error in First Group Calculation

A subtle pitfall is incorrectly calculating the first group size when the total character count is divisible by k.

Wrong approach:

first_group_size = char_count % k  # This gives 0 when divisible!

Why it fails: When char_count is perfectly divisible by k, this gives 0, meaning the first group would have 0 characters, which violates the requirement.

Solution: Use the conditional operator or an if-statement:

first_group_size = char_count % k or k
# OR
first_group_size = char_count % k
if first_group_size == 0:
    first_group_size = k

4. Better Approach to Avoid the Trailing Dash Issue Entirely

Instead of checking positions, a cleaner approach is to track remaining characters:

class Solution:
    def licenseKeyFormatting(self, s: str, k: int) -> str:
        # Remove all dashes and convert to uppercase first
        chars = [c.upper() for c in s if c != '-']
      
        if not chars:
            return ""
      
        # Calculate first group size
        first_group_size = len(chars) % k or k
      
        # Build result with proper grouping
        result = []
        idx = 0
      
        # Add first group
        result.append(''.join(chars[:first_group_size]))
        idx = first_group_size
      
        # Add remaining groups
        while idx < len(chars):
            result.append(''.join(chars[idx:idx+k]))
            idx += k
      
        return '-'.join(result)

This approach completely avoids the trailing dash issue by:

  1. Processing all characters first
  2. Building complete groups
  3. Joining groups with dashes only between them
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How does quick sort divide the problem into subproblems?


Recommended Readings

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

Load More