Write a Program to check whether a string is a valid shuffle of two strings or not
Problem Statement
Given three strings s1, s2, and result, check if result is a valid shuffle of s1 and s2. A valid shuffle means:
resultcontains all characters from boths1ands2- The relative order of characters from
s1is maintained - The relative order of characters from
s2is maintained
Example:
-
Input:
s1 = "abc",s2 = "def",result = "adbcef" -
Output:
true -
Explanation: Characters maintain their relative order: a-b-c and d-e-f
-
Input:
s1 = "abc",s2 = "def",result = "abdecf" -
Output:
true -
Input:
s1 = "abc",s2 = "def",result = "abcfed" -
Output:
false(d-e-f order is violated: f comes before e and d)
Approach 1: Using Two Pointers (Optimal)
Intuition
Use two pointers to traverse s1 and s2. For each character in result, it must match either the current character in s1 or s2. If it matches, advance the corresponding pointer.
Algorithm
- Check if length of
resultequalslen(s1) + len(s2) - Use pointers
i,jfors1ands2respectively - For each character in
result:- If it matches
s1[i], incrementi - Else if it matches
s2[j], incrementj - Else return false
- If it matches
- Return true if both pointers reached the end
public class Solution {
public boolean isValidShuffle(String s1, String s2, String result) {
int n1 = s1.length();
int n2 = s2.length();
// Length check
if (result.length() != n1 + n2) {
return false;
}
int i = 0, j = 0;
for (char c : result.toCharArray()) {
if (i < n1 && c == s1.charAt(i)) {
i++;
} else if (j < n2 && c == s2.charAt(j)) {
j++;
} else {
return false;
}
}
// Both strings should be fully traversed
return i == n1 && j == n2;
}
}Complexity Analysis
- Time Complexity: O(n) where n is length of result
- Space Complexity: O(1) - Only using pointers
Approach 2: Handling Duplicate Characters with Recursion
Intuition
When both s1 and s2 have the same current character, we need to try both possibilities. This requires backtracking or recursion.
Algorithm
- If result is empty and both s1 and s2 are empty, return true
- If current char of result matches both s1 and s2, try both paths
- If matches only s1, continue with s1
- If matches only s2, continue with s2
- If no match, return false
import java.util.*;
public class Solution {
public boolean isValidShuffle(String s1, String s2, String result) {
if (result.length() != s1.length() + s2.length()) {
return false;
}
return helper(s1, 0, s2, 0, result, 0);
}
private boolean helper(String s1, int i, String s2, int j, String result, int k) {
if (k == result.length()) {
return i == s1.length() && j == s2.length();
}
boolean match1 = i < s1.length() && s1.charAt(i) == result.charAt(k);
boolean match2 = j < s2.length() && s2.charAt(j) == result.charAt(k);
if (match1 && match2) {
return helper(s1, i + 1, s2, j, result, k + 1) ||
helper(s1, i, s2, j + 1, result, k + 1);
} else if (match1) {
return helper(s1, i + 1, s2, j, result, k + 1);
} else if (match2) {
return helper(s1, i, s2, j + 1, result, k + 1);
}
return false;
}
// With memoization
public boolean isValidShuffleMemo(String s1, String s2, String result) {
if (result.length() != s1.length() + s2.length()) {
return false;
}
Boolean[][] memo = new Boolean[s1.length() + 1][s2.length() + 1];
return helperMemo(s1, 0, s2, 0, result, 0, memo);
}
private boolean helperMemo(String s1, int i, String s2, int j,
String result, int k, Boolean[][] memo) {
if (k == result.length()) {
return i == s1.length() && j == s2.length();
}
if (memo[i][j] != null) {
return memo[i][j];
}
boolean match1 = i < s1.length() && s1.charAt(i) == result.charAt(k);
boolean match2 = j < s2.length() && s2.charAt(j) == result.charAt(k);
boolean res = false;
if (match1 && match2) {
res = helperMemo(s1, i + 1, s2, j, result, k + 1, memo) ||
helperMemo(s1, i, s2, j + 1, result, k + 1, memo);
} else if (match1) {
res = helperMemo(s1, i + 1, s2, j, result, k + 1, memo);
} else if (match2) {
res = helperMemo(s1, i, s2, j + 1, result, k + 1, memo);
}
memo[i][j] = res;
return res;
}
}Complexity Analysis
- Time Complexity: O(n*m) with memoization, O(2^n) without
- Space Complexity: O(n*m) for memoization table
Approach 3: Frequency Count (Simple Validation Only)
Intuition
This only checks if result contains exactly the same characters as s1 + s2. It does NOT verify the order, so use this as a quick pre-check.
import java.util.*;
public class Solution {
public boolean hasValidCharacters(String s1, String s2, String result) {
if (result.length() != s1.length() + s2.length()) {
return false;
}
Map<Character, Integer> freq = new HashMap<>();
for (char c : s1.toCharArray()) {
freq.put(c, freq.getOrDefault(c, 0) + 1);
}
for (char c : s2.toCharArray()) {
freq.put(c, freq.getOrDefault(c, 0) + 1);
}
for (char c : result.toCharArray()) {
int count = freq.getOrDefault(c, 0);
if (count == 0) return false;
freq.put(c, count - 1);
}
return true;
}
}Key Takeaways
- Two-pointer approach works perfectly when characters are unique
- Recursion with memoization is needed when dealing with duplicate characters
- The key constraint is maintaining relative order of characters from both strings
- Length check is an important early exit condition
- This problem is related to the "Interleaving String" problem (LeetCode 97)
- Frequency count alone cannot verify order - it's only a necessary condition, not sufficient