This problem asks for the minimum number of subsequences of the source
string needed to form the target
string. A subsequence is a sequence that can be derived from another sequence by deleting some or no elements without changing the order of the remaining elements.
The most efficient approach is a greedy algorithm using two pointers. We iterate through the target
string, trying to find the longest possible subsequence within the source
string at each step.
Algorithm:
Initialization: We initialize a counter ans
to 0 (representing the number of subsequences) and a pointer j
to 0 (pointing to the current character in the target
string).
Iteration: We iterate while j
is less than the length of target
. Inside the loop:
source
that matches a prefix of the remaining target
string (starting from index j
). This inner loop advances the pointer j
in target
whenever a match is found.j
position in target
, it means the target
string cannot be formed using subsequences from the source
string; we return -1.j
), we increment ans
(one more subsequence needed) and continue to the next portion of the target
string.Time Complexity: O(m*n), where 'm' is the length of source
and 'n' is the length of target
. The nested loops (or repeated calls to the helper function) lead to this complexity in the worst case.
Space Complexity: O(1). The algorithm uses a constant amount of extra space regardless of the input size.
Code Examples (with detailed comments):
Python:
class Solution:
def shortestWay(self, source: str, target: str) -> int:
m, n = len(source), len(target) # lengths of source and target strings
ans = 0 # counter for subsequences
j = 0 # pointer for target string
while j < n: # Iterate until all characters in target are processed
i = 0 # pointer for source string
k = j # keep track of original j to check if any progress was made
while i < m and j < n:
if source[i] == target[j]:
j += 1 # move target pointer if match found
i += 1 #always move source pointer
if k == j: #check if j was moved at all during inner loop
return -1 # Impossible to form target using source subsequences
ans += 1 # Increment subsequence count
return ans
Java:
class Solution {
public int shortestWay(String source, String target) {
int m = source.length(), n = target.length();
int ans = 0, j = 0;
while (j < n) {
int i = 0;
boolean ok = false; //flag to check if any match found in inner loop
int k = j; // Keep track of original j.
while (i < m && j < n) {
if (source.charAt(i) == target.charAt(j)) {
ok = true;
++j;
}
++i;
}
if (!ok) {
return -1;
}
if(k==j) return -1; //check if j was moved at all during inner loop
++ans;
}
return ans;
}
}
C++:
class Solution {
public:
int shortestWay(string source, string target) {
int m = source.length(), n = target.length();
int ans = 0, j = 0;
while (j < n) {
int i = 0;
bool ok = false; //flag to check if any match found in inner loop
int k = j; // keep track of original j to check for progress
while (i < m && j < n) {
if (source[i] == target[j]) {
ok = true;
++j;
}
++i;
}
if (!ok) {
return -1;
}
if(k==j) return -1; //check if j was moved at all during inner loop
++ans;
}
return ans;
}
};
Go: (Similar structure to Java and C++)
func shortestWay(source string, target string) int {
m, n := len(source), len(target)
ans, j := 0, 0
for j < n {
ok := false //flag to check if any match found in inner loop
k := j // keep track of original j to check for progress
for i := 0; i < m && j < n; i++ {
if source[i] == target[j] {
ok = true
j++
}
}
if !ok {
return -1
}
if k == j { //check if j was moved at all during inner loop
return -1
}
ans++
}
return ans
}
These examples all implement the same core algorithm with minor syntactic differences. The crucial part is the nested loop (or equivalent logic) that greedily matches characters from source
to target
and the condition to handle cases where a match is impossible. The k
variable is added to handle the edge case where no character in the source string is able to make progress in the target string.