LeetCode 2515 - Shortest Distance to Target String in a Circular Array
The problem asks us to find the shortest distance from a given starting index in a circular array of strings to a target string.
Difficulty: 🟢 Easy
Topics: Array, String
Solution
Problem Understanding
The problem asks us to find the shortest distance from a given starting index in a circular array of strings to a target string. The circular nature of the array means that moving past the end wraps around to the beginning, and moving before the first element wraps around to the last element. This is important because the shortest distance may involve wrapping around the array in either direction.
The inputs are words, a list of lowercase strings, target, a string we want to reach, and startIndex, the index from which we begin. The output is an integer representing the minimum number of steps needed to reach any occurrence of target. If target does not appear in words, we must return -1.
The constraints indicate that the array length is small (1 <= words.length <= 100) and each string is relatively short (1 <= words[i].length <= 100). This suggests that even O(n²) solutions could be feasible, but simpler O(n) approaches exist and are more elegant.
Key edge cases include:
- The target is at the starting index.
- The target appears multiple times, and the shortest path may be left or right.
- The target does not exist.
- The array contains only one element.
Approaches
The brute-force approach is to iterate through the array in both directions from the start index and compute the distance to every occurrence of the target. For each element, we calculate the forward distance and the backward distance considering the circular wrap-around. We then take the minimum of these distances. This approach works because it explicitly checks all possible paths, but it requires scanning the array multiple times.
The optimal approach leverages the observation that we can calculate the circular distance between indices using modular arithmetic. For each occurrence of the target, the shortest distance can be expressed as the minimum of moving clockwise or counter-clockwise. By iterating once over the array and applying this calculation, we can determine the minimum distance efficiently.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n²) | O(1) | Scan in both directions repeatedly for every target occurrence |
| Optimal | O(n) | O(1) | Single pass to compute circular distances to all occurrences of target |
Algorithm Walkthrough
- Initialize a variable
min_distanceto infinity. This will keep track of the smallest distance found. - Iterate through the array
wordsusing an indexi. - For each
iwherewords[i]equalstarget, calculate the forward distance as(i - startIndex + n) % nand the backward distance as(startIndex - i + n) % n. - Update
min_distanceto be the minimum of itself, the forward distance, and the backward distance. - After iterating through the array, check if
min_distanceis still infinity. If it is, return-1since the target was not found. Otherwise, returnmin_distance.
Why it works: By considering both forward and backward distances for each occurrence of the target and taking the minimum, we guarantee that we find the shortest path in the circular array. The modular arithmetic ensures correct wrap-around calculation.
Python Solution
from typing import List
class Solution:
def closestTarget(self, words: List[str], target: str, startIndex: int) -> int:
n = len(words)
min_distance = float('inf')
for i in range(n):
if words[i] == target:
forward = (i - startIndex + n) % n
backward = (startIndex - i + n) % n
min_distance = min(min_distance, forward, backward)
return -1 if min_distance == float('inf') else min_distance
The implementation first initializes min_distance as infinity to handle the case where the target is not found. It then iterates through all indices of the array. Whenever it finds the target, it computes both forward and backward circular distances using modular arithmetic. The minimum distance across all occurrences is stored. Finally, if no occurrence was found, it returns -1; otherwise, it returns the computed minimum distance.
Go Solution
func closestTarget(words []string, target string, startIndex int) int {
n := len(words)
minDistance := n + 1 // larger than any possible distance
for i, word := range words {
if word == target {
forward := (i - startIndex + n) % n
backward := (startIndex - i + n) % n
if forward < minDistance {
minDistance = forward
}
if backward < minDistance {
minDistance = backward
}
}
}
if minDistance > n {
return -1
}
return minDistance
}
The Go implementation follows the same logic. Since Go lacks inf, we initialize minDistance to n+1 as a safe upper bound. Forward and backward distances are computed using modular arithmetic, and the minimum is updated similarly. If no target was found, the function returns -1.
Worked Examples
Example 1: words = ["hello","i","am","leetcode","hello"], target = "hello", startIndex = 1
| i | words[i] | forward | backward | min_distance |
|---|---|---|---|---|
| 0 | hello | (0-1+5)%5=4 | (1-0+5)%5=1 | 1 |
| 4 | hello | (4-1+5)%5=3 | (1-4+5)%5=2 | 1 |
Shortest distance: 1
Example 2: words = ["a","b","leetcode"], target = "leetcode", startIndex = 0
| i | words[i] | forward | backward | min_distance |
|---|---|---|---|---|
| 2 | leetcode | (2-0+3)%3=2 | (0-2+3)%3=1 | 1 |
Shortest distance: 1
Example 3: words = ["i","eat","leetcode"], target = "ate", startIndex = 0
No occurrence found, return -1.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Single pass over the array to check all occurrences of target |
| Space | O(1) | Only a few integer variables used, no extra data structures |
Since n is at most 100, this solution is very efficient. No extra memory is required beyond simple counters and modular arithmetic.
Test Cases
# Provided examples
assert Solution().closestTarget(["hello","i","am","leetcode","hello"], "hello", 1) == 1
assert Solution().closestTarget(["a","b","leetcode"], "leetcode", 0) == 1
assert Solution().closestTarget(["i","eat","leetcode"], "ate", 0) == -1
# Additional edge cases
assert Solution().closestTarget(["a"], "a", 0) == 0 # target at start
assert Solution().closestTarget(["a","b","a","c"], "c", 2) == 1 # wrapping around
assert Solution().closestTarget(["a","b","c"], "d", 1) == -1 # target missing
assert Solution().closestTarget(["x","y","z","x"], "x", 3) == 0 # target at startIndex
| Test | Why |
|---|---|
| Single-element array | Ensures start index equals target index returns 0 |
| Multiple occurrences with wrap | Validates shortest distance is chosen correctly using circular logic |
| Missing target | Confirms algorithm returns -1 when target does not exist |
| Target at startIndex | Confirms algorithm handles zero-distance case correctly |
Edge Cases
One important edge case is when the target is at the start index itself. In this case, both forward and backward distances are zero, and the algorithm correctly returns 0 because min_distance is updated with the minimum of itself and these distances.
Another edge case is when the target appears multiple times. The shortest path might require wrapping around the circular array, and using modular arithmetic ensures that the minimum distance is calculated correctly in both directions.
A third edge case is when the target does not exist in the array. Since min_distance is initialized to infinity (Python) or a value larger than n (Go), the algorithm can safely detect that no occurrence was found and return -1. This avoids accidental false positives in distance calculation.