821. Shortest Distance to a Character
Problem Description
You are given a string s
and a character c
that appears somewhere in the string. Your task is to find the shortest distance from each position in the string to the nearest occurrence of character c
.
For each index i
in the string, you need to calculate the minimum distance to any position where character c
appears. The distance between two indices is simply the absolute difference between them: abs(i - j)
.
The output should be an array of integers where:
- The array has the same length as the input string
s
- Each element at index
i
represents the shortest distance from positioni
to the nearest characterc
For example, if s = "loveleetcode"
and c = 'e'
:
- The character
'e'
appears at positions 3, 5, 6, and 11 - For index 0 (
'l'
), the nearest'e'
is at position 3, so the distance is3
- For index 4 (
'l'
), the nearest'e'
is at position 3 or 5, both giving distance1
- For index 6 (
'e'
), the distance is0
since it is itself an'e'
The solution uses a two-pass approach:
- First pass (left to right): Track the most recent position of character
c
seen so far and calculate distances based on that - Second pass (right to left): Track the nearest future position of character
c
and update distances if a closer occurrence is found
This ensures that each position considers both the nearest c
to its left and the nearest c
to its right, giving the overall minimum distance.
Intuition
The key insight is that for any position in the string, the nearest character c
can only be in one of two directions: either to the left or to the right. This means we don't need to check every possible position - we just need to know the closest c
on each side.
Think of it like standing on a street looking for the nearest bus stop. You only need to check in two directions - left and right - and pick whichever is closer. You don't need to check every single point on the street.
This observation leads us to a two-pass strategy:
-
Why two passes? In a single pass from left to right, we can only know about the
c
characters we've already seen (those to our left). We have no information about anyc
characters that might be coming up ahead. Similarly, a single pass from right to left only tells us about future occurrences. -
Tracking positions with infinity: We initialize
pre = -inf
for the left-to-right pass because initially, we haven't seen anyc
yet. Using negative infinity ensures thati - pre
gives a very large distance, which is appropriate since there's noc
to the left. Similarly,suf = inf
for the right-to-left pass represents that initially there's no knownc
to the right. -
The minimum operation: After both passes, each position has been evaluated against both its nearest left
c
and nearest rightc
. Taking the minimum ensures we get the absolute shortest distance. If a position only hasc
on one side, that side will naturally give the smaller distance. -
Why this works: Every position between two
c
characters will get its correct distance because it considers both neighbors. Positions at the edges (before the firstc
or after the lastc
) will also get correct distances because they only havec
on one side, and the infinity on the other side ensures the correct side is chosen.
The beauty of this approach is its simplicity - rather than doing complex searches for each position, we leverage the linear nature of the string to solve the problem in just two straightforward passes.
Learn more about Two Pointers patterns.
Solution Approach
Let's walk through the implementation step by step:
Initialization:
n = len(s)
ans = [n] * n
pre = -inf
- We create an answer array
ans
initialized withn
(the length of string). This serves as an upper bound for distances since no distance can be greater than the string length. pre
tracks the position of the most recentc
encountered during the left-to-right pass, initialized to negative infinity.
First Pass (Left to Right):
for i, ch in enumerate(s):
if ch == c:
pre = i
ans[i] = min(ans[i], i - pre)
- We iterate through each character from left to right.
- When we encounter the target character
c
, we updatepre
to the current positioni
. - For every position, we calculate the distance to the most recent
c
on the left:i - pre
. - We use
min(ans[i], i - pre)
to keep the smaller distance. Initially,ans[i]
isn
, so in this first pass, we're effectively storingi - pre
. - If no
c
has been seen yet,i - pre
will be very large (sincepre = -inf
), which is correct.
Second Pass (Right to Left):
suf = inf
for i in range(n - 1, -1, -1):
if s[i] == c:
suf = i
ans[i] = min(ans[i], suf - i)
- We iterate through the string backwards.
suf
tracks the position of the nearestc
to the right, initialized to positive infinity.- When we encounter
c
, we updatesuf
to that position. - For each position, we calculate the distance to the nearest
c
on the right:suf - i
. - We update
ans[i]
with the minimum of its current value (distance to leftc
) and the distance to rightc
.
Why This Works:
Consider a position i
in the string:
- After the first pass,
ans[i]
contains the distance to the nearestc
on the left (or a very large value if there's noc
on the left). - After the second pass,
ans[i]
is updated to be the minimum of:- Distance to nearest
c
on the left (from first pass) - Distance to nearest
c
on the right (from second pass)
- Distance to nearest
This guarantees that ans[i]
contains the shortest distance to any c
in the string.
Time and Space Complexity:
- Time:
O(n)
wheren
is the length of the string. We make exactly two passes through the string. - Space:
O(n)
for the answer array (orO(1)
extra space if we don't count the output array).
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 algorithm with s = "abaacab"
and c = 'a'
.
Initial Setup:
- String:
"abaacab"
(length = 7) - Target character:
'a'
(appears at indices 0, 2, 3, 5) - Initialize
ans = [7, 7, 7, 7, 7, 7, 7]
- Set
pre = -inf
First Pass (Left to Right):
i | s[i] | Is 'a'? | pre | i - pre | ans[i] | ans array |
---|---|---|---|---|---|---|
0 | 'a' | Yes | 0 (update) | 0 | 0 | [0, 7, 7, 7, 7, 7, 7] |
1 | 'b' | No | 0 | 1 | 1 | [0, 1, 7, 7, 7, 7, 7] |
2 | 'a' | Yes | 2 (update) | 0 | 0 | [0, 1, 0, 7, 7, 7, 7] |
3 | 'a' | Yes | 3 (update) | 0 | 0 | [0, 1, 0, 0, 7, 7, 7] |
4 | 'c' | No | 3 | 1 | 1 | [0, 1, 0, 0, 1, 7, 7] |
5 | 'a' | Yes | 5 (update) | 0 | 0 | [0, 1, 0, 0, 1, 0, 7] |
6 | 'b' | No | 5 | 1 | 1 | [0, 1, 0, 0, 1, 0, 1] |
After first pass: ans = [0, 1, 0, 0, 1, 0, 1]
Second Pass (Right to Left):
i | s[i] | Is 'a'? | suf | suf - i | Current ans[i] | New ans[i] | ans array |
---|---|---|---|---|---|---|---|
6 | 'b' | No | inf | inf | 1 | 1 | [0, 1, 0, 0, 1, 0, 1] |
5 | 'a' | Yes | 5 (update) | 0 | 0 | 0 | [0, 1, 0, 0, 1, 0, 1] |
4 | 'c' | No | 5 | 1 | 1 | 1 | [0, 1, 0, 0, 1, 0, 1] |
3 | 'a' | Yes | 3 (update) | 0 | 0 | 0 | [0, 1, 0, 0, 1, 0, 1] |
2 | 'a' | Yes | 2 (update) | 0 | 0 | 0 | [0, 1, 0, 0, 1, 0, 1] |
1 | 'b' | No | 2 | 1 | 1 | 1 | [0, 1, 0, 0, 1, 0, 1] |
0 | 'a' | Yes | 0 (update) | 0 | 0 | 0 | [0, 1, 0, 0, 1, 0, 1] |
Final Result: [0, 1, 0, 0, 1, 0, 1]
Verification:
- Index 0 ('a'): Distance is 0 (it is 'a' itself) β
- Index 1 ('b'): Nearest 'a' is at index 0 or 2, both at distance 1 β
- Index 2 ('a'): Distance is 0 (it is 'a' itself) β
- Index 3 ('a'): Distance is 0 (it is 'a' itself) β
- Index 4 ('c'): Nearest 'a' is at index 3 or 5, both at distance 1 β
- Index 5 ('a'): Distance is 0 (it is 'a' itself) β
- Index 6 ('b'): Nearest 'a' is at index 5, distance is 1 β
The algorithm correctly finds the shortest distance to 'a' for each position by considering both directions.
Solution Implementation
1class Solution:
2 def shortestToChar(self, s: str, c: str) -> List[int]:
3 """
4 Given a string s and a character c that occurs in s,
5 return an array of integers where answer[i] is the distance from index i
6 to the closest occurrence of character c in s.
7 """
8 n = len(s)
9 # Initialize result array with maximum possible distance
10 result = [n] * n
11
12 # First pass: left to right
13 # Track the most recent position of character c seen so far
14 prev_c_position = float('-inf')
15
16 for i in range(n):
17 if s[i] == c:
18 # Update the position of the most recent c
19 prev_c_position = i
20 # Calculate distance from current position to previous c
21 result[i] = min(result[i], i - prev_c_position)
22
23 # Second pass: right to left
24 # Track the most recent position of character c seen from the right
25 next_c_position = float('inf')
26
27 for i in range(n - 1, -1, -1):
28 if s[i] == c:
29 # Update the position of the most recent c from right
30 next_c_position = i
31 # Calculate distance from current position to next c
32 # and keep the minimum distance
33 result[i] = min(result[i], next_c_position - i)
34
35 return result
36
1class Solution {
2 public int[] shortestToChar(String s, char c) {
3 int length = s.length();
4 int[] result = new int[length];
5
6 // Initialize a large value representing infinity
7 final int INFINITY = 1 << 30;
8 Arrays.fill(result, INFINITY);
9
10 // First pass: left to right
11 // Track the most recent occurrence of character c from the left
12 int previousCharPosition = -INFINITY;
13 for (int i = 0; i < length; i++) {
14 // Update the position when we find character c
15 if (s.charAt(i) == c) {
16 previousCharPosition = i;
17 }
18 // Calculate distance from current position to the previous occurrence of c
19 result[i] = Math.min(result[i], i - previousCharPosition);
20 }
21
22 // Second pass: right to left
23 // Track the most recent occurrence of character c from the right
24 int nextCharPosition = INFINITY;
25 for (int i = length - 1; i >= 0; i--) {
26 // Update the position when we find character c
27 if (s.charAt(i) == c) {
28 nextCharPosition = i;
29 }
30 // Calculate distance from current position to the next occurrence of c
31 // Keep the minimum distance from both directions
32 result[i] = Math.min(result[i], nextCharPosition - i);
33 }
34
35 return result;
36 }
37}
38
1class Solution {
2public:
3 vector<int> shortestToChar(string s, char c) {
4 int n = s.size();
5 const int INF = 1 << 30; // Large value representing infinity
6 vector<int> result(n, INF);
7
8 // First pass: left to right
9 // Calculate distance from each position to the nearest 'c' on the left
10 int prevCharIndex = -INF; // Initialize to negative infinity to handle edge case
11 for (int i = 0; i < n; ++i) {
12 if (s[i] == c) {
13 prevCharIndex = i; // Update position of last seen character 'c'
14 }
15 result[i] = min(result[i], i - prevCharIndex); // Distance to nearest 'c' on the left
16 }
17
18 // Second pass: right to left
19 // Calculate distance from each position to the nearest 'c' on the right
20 int nextCharIndex = INF; // Initialize to positive infinity to handle edge case
21 for (int i = n - 1; i >= 0; --i) {
22 if (s[i] == c) {
23 nextCharIndex = i; // Update position of last seen character 'c'
24 }
25 result[i] = min(result[i], nextCharIndex - i); // Take minimum of left and right distances
26 }
27
28 return result;
29 }
30};
31
1/**
2 * Finds the shortest distance from each character in string s to the nearest character c
3 * @param s - The input string to search through
4 * @param c - The target character to find distances to
5 * @returns An array where each element represents the shortest distance from that position to character c
6 */
7function shortestToChar(s: string, c: string): number[] {
8 const stringLength = s.length;
9 const infinity = 1 << 30; // Large number representing infinity (2^30)
10 const distances: number[] = new Array(stringLength).fill(infinity);
11
12 // First pass: left to right
13 // Track the most recent position of character c seen so far
14 let previousCharPosition = -infinity;
15 for (let i = 0; i < stringLength; i++) {
16 if (s[i] === c) {
17 previousCharPosition = i;
18 }
19 // Calculate distance from current position to the previous occurrence of c
20 distances[i] = i - previousCharPosition;
21 }
22
23 // Second pass: right to left
24 // Track the nearest position of character c seen from the right
25 let nextCharPosition = infinity;
26 for (let i = stringLength - 1; i >= 0; i--) {
27 if (s[i] === c) {
28 nextCharPosition = i;
29 }
30 // Update with minimum distance between left and right occurrences
31 distances[i] = Math.min(distances[i], nextCharPosition - i);
32 }
33
34 return distances;
35}
36
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of string s
. The algorithm makes two passes through the string:
- First pass: iterates from left to right through all
n
characters, performing constant time operations (comparison, assignment, and minimum calculation) for each character - Second pass: iterates from right to left through all
n
characters, again performing constant time operations for each character - Total:
O(n) + O(n) = O(2n) = O(n)
Space Complexity: O(n)
, where n
is the length of string s
. The algorithm uses:
ans
array: storesn
integers to hold the result, requiringO(n)
space- Other variables (
n
,pre
,suf
,i
,ch
): constant spaceO(1)
- Total:
O(n) + O(1) = O(n)
Note: The space complexity analysis excludes the output array from consideration as it's required for the problem's result. If we include the output array, the space complexity remains O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Initialization of Boundary Values
Pitfall: Using incorrect initial values for pre
and suf
variables, such as:
- Setting
pre = 0
orsuf = n-1
instead of infinity values - Using
pre = None
orsuf = None
without proper handling
Why it's wrong: If you initialize pre = 0
, then for characters before the first occurrence of c
, the distance calculation i - pre
would give incorrect small values instead of representing "no character c found yet."
Incorrect Example:
pre = 0 # Wrong!
for i, ch in enumerate(s):
if ch == c:
pre = i
ans[i] = i - pre # This gives wrong distance when no 'c' found yet
Solution: Always use float('-inf')
for pre
and float('inf')
for suf
to ensure that positions without a nearby c
get properly large distance values that will be overwritten by the correct minimum.
2. Forgetting to Use min()
When Updating Distances
Pitfall: Directly assigning distances without comparing with existing values:
# Wrong approach - overwrites instead of keeping minimum
for i in range(n - 1, -1, -1):
if s[i] == c:
suf = i
ans[i] = suf - i # Should be min(ans[i], suf - i)
Why it's wrong: The second pass would completely overwrite the distances calculated in the first pass, losing information about closer occurrences of c
on the left side.
Solution: Always use min(ans[i], new_distance)
to ensure you're keeping the shortest distance from either direction.
3. Off-by-One Errors in Range Iteration
Pitfall: Using incorrect range boundaries in the backward pass:
# Wrong - misses the first element
for i in range(n - 1, 0, -1): # Should be range(n - 1, -1, -1)
...
Why it's wrong: This would skip index 0, leaving its distance potentially incorrect if there's a c
to its right but not to its left.
Solution: Use range(n - 1, -1, -1)
to ensure all indices from n-1
down to 0
are processed.
4. Not Handling Edge Cases with Single Character
Pitfall: Assuming the string has multiple characters without checking:
# Might fail for s = "e", c = "e" if n == 1: return [0] if s[0] == c else [1] # Wrong logic
Why it's wrong: Single character strings are valid inputs and the algorithm should handle them naturally without special cases.
Solution: The two-pass algorithm handles single character strings correctly without special casing. When s = "e"
and c = "e"
, both passes will correctly set the distance to 0.
Which of these properties could exist for a graph but not a tree?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
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!