186 lines
9.3 KiB
YAML
186 lines
9.3 KiB
YAML
title: Check Distances Between Same Letters
|
|
slug: check-distances-between-same-letters
|
|
difficulty: easy
|
|
leetcode_id: 2399
|
|
leetcode_url: https://leetcode.com/problems/check-distances-between-same-letters/
|
|
categories:
|
|
- arrays
|
|
- strings
|
|
- hash-tables
|
|
patterns:
|
|
- slug: two-pointers
|
|
is_optimal: true
|
|
|
|
function_signature: "def check_distances(s: str, distance: list[int]) -> bool:"
|
|
|
|
test_cases:
|
|
visible:
|
|
- input: { s: "abaccb", distance: [1, 3, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }
|
|
expected: true
|
|
- input: { s: "aa", distance: [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }
|
|
expected: false
|
|
hidden:
|
|
- input: { s: "zz", distance: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }
|
|
expected: true
|
|
- input: { s: "abba", distance: [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }
|
|
expected: true
|
|
- input: { s: "aabb", distance: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }
|
|
expected: true
|
|
|
|
description: |
|
|
You are given a **0-indexed** string `s` consisting of only lowercase English letters, where each letter in `s` appears **exactly twice**. You are also given a **0-indexed** integer array `distance` of length `26`.
|
|
|
|
Each letter in the alphabet is numbered from `0` to `25` (i.e. `'a' -> 0`, `'b' -> 1`, `'c' -> 2`, ... , `'z' -> 25`).
|
|
|
|
In a **well-spaced** string, the number of letters between the two occurrences of the i<sup>th</sup> letter is `distance[i]`. If the i<sup>th</sup> letter does not appear in `s`, then `distance[i]` can be **ignored**.
|
|
|
|
Return `true` *if* `s` *is a **well-spaced** string, otherwise return* `false`.
|
|
|
|
constraints: |
|
|
- `2 <= s.length <= 52`
|
|
- `s` consists only of lowercase English letters
|
|
- Each letter appears in `s` exactly twice
|
|
- `distance.length == 26`
|
|
- `0 <= distance[i] <= 50`
|
|
|
|
examples:
|
|
- input: 's = "abaccb", distance = [1,3,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]'
|
|
output: "true"
|
|
explanation: "'a' appears at indices 0 and 2 (distance 1). 'b' appears at indices 1 and 5 (distance 3). 'c' appears at indices 3 and 4 (distance 0). All letters satisfy their required distances."
|
|
- input: 's = "aa", distance = [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]'
|
|
output: "false"
|
|
explanation: "'a' appears at indices 0 and 1, so there are zero letters between them. Since distance[0] = 1, the string is not well-spaced."
|
|
|
|
explanation:
|
|
intuition: |
|
|
Think of this problem as a **verification task**: for each letter in the string, we need to confirm that the gap between its two occurrences matches the expected distance.
|
|
|
|
Imagine walking through the string character by character. The first time you encounter a letter, you note its position. The second time you see that same letter, you calculate how many characters are *between* the two positions and check if it matches the required distance.
|
|
|
|
The key insight is that the "distance" is the number of characters *between* the two occurrences, not the difference in indices. If a letter appears at indices `i` and `j` (where `j > i`), the number of letters between them is `j - i - 1`.
|
|
|
|
For example, if `'a'` appears at index 0 and index 2, there's exactly 1 character between them (at index 1), so the distance is `2 - 0 - 1 = 1`.
|
|
|
|
approach: |
|
|
We solve this using a **single pass with position tracking**:
|
|
|
|
**Step 1: Create a position tracker**
|
|
|
|
- Use an array or dictionary to store the first occurrence index of each letter
|
|
- Initialise all positions to `-1` (indicating "not yet seen")
|
|
|
|
|
|
|
|
**Step 2: Iterate through the string**
|
|
|
|
- For each character at index `i`:
|
|
- Get the letter's alphabet index: `char_index = ord(c) - ord('a')`
|
|
- If this is the first occurrence (position is `-1`), store the current index
|
|
- If this is the second occurrence, calculate the actual distance: `i - first_index - 1`
|
|
- Compare with the expected distance from `distance[char_index]`
|
|
- If they don't match, return `false` immediately
|
|
|
|
|
|
|
|
**Step 3: Return the result**
|
|
|
|
- If we complete the loop without finding any mismatches, return `true`
|
|
|
|
|
|
|
|
This approach works because we process each character exactly once, and by the time we see the second occurrence, we have all the information needed to verify the distance constraint.
|
|
|
|
common_pitfalls:
|
|
- title: Off-by-One Error in Distance Calculation
|
|
description: |
|
|
A common mistake is calculating the distance as `j - i` instead of `j - i - 1`.
|
|
|
|
The problem asks for the number of letters *between* the two occurrences, not the difference in indices. If `'a'` is at index 0 and index 2, the indices differ by 2, but there's only 1 letter between them (at index 1).
|
|
|
|
Always remember: **distance = second_index - first_index - 1**
|
|
wrong_approach: "Using j - i as the distance"
|
|
correct_approach: "Using j - i - 1 to count characters between positions"
|
|
|
|
- title: Forgetting to Handle the Alphabet Index
|
|
description: |
|
|
Each letter maps to an index in the `distance` array: `'a' -> 0`, `'b' -> 1`, etc.
|
|
|
|
To convert a character to its alphabet index, use: `ord(c) - ord('a')` in Python or `c.charCodeAt(0) - 97` in JavaScript.
|
|
|
|
Forgetting this mapping will cause incorrect lookups in the distance array.
|
|
wrong_approach: "Using the character directly as an index"
|
|
correct_approach: "Converting character to 0-25 index using ASCII arithmetic"
|
|
|
|
- title: Checking All 26 Letters
|
|
description: |
|
|
Don't iterate through all 26 letters checking if they exist in the string. The problem states that unused letters in `distance` can be ignored.
|
|
|
|
It's more efficient to iterate through the string once and only check letters that actually appear. This gives O(n) time where n is the string length, rather than potentially checking letters that don't exist.
|
|
wrong_approach: "Iterating through alphabet and searching for each letter"
|
|
correct_approach: "Iterating through string and tracking first occurrences"
|
|
|
|
key_takeaways:
|
|
- "**Position tracking pattern**: Store first occurrence positions to compute distances when second occurrence is found"
|
|
- "**Early termination**: Return `false` immediately when a mismatch is found — no need to check remaining characters"
|
|
- "**ASCII arithmetic**: Converting characters to array indices with `ord(c) - ord('a')` is a common technique for alphabet-indexed problems"
|
|
- "**Verification vs construction**: This is a verification problem — we're checking a property, not building something"
|
|
|
|
time_complexity: "O(n). We iterate through the string once, where `n` is the length of `s`. Each character is processed in O(1) time."
|
|
space_complexity: "O(1). We use an array of size 26 to track first occurrences, which is constant regardless of input size."
|
|
|
|
solutions:
|
|
- approach_name: Single Pass with Position Tracking
|
|
is_optimal: true
|
|
code: |
|
|
def check_distances(s: str, distance: list[int]) -> bool:
|
|
# Track first occurrence index of each letter (-1 means not seen)
|
|
first_occurrence = [-1] * 26
|
|
|
|
for i, c in enumerate(s):
|
|
# Convert character to alphabet index (a=0, b=1, ..., z=25)
|
|
char_index = ord(c) - ord('a')
|
|
|
|
if first_occurrence[char_index] == -1:
|
|
# First time seeing this letter, store its position
|
|
first_occurrence[char_index] = i
|
|
else:
|
|
# Second occurrence: check if distance matches
|
|
actual_distance = i - first_occurrence[char_index] - 1
|
|
if actual_distance != distance[char_index]:
|
|
return False
|
|
|
|
# All letters satisfied their distance requirements
|
|
return True
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Single pass through the string.
|
|
|
|
**Space Complexity:** O(1) — Fixed array of 26 elements for tracking.
|
|
|
|
We iterate through each character, storing first occurrence positions and verifying distances on second occurrences. Early termination on mismatch makes this efficient for invalid strings.
|
|
|
|
- approach_name: Hash Map Approach
|
|
is_optimal: false
|
|
code: |
|
|
def check_distances(s: str, distance: list[int]) -> bool:
|
|
# Dictionary to store first occurrence index
|
|
first_seen = {}
|
|
|
|
for i, c in enumerate(s):
|
|
if c not in first_seen:
|
|
# First occurrence: record position
|
|
first_seen[c] = i
|
|
else:
|
|
# Second occurrence: verify distance
|
|
actual_distance = i - first_seen[c] - 1
|
|
expected_distance = distance[ord(c) - ord('a')]
|
|
if actual_distance != expected_distance:
|
|
return False
|
|
|
|
return True
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Single pass through the string.
|
|
|
|
**Space Complexity:** O(k) — Where k is the number of unique letters (at most 26).
|
|
|
|
This approach uses a dictionary instead of a fixed-size array. While asymptotically equivalent, the array approach has slightly better constant factors due to direct indexing. The dictionary approach is more intuitive and works well when the character set is unknown or sparse.
|