2399. Check Distances Between Same Letters
Problem Description
You are given a string s
where every lowercase letter that appears in the string appears exactly twice. You are also given an array distance
of length 26, where each position corresponds to a letter of the alphabet ('a' at index 0, 'b' at index 1, and so on).
A string is considered "well-spaced" if, for each letter that appears in the string, the number of letters between its two occurrences equals the value specified in the distance
array for that letter.
For example, if the letter 'a' appears at positions 1 and 4 in the string, there are 2 letters between them (positions 2 and 3). This would be valid only if distance[0] = 2
.
Your task is to determine if the given string s
is well-spaced according to the distance
array. Return true
if it is well-spaced, otherwise return false
.
Key points to understand:
- Each letter in
s
appears exactly twice - The distance between two occurrences of a letter is the count of letters between them (not including the letter itself)
- If a letter doesn't appear in
s
, its corresponding value indistance
can be ignored - Letters are mapped to indices: 'a' → 0, 'b' → 1, ..., 'z' → 25
Intuition
Since each letter appears exactly twice in the string, we need to track where we first see each letter and then verify the distance when we encounter it the second time.
The key insight is that we don't need to store both positions for each letter. When we see a letter for the first time, we just record its position. When we see it again, we can immediately calculate the distance between the two occurrences and check if it matches the required distance.
Think of it like marking checkpoints: as we traverse the string from left to right, we place a marker when we first see a letter. When we see that same letter again, we measure how far we've traveled since the marker and verify if it matches our expected distance.
The distance between two occurrences at positions i
and j
(where j > i
) is j - i - 1
. For example, if a letter appears at position 2 and position 5, the distance is 5 - 2 - 1 = 2
(there are 2 letters between them).
This leads to a single-pass solution: iterate through the string once, and for each character:
- If we haven't seen it before, record its position
- If we have seen it before, calculate the distance and verify it matches the expected value
- If any distance doesn't match, we can immediately return
false
- If we complete the traversal without issues, the string is well-spaced
Using a hash table or array to store the first occurrence positions makes this checking process efficient with O(n)
time complexity where n
is the length of the string.
Solution Approach
The solution uses a hash table (or dictionary) to track the first occurrence of each letter and validates the distance when encountering the second occurrence.
Implementation walkthrough:
-
Initialize a hash table
d
usingdefaultdict(int)
to store the position of the first occurrence of each letter. The default value of 0 helps us identify if we've seen a letter before. -
Iterate through the string using
enumerate(map(ord, s), 1)
:- We use
enumerate
starting from 1 (not 0) to make position tracking easier map(ord, s)
converts each character to its ASCII value for easier calculation- For each character at position
i
, we get its ASCII valuec
- We use
-
Convert character to alphabet index: Calculate
j = c - ord("a")
to get the 0-based index for the letter (0 for 'a', 1 for 'b', etc.) -
Check if this is the second occurrence:
- If
d[j]
is non-zero, we've seen this letter before - Calculate the distance:
i - d[j] - 1
i
is the current position (1-indexed)d[j]
is the first occurrence position (1-indexed)- Subtract 1 to get the number of letters between them
- Compare with
distance[j]
- if they don't match, returnFalse
- If
-
Record the position: If this is the first occurrence (or after checking the second), set
d[j] = i
-
Return the result: If we complete the iteration without finding any mismatches, return
True
Example walkthrough:
For string "abaccb"
with distance = [2, 3, 0, ...]
:
- Position 1: 'a' → Store
d[0] = 1
- Position 2: 'b' → Store
d[1] = 2
- Position 3: 'a' → Check:
3 - 1 - 1 = 1 ≠ 2
, returnFalse
The algorithm runs in O(n)
time where n
is the length of the string, with O(1)
space since we store at most 26 letters.
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 to understand how it works.
Example Input:
- String
s = "abccba"
- Distance array
distance = [4, 2, 1, ...]
(showing first 3 values for letters a, b, c)
Step-by-step execution:
Starting with an empty hash table d = {}
to track first occurrences.
Position 1 (index 0): Character 'a'
- Convert 'a' to index:
j = ord('a') - ord('a') = 0
- Check
d[0]
: It's 0 (not seen before) - Store first occurrence:
d[0] = 1
- Hash table:
d = {0: 1}
Position 2 (index 1): Character 'b'
- Convert 'b' to index:
j = ord('b') - ord('a') = 1
- Check
d[1]
: It's 0 (not seen before) - Store first occurrence:
d[1] = 2
- Hash table:
d = {0: 1, 1: 2}
Position 3 (index 2): Character 'c'
- Convert 'c' to index:
j = ord('c') - ord('a') = 2
- Check
d[2]
: It's 0 (not seen before) - Store first occurrence:
d[2] = 3
- Hash table:
d = {0: 1, 1: 2, 2: 3}
Position 4 (index 3): Character 'c' (second occurrence)
- Convert 'c' to index:
j = ord('c') - ord('a') = 2
- Check
d[2]
: It's 3 (seen before at position 3) - Calculate distance:
4 - 3 - 1 = 0
- Compare with
distance[2] = 1
:0 ≠ 1
- Return False - The string is not well-spaced!
The algorithm detected that the two 'c' characters are adjacent (0 letters between them), but the distance array requires 1 letter between them. Therefore, the string "abccba" is not well-spaced according to the given distance requirements.
Alternative scenario: If the string were "abcbca"
with the same distance array:
- 'a' appears at positions 1 and 6: distance =
6 - 1 - 1 = 4
✓ (matches distance[0]) - 'b' appears at positions 2 and 4: distance =
4 - 2 - 1 = 1
✗ (doesn't match distance[1] = 2) - Result: False
This demonstrates how the algorithm efficiently checks distances in a single pass through the string.
Solution Implementation
1from collections import defaultdict
2from typing import List
3
4class Solution:
5 def checkDistances(self, s: str, distance: List[int]) -> bool:
6 # Dictionary to store the first occurrence position (1-indexed) of each character
7 first_occurrence = defaultdict(int)
8
9 # Iterate through the string with 1-indexed positions
10 for position, char in enumerate(s, 1):
11 # Convert character to its alphabetical index (0 for 'a', 1 for 'b', etc.)
12 char_index = ord(char) - ord('a')
13
14 # If this character has appeared before
15 if first_occurrence[char_index]:
16 # Calculate the actual distance between two occurrences
17 # Distance = current_position - first_position - 1
18 actual_distance = position - first_occurrence[char_index] - 1
19
20 # Check if actual distance matches the expected distance
21 if actual_distance != distance[char_index]:
22 return False
23 else:
24 # Store the first occurrence position of this character
25 first_occurrence[char_index] = position
26
27 return True
28
1class Solution {
2 public boolean checkDistances(String s, int[] distance) {
3 // Array to store the first occurrence position (1-indexed) of each letter
4 int[] firstOccurrencePosition = new int[26];
5
6 // Iterate through each character in the string
7 for (int i = 1; i <= s.length(); ++i) {
8 // Get the letter index (0-25 for 'a'-'z')
9 int letterIndex = s.charAt(i - 1) - 'a';
10
11 // If this letter has appeared before
12 if (firstOccurrencePosition[letterIndex] > 0) {
13 // Calculate the distance between two occurrences
14 // Distance = current position - first position - 1
15 int actualDistance = i - firstOccurrencePosition[letterIndex] - 1;
16
17 // Check if the actual distance matches the required distance
18 if (actualDistance != distance[letterIndex]) {
19 return false;
20 }
21 }
22
23 // Store the current position as the first occurrence (1-indexed)
24 firstOccurrencePosition[letterIndex] = i;
25 }
26
27 // All distances are valid
28 return true;
29 }
30}
31
1class Solution {
2public:
3 bool checkDistances(string s, vector<int>& distance) {
4 // Array to store the first occurrence position (1-indexed) of each letter
5 // Initialize all elements to 0 (indicating letter not yet seen)
6 int firstPosition[26]{};
7
8 // Iterate through the string with 1-indexed position
9 for (int currentPos = 1; currentPos <= s.size(); ++currentPos) {
10 // Get the character index (0-25 for 'a'-'z')
11 int charIndex = s[currentPos - 1] - 'a';
12
13 // If we've seen this character before
14 if (firstPosition[charIndex] > 0) {
15 // Calculate the distance between two occurrences
16 // Distance = current position - first position - 1
17 int actualDistance = currentPos - firstPosition[charIndex] - 1;
18
19 // Check if the actual distance matches the required distance
20 if (actualDistance != distance[charIndex]) {
21 return false;
22 }
23 }
24
25 // Store the first occurrence position of this character
26 firstPosition[charIndex] = currentPos;
27 }
28
29 return true;
30 }
31};
32
1/**
2 * Checks if the distance between each pair of same letters in string s
3 * matches the expected distance array
4 * @param s - Input string containing lowercase letters
5 * @param distance - Array where distance[i] represents the expected distance
6 * between two occurrences of the i-th letter of the alphabet
7 * @returns true if all letter distances match the expected values, false otherwise
8 */
9function checkDistances(s: string, distance: number[]): boolean {
10 // Array to store the first occurrence position (1-indexed) of each letter
11 // Index represents letter (0='a', 1='b', ..., 25='z')
12 const firstOccurrencePosition: number[] = Array(26).fill(0);
13
14 // Get the length of the input string
15 const stringLength: number = s.length;
16
17 // Iterate through each character in the string (using 1-based indexing)
18 for (let currentPosition: number = 1; currentPosition <= stringLength; ++currentPosition) {
19 // Convert current character to alphabet index (0-25)
20 // 'a' has ASCII code 97, so subtract 97 to get 0-based index
21 const alphabetIndex: number = s.charCodeAt(currentPosition - 1) - 97;
22
23 // If this letter has been seen before
24 if (firstOccurrencePosition[alphabetIndex]) {
25 // Calculate actual distance between two occurrences
26 // Distance = current position - first position - 1
27 const actualDistance: number = currentPosition - firstOccurrencePosition[alphabetIndex] - 1;
28
29 // Check if actual distance matches expected distance
30 if (actualDistance !== distance[alphabetIndex]) {
31 return false;
32 }
33 }
34
35 // Store the current position as the first occurrence for this letter
36 firstOccurrencePosition[alphabetIndex] = currentPosition;
37 }
38
39 // All distances match the expected values
40 return true;
41}
42
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 exactly once using the enumerate function, and for each character, it performs constant-time operations: calculating the character index j
, checking if the character has been seen before, comparing distances, and updating the dictionary.
The space complexity is O(|Σ|)
, where Σ
is the character set (lowercase English letters in this case). The dictionary d
can store at most 26 entries, one for each lowercase letter from 'a' to 'z', regardless of the input string length. Since the size of the alphabet is constant (26), this can also be expressed as O(1)
space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Distance Calculation (Off-by-One Error)
The most common mistake is incorrectly calculating the distance between two occurrences of a letter. Many developers confuse:
- The number of positions between two indices
- The difference between two indices
- Whether to use 0-indexed or 1-indexed positions
Incorrect implementations:
# Pitfall 1: Forgetting to subtract 1
actual_distance = position2 - position1 # Wrong! This includes both positions
# Pitfall 2: Using 0-indexed positions incorrectly
for i, char in enumerate(s): # i is 0-indexed
if first_occurrence[char_index] is not None:
actual_distance = i - first_occurrence[char_index] - 1 # Wrong calculation!
Why this happens: When positions are 0-indexed, if 'a' appears at positions 0 and 3, the difference is 3, but there are only 2 letters between them (positions 1 and 2).
Solution: Use consistent indexing throughout. The provided solution uses 1-indexed positions with enumerate(s, 1)
, making the calculation intuitive: distance = current_pos - first_pos - 1
.
2. Using Wrong Default Values in Dictionary
Incorrect implementation:
first_occurrence = {} # or dict()
for position, char in enumerate(s, 1):
char_index = ord(char) - ord('a')
if char_index in first_occurrence: # Need to check existence
# calculate distance
else:
first_occurrence[char_index] = position
Problem: Using a regular dictionary requires explicit checking with in
operator. If you use if first_occurrence[char_index]:
with a regular dict, it will raise a KeyError.
Solution: Use defaultdict(int)
which returns 0 for missing keys. Since positions start from 1, a value of 0 clearly indicates "not seen yet", making the code cleaner and avoiding the need for existence checks.
3. Confusing Character Index with Position
Incorrect implementation:
for i, char in enumerate(s):
if first_occurrence[char]: # Using character as key instead of index
actual_distance = i - first_occurrence[char] - 1
Problem: Mixing up the character itself with its alphabetical index leads to incorrect dictionary keys and wrong distance array lookups.
Solution: Always convert the character to its alphabetical index: char_index = ord(char) - ord('a')
and use this consistently for both dictionary access and distance array indexing.
Which of the following shows the order of node visit in a Breadth-first Search?
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!