Facebook Pixel

2515. Shortest Distance to Target String in a Circular Array

Problem Description

You have a circular array of strings called words and need to find the shortest distance to reach a target string starting from a given startIndex.

In a circular array, the elements wrap around - after the last element comes the first element again. This means:

  • Moving forward from position i takes you to position (i + 1) % n
  • Moving backward from position i takes you to position (i - 1 + n) % n
  • Where n is the length of the array

Starting at startIndex, you can move either forward or backward one position at a time. Each move counts as 1 step.

Your task is to find the minimum number of steps needed to reach any position that contains the target string. You can move in either direction (forward or backward) around the circular array.

If the target string doesn't exist anywhere in the words array, return -1.

For example, if you have words = ["a", "b", "c", "d"] with startIndex = 0 and target = "c":

  • Moving forward: 0 → 1 → 2 (2 steps to reach "c")
  • Moving backward: 0 → 3 → 2 (2 steps to reach "c")
  • The minimum distance would be 2

The solution iterates through all positions containing the target string and calculates both the direct distance t = abs(i - startIndex) and the wrap-around distance n - t. It keeps track of the minimum distance found. If no target is found (ans remains equal to n), it returns -1.

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

Intuition

The key insight is recognizing that in a circular array, there are only two possible paths between any two positions: going clockwise or going counterclockwise.

When we want to reach a target position from our starting position, we can either:

  1. Move forward (clockwise) directly
  2. Move backward (counterclockwise), wrapping around the array

For any target position i and starting position startIndex, the direct distance is simply |i - startIndex|. But since the array is circular, we might get there faster by going the "other way around."

Think of it like a clock face - if you're at 2 o'clock and want to reach 10 o'clock, you could move forward 8 hours, or you could move backward 4 hours. The backward path (4 hours) is shorter.

The wrap-around distance can be calculated as n - direct_distance, where n is the array length. This represents going the opposite direction around the circle.

Since there might be multiple occurrences of the target string in the array, we need to check all positions where the target appears and find which one gives us the minimum distance. For each occurrence, we calculate both the direct distance and the wrap-around distance, then take the smaller of the two.

The algorithm initializes ans to n (the array length) as a sentinel value. This is clever because the maximum possible distance in a circular array is n-1. If after checking all elements, ans is still n, it means we never found the target, so we return -1.

Solution Approach

The implementation uses a simple linear search with distance calculation optimization for circular arrays.

Algorithm Steps:

  1. Initialize variables:

    • Get the length n of the words array
    • Set ans = n as a sentinel value (since maximum valid distance is n-1)
  2. Iterate through the array:

    • Use enumerate() to get both index i and word w from the words array
    • For each word, check if it matches the target
  3. Calculate distances when target is found:

    • Compute the direct distance: t = abs(i - startIndex)
    • The wrap-around distance is automatically n - t
    • Update ans with the minimum of three values: current ans, direct distance t, and wrap-around distance n - t
  4. Return the result:

    • If ans is still equal to n, no target was found, return -1
    • Otherwise, return ans as the minimum distance found

Code walkthrough:

def closetTarget(self, words: List[str], target: str, startIndex: int) -> int:
    n = len(words)
    ans = n  # Initialize with impossible distance
  
    for i, w in enumerate(words):
        if w == target:  # Found a match
            t = abs(i - startIndex)  # Direct distance
            ans = min(ans, t, n - t)  # Compare with wrap-around distance
  
    return -1 if ans == n else ans  # Check if target was found

Time Complexity: O(n) where n is the length of the words array, as we iterate through it once.

Space Complexity: O(1) as we only use a constant amount of extra space for variables.

The elegance of this solution lies in its simplicity - by recognizing that there are only two paths in a circular array and that n - direct_distance gives us the wrap-around distance, we can solve the problem with a single pass through the 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 a concrete example:

Input: words = ["hello", "i", "am", "leetcode", "hello"], target = "hello", startIndex = 1

Step 1: Initialize

  • n = 5 (length of array)
  • ans = 5 (sentinel value)

Step 2: Iterate through array

Iteration 1 (i=0, w="hello"):

  • Found target! Calculate distances:
  • Direct distance: t = abs(0 - 1) = 1
  • Wrap-around distance: n - t = 5 - 1 = 4
  • Update ans = min(5, 1, 4) = 1

Iteration 2 (i=1, w="i"):

  • Not target, skip

Iteration 3 (i=2, w="am"):

  • Not target, skip

Iteration 4 (i=3, w="leetcode"):

  • Not target, skip

Iteration 5 (i=4, w="hello"):

  • Found target! Calculate distances:
  • Direct distance: t = abs(4 - 1) = 3
  • Wrap-around distance: n - t = 5 - 3 = 2
  • Update ans = min(1, 3, 2) = 1 (no change)

Step 3: Return result

  • ans = 1 which is not equal to n = 5
  • Return 1

Visual representation:

Index:    [0]      [1]    [2]    [3]         [4]
Words:  "hello" -> "i" -> "am" -> "leetcode" -> "hello"
START        TARGET                                 TARGET

Path to index 0: Move backward 1 step (10)
Path to index 4: Move forward 3 steps OR backward 2 steps (minimum is 2)
Overall minimum: 1 step

Solution Implementation

1class Solution:
2    def closetTarget(self, words: List[str], target: str, startIndex: int) -> int:
3        """
4        Find the minimum distance from startIndex to any occurrence of target in a circular array.
5      
6        Args:
7            words: List of strings representing the circular array
8            target: The target string to search for
9            startIndex: The starting position in the array
10          
11        Returns:
12            The minimum distance to reach target, or -1 if target doesn't exist
13        """
14        n = len(words)
15        min_distance = n  # Initialize with maximum possible distance
16      
17        # Iterate through all words to find occurrences of target
18        for index, word in enumerate(words):
19            if word == target:
20                # Calculate direct distance from startIndex to current index
21                direct_distance = abs(index - startIndex)
22              
23                # Calculate wrap-around distance (going the other way in circular array)
24                wrap_distance = n - direct_distance
25              
26                # Update minimum distance considering both paths
27                min_distance = min(min_distance, direct_distance, wrap_distance)
28      
29        # Return -1 if target was not found, otherwise return minimum distance
30        return -1 if min_distance == n else min_distance
31
1class Solution {
2    public int closetTarget(String[] words, String target, int startIndex) {
3        int arrayLength = words.length;
4        int minDistance = arrayLength; // Initialize with maximum possible distance
5      
6        // Iterate through each word in the array
7        for (int currentIndex = 0; currentIndex < arrayLength; ++currentIndex) {
8            String currentWord = words[currentIndex];
9          
10            // Check if current word matches the target
11            if (currentWord.equals(target)) {
12                // Calculate direct distance from startIndex to currentIndex
13                int directDistance = Math.abs(currentIndex - startIndex);
14              
15                // Calculate wrap-around distance (going the opposite direction in circular array)
16                int wrapAroundDistance = arrayLength - directDistance;
17              
18                // Choose the minimum between direct and wrap-around distance
19                int shortestPath = Math.min(directDistance, wrapAroundDistance);
20              
21                // Update the overall minimum distance
22                minDistance = Math.min(minDistance, shortestPath);
23            }
24        }
25      
26        // If minDistance hasn't changed from initial value, target wasn't found
27        return minDistance == arrayLength ? -1 : minDistance;
28    }
29}
30
1class Solution {
2public:
3    int closetTarget(vector<string>& words, string target, int startIndex) {
4        int arraySize = words.size();
5        int minDistance = arraySize;  // Initialize with maximum possible distance
6      
7        // Iterate through all words in the array
8        for (int currentIndex = 0; currentIndex < arraySize; ++currentIndex) {
9            string currentWord = words[currentIndex];
10          
11            // Check if current word matches the target
12            if (currentWord == target) {
13                // Calculate direct distance between current index and start index
14                int directDistance = abs(currentIndex - startIndex);
15              
16                // Calculate wrap-around distance (going the opposite direction in circular array)
17                int wrapAroundDistance = arraySize - directDistance;
18              
19                // Update minimum distance with the smaller of the two paths
20                minDistance = min(minDistance, min(directDistance, wrapAroundDistance));
21            }
22        }
23      
24        // If no target word was found, minDistance remains as arraySize, return -1
25        // Otherwise, return the minimum distance found
26        return minDistance == arraySize ? -1 : minDistance;
27    }
28};
29
1/**
2 * Finds the minimum distance from startIndex to the target word in a circular array
3 * @param words - Array of strings to search through
4 * @param target - The target string to find
5 * @param startIndex - Starting position in the array
6 * @returns The minimum distance to target, or -1 if not found
7 */
8function closetTarget(words: string[], target: string, startIndex: number): number {
9    const n: number = words.length;
10  
11    // Search in both directions simultaneously up to half the array length
12    for (let distance: number = 0; distance <= n >> 1; distance++) {
13        // Calculate left index (moving backwards in circular array)
14        const leftIndex: number = (startIndex - distance + n) % n;
15      
16        // Calculate right index (moving forwards in circular array)
17        const rightIndex: number = (startIndex + distance) % n;
18      
19        // Check if target is found at either the left or right position
20        if (words[leftIndex] === target || words[rightIndex] === target) {
21            return distance;
22        }
23    }
24  
25    // Target not found in the array
26    return -1;
27}
28

Time and Space Complexity

Time Complexity: O(n) where n is the length of the words array. The algorithm iterates through the entire array once using enumerate(), and for each element that matches the target, it performs constant time operations (calculating absolute difference, minimum comparison). Therefore, the overall time complexity is linear.

Space Complexity: O(1). The algorithm only uses a constant amount of extra space to store variables n, ans, i, w, and t. No additional data structures that scale with input size are created, making the space complexity constant.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting the Circular Nature of the Array

A common mistake is treating the array as linear and only calculating the direct distance without considering the wrap-around path. This leads to suboptimal or incorrect results.

Incorrect approach:

def closetTarget(self, words: List[str], target: str, startIndex: int) -> int:
    min_distance = float('inf')
    for i, word in enumerate(words):
        if word == target:
            # Only considering direct path - WRONG!
            min_distance = min(min_distance, abs(i - startIndex))
    return -1 if min_distance == float('inf') else min_distance

Why it fails: For words = ["a", "b", "c"], startIndex = 0, target = "c", this would return 2, but the wrap-around path (0 → 2) gives us distance 1.

Solution: Always calculate both paths:

  • Direct: abs(i - startIndex)
  • Wrap-around: n - abs(i - startIndex)

2. Incorrect Wrap-Around Distance Calculation

Another pitfall is miscalculating the wrap-around distance using complex modulo operations that can introduce bugs.

Incorrect approach:

# Overly complex and error-prone
forward_dist = (i - startIndex) % n
backward_dist = (startIndex - i) % n
min_distance = min(min_distance, forward_dist, backward_dist)

Why it's problematic: The modulo operations can give unexpected results with negative numbers, and the logic becomes harder to verify.

Solution: Use the simple formula n - direct_distance for wrap-around distance. Since the direct distance is abs(i - startIndex), the other way around the circle is simply the remainder of the circle.

3. Edge Case: Starting Position Contains Target

Forgetting that the starting position itself might contain the target string, which should return 0.

The provided solution handles this correctly: When i == startIndex and word == target, the direct distance calculation abs(i - startIndex) equals 0, which is the correct answer.

4. Using Wrong Sentinel Value

Using float('inf') or a very large number as the initial minimum distance can work but is less elegant than using n.

Why n is better:

  • It's the smallest impossible value (maximum valid distance is n-1)
  • It naturally serves as a flag for "target not found"
  • No need to import additional modules or use floating-point comparisons

Best practice:

min_distance = n  # Perfect sentinel value
# ... calculation logic ...
return -1 if min_distance == n else min_distance
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following array represent a max heap?


Recommended Readings

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

Load More