Facebook Pixel

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 position i to the nearest character c

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 is 3
  • For index 4 ('l'), the nearest 'e' is at position 3 or 5, both giving distance 1
  • For index 6 ('e'), the distance is 0 since it is itself an 'e'

The solution uses a two-pass approach:

  1. First pass (left to right): Track the most recent position of character c seen so far and calculate distances based on that
  2. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. 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 any c characters that might be coming up ahead. Similarly, a single pass from right to left only tells us about future occurrences.

  2. Tracking positions with infinity: We initialize pre = -inf for the left-to-right pass because initially, we haven't seen any c yet. Using negative infinity ensures that i - pre gives a very large distance, which is appropriate since there's no c to the left. Similarly, suf = inf for the right-to-left pass represents that initially there's no known c to the right.

  3. The minimum operation: After both passes, each position has been evaluated against both its nearest left c and nearest right c. Taking the minimum ensures we get the absolute shortest distance. If a position only has c on one side, that side will naturally give the smaller distance.

  4. 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 first c or after the last c) will also get correct distances because they only have c 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 with n (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 recent c 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 update pre to the current position i.
  • 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] is n, so in this first pass, we're effectively storing i - pre.
  • If no c has been seen yet, i - pre will be very large (since pre = -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 nearest c to the right, initialized to positive infinity.
  • When we encounter c, we update suf 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 left c) and the distance to right c.

Why This Works:

Consider a position i in the string:

  • After the first pass, ans[i] contains the distance to the nearest c on the left (or a very large value if there's no c 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)

This guarantees that ans[i] contains the shortest distance to any c in the string.

Time and Space Complexity:

  • Time: O(n) where n is the length of the string. We make exactly two passes through the string.
  • Space: O(n) for the answer array (or O(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 Evaluator

Example 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):

is[i]Is 'a'?prei - preans[i]ans array
0'a'Yes0 (update)00[0, 7, 7, 7, 7, 7, 7]
1'b'No011[0, 1, 7, 7, 7, 7, 7]
2'a'Yes2 (update)00[0, 1, 0, 7, 7, 7, 7]
3'a'Yes3 (update)00[0, 1, 0, 0, 7, 7, 7]
4'c'No311[0, 1, 0, 0, 1, 7, 7]
5'a'Yes5 (update)00[0, 1, 0, 0, 1, 0, 7]
6'b'No511[0, 1, 0, 0, 1, 0, 1]

After first pass: ans = [0, 1, 0, 0, 1, 0, 1]

Second Pass (Right to Left):

is[i]Is 'a'?sufsuf - iCurrent ans[i]New ans[i]ans array
6'b'Noinfinf11[0, 1, 0, 0, 1, 0, 1]
5'a'Yes5 (update)000[0, 1, 0, 0, 1, 0, 1]
4'c'No5111[0, 1, 0, 0, 1, 0, 1]
3'a'Yes3 (update)000[0, 1, 0, 0, 1, 0, 1]
2'a'Yes2 (update)000[0, 1, 0, 0, 1, 0, 1]
1'b'No2111[0, 1, 0, 0, 1, 0, 1]
0'a'Yes0 (update)000[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: stores n integers to hold the result, requiring O(n) space
  • Other variables (n, pre, suf, i, ch): constant space O(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 or suf = n-1 instead of infinity values
  • Using pre = None or suf = 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of these properties could exist for a graph but not a tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More