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.
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:
- Move forward (clockwise) directly
- 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:
-
Initialize variables:
- Get the length
n
of the words array - Set
ans = n
as a sentinel value (since maximum valid distance isn-1
)
- Get the length
-
Iterate through the array:
- Use
enumerate()
to get both indexi
and wordw
from the words array - For each word, check if it matches the target
- Use
-
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: currentans
, direct distancet
, and wrap-around distancen - t
- Compute the direct distance:
-
Return the result:
- If
ans
is still equal ton
, no target was found, return-1
- Otherwise, return
ans
as the minimum distance found
- If
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 EvaluatorExample 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 ton = 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 (1 → 0) 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
Which of the following array represent a max heap?
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!