3412. Find Mirror Score of a String
Problem Description
You are given a string s
consisting of lowercase English letters.
The mirror of a letter is defined as its counterpart when the alphabet is reversed. For example:
- The mirror of
'a'
is'z'
(first letter ↔ last letter) - The mirror of
'b'
is'y'
(second letter ↔ second-to-last letter) - The mirror of
'c'
is'x'
, and so on
Initially, all characters in the string are unmarked. You need to process the string from left to right and calculate a score based on the following rules:
- Iterate through each index
i
from left to right (starting from index 0) - For each index
i
, look for the closest unmarked indexj
where:j < i
(j must be to the left of i)s[j]
is the mirror ofs[i]
- If such an index
j
is found:- Mark both indices
i
andj
as used (they cannot be paired again) - Add
i - j
to your total score
- Mark both indices
- If no such index
j
exists, skip indexi
and continue
Return the total score after processing the entire string.
Example walkthrough:
For a string like "abcyxz"
:
- At index 3 (
'y'
), we can pair it with index 1 ('b'
) since'b'
is the mirror of'y'
. Score += 3 - 1 = 2 - At index 4 (
'x'
), we can pair it with index 2 ('c'
) since'c'
is the mirror of'x'
. Score += 4 - 2 = 2 - At index 5 (
'z'
), we can pair it with index 0 ('a'
) since'a'
is the mirror of'z'
. Score += 5 - 0 = 5 - Total score = 2 + 2 + 5 = 9
The key insight is that when multiple unmarked mirror characters exist to the left, we always choose the closest one (the one with the largest index that is still less than i
).
Intuition
When we process the string from left to right, at each position i
, we need to find the closest unmarked mirror character to its left. The key insight is that "closest" means we want the rightmost occurrence among all the unmarked mirror characters that appear before position i
.
This naturally suggests using a data structure that can efficiently:
- Store positions of unmarked characters
- Retrieve the most recent (rightmost) position of a specific character
- Remove a position once it's been marked
A hash table with lists is perfect for this. For each character, we maintain a list of its unmarked positions. When we need to find a mirror match, we look for the mirror character's list and take the last element (most recent position). This gives us the closest unmarked position automatically.
The clever part of the solution is that instead of storing both the character and its mirror separately, we can optimize by only storing positions when we can't find a match. Here's the key observation:
- When we encounter character
x
at positioni
, we check if its mirrory
has any unmarked positions available - If
y
has positions available, we found a match! We use the most recent one and add the score - If
y
has no positions available, thenx
at positioni
might be a future match for somey
that comes later, so we store positioni
under characterx
This approach ensures that:
- We always pair with the closest (most recent) unmarked mirror character
- We efficiently manage the unmarked positions without explicitly tracking a "marked" status
- The time complexity is optimal since each position is processed at most twice (once when stored, once when matched)
The formula to calculate the mirror is elegant: for character x
, its mirror is chr(ord('a') + ord('z') - ord(x))
. This works because if x
is the k-th letter from the start, its mirror is the k-th letter from the end.
Learn more about Stack patterns.
Solution Approach
The solution uses a hash table with lists to efficiently track unmarked character positions and find mirror matches.
Data Structure:
- Use a
defaultdict(list)
namedd
where:- Key: a character
- Value: list of indices where this character appears and is still unmarked
Algorithm Steps:
-
Initialize variables:
- Create an empty hash table
d
to store character positions - Set
ans = 0
to accumulate the total score
- Create an empty hash table
-
Process each character from left to right: For each position
i
with characterx
:a. Calculate the mirror character:
- Use the formula
y = chr(ord('a') + ord('z') - ord(x))
- This converts
'a'
↔'z'
,'b'
↔'y'
, etc.
b. Check if mirror character has unmarked positions:
- If
d[y]
is not empty (mirror charactery
has unmarked positions):- Pop the last element
j
fromd[y]
usingd[y].pop()
- This gives us the closest (rightmost) unmarked position of the mirror
- Add
i - j
to the answer - Both positions
i
andj
are now implicitly marked (removed from tracking)
- Pop the last element
- If
d[y]
is empty (no unmarked mirror positions available):- Append current position
i
tod[x]
usingd[x].append(i)
- This position might be matched with a future occurrence of its mirror
- Append current position
- Use the formula
-
Return the total score accumulated in
ans
Why this works:
- By using
pop()
on the list, we always get the most recent (closest) unmarked position - By storing positions only when no match is found, we avoid redundant storage
- Each position is handled at most twice: once when stored, once when matched and removed
- The implicit marking happens through removal from the hash table, eliminating the need for a separate "marked" array
Time Complexity: O(n)
where n
is the length of the string, as each character is processed once
Space Complexity: O(n)
in the worst case when no characters can be paired
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 trace through the string "dcbaz"
to see how the solution works:
Initial Setup:
d = {}
(empty hash table)ans = 0
Step 1: Process index 0, character 'd'
- Mirror of 'd' is 'w' (using formula: chr(ord('a') + ord('z') - ord('d')) = chr(97 + 122 - 100) = chr(119) = 'w')
- Check
d['w']
: empty list (no unmarked 'w' positions) - No match found, so store this position:
d['d'] = [0]
- Current state:
d = {'d': [0]}
,ans = 0
Step 2: Process index 1, character 'c'
- Mirror of 'c' is 'x'
- Check
d['x']
: empty list - No match found, so store:
d['c'] = [1]
- Current state:
d = {'d': [0], 'c': [1]}
,ans = 0
Step 3: Process index 2, character 'b'
- Mirror of 'b' is 'y'
- Check
d['y']
: empty list - No match found, so store:
d['b'] = [2]
- Current state:
d = {'d': [0], 'c': [1], 'b': [2]}
,ans = 0
Step 4: Process index 3, character 'a'
- Mirror of 'a' is 'z'
- Check
d['z']
: empty list - No match found, so store:
d['a'] = [3]
- Current state:
d = {'d': [0], 'c': [1], 'b': [2], 'a': [3]}
,ans = 0
Step 5: Process index 4, character 'z'
- Mirror of 'z' is 'a'
- Check
d['a']
: contains [3] - Match found! Pop the last (and only) element:
j = 3
- Add to score:
ans += 4 - 3 = 1
- After popping,
d['a']
becomes empty - Current state:
d = {'d': [0], 'c': [1], 'b': [2], 'a': []}
,ans = 1
Final Result: ans = 1
The only pair formed was between 'a' at index 3 and 'z' at index 4, contributing a score of 1 to the total.
Solution Implementation
1from collections import defaultdict
2
3class Solution:
4 def calculateScore(self, s: str) -> int:
5 # Dictionary to store indices of unmatched characters
6 # Key: character, Value: list of indices where this character appears
7 char_indices = defaultdict(list)
8 total_score = 0
9
10 for current_index, current_char in enumerate(s):
11 # Calculate the mirror character (a↔z, b↔y, c↔x, etc.)
12 # Formula: mirror = 'a' + 'z' - current_char
13 mirror_char = chr(ord('a') + ord('z') - ord(current_char))
14
15 # Check if we have an unmatched mirror character
16 if char_indices[mirror_char]:
17 # Found a matching pair: pop the most recent index of mirror character
18 mirror_index = char_indices[mirror_char].pop()
19 # Add the distance between current and mirror character to score
20 total_score += current_index - mirror_index
21 else:
22 # No mirror character available, store current character for future matching
23 char_indices[current_char].append(current_index)
24
25 return total_score
26
1class Solution {
2 public long calculateScore(String s) {
3 // Map to store unmatched characters and their indices
4 // Key: character, Value: list of indices where this character appears
5 Map<Character, List<Integer>> unmatchedCharIndices = new HashMap<>(26);
6
7 int stringLength = s.length();
8 long totalScore = 0;
9
10 // Process each character in the string
11 for (int currentIndex = 0; currentIndex < stringLength; ++currentIndex) {
12 char currentChar = s.charAt(currentIndex);
13 // Calculate the mirror character (a↔z, b↔y, c↔x, etc.)
14 char mirrorChar = (char) ('a' + 'z' - currentChar);
15
16 // Check if we have an unmatched mirror character waiting
17 if (unmatchedCharIndices.containsKey(mirrorChar)) {
18 // Get the list of indices for the mirror character
19 List<Integer> mirrorCharIndices = unmatchedCharIndices.get(mirrorChar);
20
21 // Match with the most recent occurrence (last in the list)
22 int matchedIndex = mirrorCharIndices.remove(mirrorCharIndices.size() - 1);
23
24 // Clean up: remove the key if no more indices remain
25 if (mirrorCharIndices.isEmpty()) {
26 unmatchedCharIndices.remove(mirrorChar);
27 }
28
29 // Add the distance between matched pairs to the score
30 totalScore += currentIndex - matchedIndex;
31 } else {
32 // No mirror character available, store current character for future matching
33 unmatchedCharIndices.computeIfAbsent(currentChar, key -> new ArrayList<>()).add(currentIndex);
34 }
35 }
36
37 return totalScore;
38 }
39}
40
1class Solution {
2public:
3 long long calculateScore(string s) {
4 // Map to store character positions: key is character, value is list of indices
5 unordered_map<char, vector<int>> charPositions;
6 int n = s.length();
7 long long totalScore = 0;
8
9 // Iterate through each character in the string
10 for (int i = 0; i < n; ++i) {
11 char currentChar = s[i];
12 // Calculate the complementary character (mirror around middle of alphabet)
13 // For example: 'a' pairs with 'z', 'b' pairs with 'y', etc.
14 char complementChar = 'a' + 'z' - currentChar;
15
16 // Check if the complement character has unmatched positions
17 if (charPositions.contains(complementChar)) {
18 // Get the list of positions for the complement character
19 vector<int>& complementPositions = charPositions[complementChar];
20
21 // Match with the most recent position (last element)
22 int matchedIndex = complementPositions.back();
23 complementPositions.pop_back();
24
25 // Remove the entry if no more positions left
26 if (complementPositions.empty()) {
27 charPositions.erase(complementChar);
28 }
29
30 // Add the distance between current position and matched position to score
31 totalScore += i - matchedIndex;
32 } else {
33 // No complement found, store current character's position for future matching
34 charPositions[currentChar].push_back(i);
35 }
36 }
37
38 return totalScore;
39 }
40};
41
1/**
2 * Calculates a score based on pairing characters with their "mirror" characters in the alphabet.
3 * For each character, its mirror is the character that when added together equals 'a' + 'z'.
4 * For example, 'a' pairs with 'z', 'b' pairs with 'y', etc.
5 *
6 * @param s - The input string to process
7 * @returns The total score calculated from the distances between paired characters
8 */
9function calculateScore(s: string): number {
10 // Map to store character positions waiting to be paired
11 // Key: character, Value: array of indices where this character appears
12 const characterPositionsMap: Map<string, number[]> = new Map();
13
14 const stringLength = s.length;
15 let totalScore = 0;
16
17 // Process each character in the string
18 for (let currentIndex = 0; currentIndex < stringLength; currentIndex++) {
19 const currentChar = s[currentIndex];
20
21 // Calculate the mirror character (e.g., 'a' -> 'z', 'b' -> 'y')
22 // This works because 'a' + 'z' = 97 + 122 = 219 (constant sum)
23 const mirrorChar = String.fromCharCode(
24 'a'.charCodeAt(0) + 'z'.charCodeAt(0) - currentChar.charCodeAt(0)
25 );
26
27 // Check if we can pair current character with a previously seen mirror character
28 if (characterPositionsMap.has(mirrorChar)) {
29 // Get the list of positions for the mirror character
30 const mirrorPositions = characterPositionsMap.get(mirrorChar)!;
31
32 // Pair with the most recent occurrence (pop from the end)
33 const pairedIndex = mirrorPositions.pop()!;
34
35 // Clean up the map if no more positions left for this character
36 if (mirrorPositions.length === 0) {
37 characterPositionsMap.delete(mirrorChar);
38 }
39
40 // Add the distance between paired characters to the score
41 totalScore += currentIndex - pairedIndex;
42 } else {
43 // No mirror character available to pair, store current character position
44 if (!characterPositionsMap.has(currentChar)) {
45 characterPositionsMap.set(currentChar, []);
46 }
47 characterPositionsMap.get(currentChar)!.push(currentIndex);
48 }
49 }
50
51 return totalScore;
52}
53
Time and Space Complexity
Time Complexity: O(n)
The algorithm iterates through the string once using a single for loop that processes each character at index i
. For each character:
- Computing the mirror character
y
usingchr(ord("a") + ord("z") - ord(x))
takesO(1)
time - Checking if
d[y]
exists takesO(1)
time (dictionary lookup) - Either popping from the list (
d[y].pop()
) or appending to the list (d[x].append(i)
) takesO(1)
amortized time - The arithmetic operation
i - j
takesO(1)
time
Since we perform O(1)
operations for each of the n
characters, the total time complexity is O(n)
.
Space Complexity: O(n)
The space is primarily used by the dictionary d
which stores lists of indices:
- In the worst case, when no matching pairs are found (e.g., all characters are the same), the dictionary would store all
n
indices - The dictionary has at most 26 keys (one for each letter), but the total number of indices stored across all lists can be up to
n
- Additional variables like
ans
,i
,x
,y
, andj
useO(1)
space
Therefore, the space complexity is O(n)
where n
is the length of the string s
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect Mirror Character Calculation
The Problem: A common mistake is incorrectly calculating the mirror character. Developers might try formulas like:
mirror = 'z' - current_char + 'a'
(wrong order)mirror = chr(25 - (ord(current_char) - ord('a')) + ord('a'))
(overcomplicated but could work)mirror = chr(ord('z') - ord(current_char))
(missing the 'a' offset)
Why It Happens:
The mirror relationship requires mapping the first letter to the last, second to second-last, etc. The correct formula chr(ord('a') + ord('z') - ord(current_char))
works because:
- For 'a': ord('a') + ord('z') - ord('a') = ord('z')
- For 'z': ord('a') + ord('z') - ord('z') = ord('a')
Solution: Always verify the mirror calculation with test cases:
def get_mirror(char):
return chr(ord('a') + ord('z') - ord(char))
# Test the formula
assert get_mirror('a') == 'z'
assert get_mirror('b') == 'y'
assert get_mirror('z') == 'a'
assert get_mirror('m') == 'n'
Pitfall 2: Using the Wrong End of the List
The Problem:
Using char_indices[mirror_char].pop(0)
instead of char_indices[mirror_char].pop()
would give us the leftmost (farthest) unmarked position instead of the rightmost (closest) one.
Why It Happens: The problem explicitly states we need the "closest unmarked index j where j < i". Since we're processing left to right and storing indices in order, the last element in the list is the closest one to our current position.
Solution:
Always use pop()
without arguments (or pop(-1)
) to get the most recent/closest index:
# Correct: gets the closest (rightmost) unmarked position mirror_index = char_indices[mirror_char].pop() # Wrong: gets the farthest (leftmost) unmarked position # mirror_index = char_indices[mirror_char].pop(0) # Don't do this!
Pitfall 3: Attempting to Track Already Matched Characters
The Problem: Some might try to add extra logic to track which indices have been "marked" or paired:
# Unnecessary complexity
marked = set()
for i, char in enumerate(s):
if i in marked:
continue
# ... rest of logic
Why It Happens: The problem statement mentions "marking" indices, which might lead to overthinking the implementation.
Solution: The beauty of this approach is that "marking" happens implicitly through removal from the dictionary. Once a character index is popped from the list, it's effectively marked as used. No additional tracking is needed.
What's the relationship between a tree and a graph?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
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
Want a Structured Path to Master System Design Too? Don’t Miss This!