482. License Key Formatting
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
kcharacters, except for the first group which can be shorter thankbut 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.
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:
- Skip all dashes
- Convert letters to uppercase and add them to our result
- Keep track of how many more characters we need for the current group using a counter
cnt - When
cntreaches 0, we've completed a group, so we:- Reset
cnttokfor the next group - Add a dash separator (unless we're at the end)
- Reset
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 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("-") 401class 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} 511class 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}; 491/** 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} 54Solution 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
kusing modulo:(n - s.count("-")) % k - If the remainder is 0 (perfectly divisible), we want
kcharacters in the first group, henceor 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
ansto 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
cntsince 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
cntreaches 0, we've completed the current group - Reset
cnttokfor 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 EvaluatorExample 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:
| Index | Character | Action | cnt | ans |
|---|---|---|---|---|
| 0 | '2' | Add '2' to ans, decrement cnt | 0 | ['2'] |
| cnt=0, reset to k=2, add '-' | 2 | ['2', '-'] | ||
| 1 | '-' | Skip dash | 2 | ['2', '-'] |
| 2 | '5' | Add '5' to ans, decrement cnt | 1 | ['2', '-', '5'] |
| 3 | 'g' | Add 'G' (uppercase), decrement cnt | 0 | ['2', '-', '5', 'G'] |
| cnt=0, reset to k=2, add '-' | 2 | ['2', '-', '5', 'G', '-'] | ||
| 4 | '-' | Skip dash | 2 | ['2', '-', '5', 'G', '-'] |
| 5 | '3' | Add '3' to ans, decrement cnt | 1 | ['2', '-', '5', 'G', '-', '3'] |
| 6 | '-' | Skip dash | 1 | ['2', '-', '5', 'G', '-', '3'] |
| 7 | 'J' | Add 'J' (already uppercase), decrement cnt | 0 | ['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.
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:
- Processing all characters first
- Building complete groups
- Joining groups with dashes only between them
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!