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
k
characters, except for the first group which can be shorter thank
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.
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
cnt
reaches 0, we've completed a group, so we:- Reset
cnt
tok
for 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 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, 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
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
tok
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 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.
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:
- Processing all characters first
- Building complete groups
- Joining groups with dashes only between them
How does quick sort divide the problem into subproblems?
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 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
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!