A two-pass solution for computing the shortest distance from each index to the nearest occurrence of a target character.
Problem Restatement
We are given a string s and a character c.
The character c appears at least once in s.
For every index i, we need to find the shortest distance from i to any index where s[index] == c.
The distance between two indices i and j is:
abs(i - j)Return an array answer, where:
answer[i] = distance from i to the closest cThe official constraints say s has length at most 10^4, all characters are lowercase English letters, and c occurs at least once in s.
Input and Output
| Item | Meaning |
|---|---|
| Input | A string s and a character c |
| Output | An integer array of length len(s) |
| Distance | abs(i - j) between two indices |
| Guarantee | c appears at least once in s |
Examples
Example 1:
s = "loveleetcode"
c = "e"The character "e" appears at indices:
3, 5, 6, 11The closest distances are:
[3, 2, 1, 0, 1, 0, 0, 1, 2, 2, 1, 0]So the answer is:
[3, 2, 1, 0, 1, 0, 0, 1, 2, 2, 1, 0]Example 2:
s = "aaab"
c = "b"The only "b" is at index 3.
So the answer is:
[3, 2, 1, 0]First Thought: Check Every Occurrence
One simple approach is:
- Store all positions where
s[i] == c. - For every index
i, compare it with every stored position. - Keep the minimum absolute distance.
This works, but it can be too slow.
If the string has length n, and many characters are equal to c, this can approach:
O(n^2)We can do better by scanning the string twice.
Key Insight
For any index i, the closest occurrence of c is either:
- The closest
con the left. - The closest
con the right.
So we can compute both directions separately.
First pass, left to right:
distance to closest c on the leftSecond pass, right to left:
distance to closest c on the rightThe answer for each index is the smaller of those two distances.
Algorithm
Let:
n = len(s)
answer = [n] * nUse n as a large initial distance.
First pass:
- Set
prev = -n. - Scan from left to right.
- Whenever
s[i] == c, setprev = i. - Store:
answer[i] = i - prevSecond pass:
- Set
next_pos = 2 * n. - Scan from right to left.
- Whenever
s[i] == c, setnext_pos = i. - Update:
answer[i] = min(answer[i], next_pos - i)Return answer.
Correctness
During the left-to-right pass, prev always stores the nearest occurrence of c at or before the current index. Therefore, i - prev is the distance from i to the closest c on its left side.
During the right-to-left pass, next_pos always stores the nearest occurrence of c at or after the current index. Therefore, next_pos - i is the distance from i to the closest c on its right side.
For any index, the closest occurrence of c must be either on the left or on the right. The algorithm takes the minimum of these two distances, so answer[i] becomes the shortest distance from index i to any occurrence of c.
Thus the returned array is correct.
Complexity
Let n = len(s).
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | We scan the string twice |
| Space | O(1) extra | Apart from the output array, only two variables are used |
Implementation
class Solution:
def shortestToChar(self, s: str, c: str) -> list[int]:
n = len(s)
answer = [n] * n
prev = -n
for i in range(n):
if s[i] == c:
prev = i
answer[i] = i - prev
next_pos = 2 * n
for i in range(n - 1, -1, -1):
if s[i] == c:
next_pos = i
answer[i] = min(answer[i], next_pos - i)
return answerCode Explanation
We initialize the answer with a large value:
answer = [n] * nThe first pass records distance to the nearest c on the left:
prev = -nBefore any c has been seen, prev is far outside the string, so the distance is large.
Whenever we find c, we update prev:
if s[i] == c:
prev = iThen we store the left-side distance:
answer[i] = i - prevThe second pass records distance to the nearest c on the right:
next_pos = 2 * nWhenever we find c, we update next_pos:
if s[i] == c:
next_pos = iThen we keep the smaller distance:
answer[i] = min(answer[i], next_pos - i)Testing
def run_tests():
s = Solution()
assert s.shortestToChar("loveleetcode", "e") == [
3, 2, 1, 0, 1, 0, 0, 1, 2, 2, 1, 0
]
assert s.shortestToChar("aaab", "b") == [3, 2, 1, 0]
assert s.shortestToChar("a", "a") == [0]
assert s.shortestToChar("abcde", "c") == [2, 1, 0, 1, 2]
assert s.shortestToChar("aaaaa", "a") == [0, 0, 0, 0, 0]
print("all tests passed")
run_tests()| Test | Why |
|---|---|
"loveleetcode", "e" | Official example with several target positions |
"aaab", "b" | Target appears only at the end |
"a", "a" | Minimum input |
"abcde", "c" | Target appears in the middle |
"aaaaa", "a" | Every index is already the target |