Interleaving String
Understanding the Problem of Interleaving Strings
String manipulation is a cornerstone of problem-solving in programming. Among the many fascinating challenges, the Interleaving String Problem offers an interesting test of logical thinking and algorithmic design.
In this article, we will break down this problem, understand what interleaving means, and explore how to determine whether one string can be formed by interleaving two other strings.
Problem Description
Imagine you are given three strings: s1
, s2
, and s3
. Your goal is to determine whether the third string, s3
, can be created by combining the characters of s1
and s2
in an interleaved fashion. You should return true
if it’s possible to interleave the strings to form s3
, or false
otherwise.
What Does Interleaving Mean?
Interleaving two strings involves taking their characters alternately to form a new string. However, there are a few rules to follow:
Balanced Partitions: Divide the strings
s1
ands2
into substrings such that the difference in the number of substrings between them is at most 1.- If
s1
is split inton
substrings ands2
intom
substrings, then the condition|n - m| <= 1
must hold.
- If
Order Preservation: The order of characters in
s1
ands2
should remain the same in the final string.- For example, given
s1 = "abc"
ands2 = "def"
, a valid interleaved string could be"adbcef"
or"abdecf"
, but not"acbdef"
.
- For example, given
Valid Interleaving Structure: The final interleaved string alternates parts of
s1
ands2
. Examples of interleaving include:s1 + t1 + s2 + t2 + ...
- Or
t1 + s1 + t2 + s2 + ...
Why Is This Problem Useful?
This problem is a great exercise for mastering:
- Dynamic Programming: Efficiently solving subproblems that require decision-making between choices.
- String Manipulation: Understanding how to handle character sequences and their relationships.
- Logical Thinking: Designing a solution while keeping track of multiple constraints.
Explanation:
We can split s1
into ["aa", "aa"]
, s2
can remain as "bbbb"
and s3
is formed by interleaving ["aa", "aa"]
and "bbbb"
.
Explanation:
We can’t split s3
into ["ab", "xz", "cy"]
as the order of characters is not maintained.
Approaching the Solution
To solve the interleaving string problem, one might consider using a dynamic programming or recursive approach to evaluate all possible ways to combine the strings. The solution must ensure that all conditions for interleaving are satisfied at every step.
There are mainly Four approach to solve this problem –
- Recursion
- Dynamic Programming (Top-Down)
- Dynamic Programming (Bottom-up)
- Dynamic Programming (Space Optimized)
1.Recursion
- Time complexity: O(n∗2n)
- Space complexity: O(n∗2n)
class Solution: def isInterleave(self, s1: str, s2: str, s3: str) -> bool: def dfs(i, j, k): if k == len(s3): return (i == len(s1)) and (j == len(s2)) if i < len(s1) and s1[i] == s3[k]: if dfs(i + 1, j, k + 1): return True if j < len(s2) and s2[j] == s3[k]: if dfs(i, j + 1, k + 1): return True return False return dfs(0, 0, 0)
class Solution { public: bool isInterleave(string s1, string s2, string s3) { return dfs(0, 0, 0, s1, s2, s3); } bool dfs(int i, int j, int k, string& s1, string& s2, string& s3) { if (k == s3.length()) { return (i == s1.length()) && (j == s2.length()); } if (i < s1.length() && s1[i] == s3[k]) { if (dfs(i + 1, j, k + 1, s1, s2, s3)) { return true; } } if (j < s2.length() && s2[j] == s3[k]) { if (dfs(i, j + 1, k + 1, s1, s2, s3)) { return true; } } return false; } };
public class Solution { public boolean isInterleave(String s1, String s2, String s3) { return dfs(0, 0, 0, s1, s2, s3); } private boolean dfs(int i, int j, int k, String s1, String s2, String s3) { if (k == s3.length()) { return (i == s1.length()) && (j == s2.length()); } if (i < s1.length() && s1.charAt(i) == s3.charAt(k)) { if (dfs(i + 1, j, k + 1, s1, s2, s3)) { return true; } } if (j < s2.length() && s2.charAt(j) == s3.charAt(k)) { if (dfs(i, j + 1, k + 1, s1, s2, s3)) { return true; } } return false; } }
class Solution { /** * @param {string} s1 * @param {string} s2 * @param {string} s3 * @return {boolean} */ isInterleave(s1, s2, s3) { const dfs = (i, j, k) => { if (k === s3.length) { return (i === s1.length) && (j === s2.length); } if (i < s1.length && s1[i] === s3[k]) { if (dfs(i + 1, j, k + 1)) { return true; } } if (j < s2.length && s2[j] === s3[k]) { if (dfs(i, j + 1, k + 1)) { return true; } } return false; } return dfs(0, 0, 0); } }
2. Dynamic Programming (Top-Down)
Time & Space Complexity
- Time complexity: O(2^m+n)
- Space complexity: O(m+n)
class Solution: def isInterleave(self, s1: str, s2: str, s3: str) -> bool: if len(s1) + len(s2) != len(s3): return False dp = [[False] * (len(s2) + 1) for i in range(len(s1) + 1)] dp[len(s1)][len(s2)] = True for i in range(len(s1), -1, -1): for j in range(len(s2), -1, -1): if i < len(s1) and s1[i] == s3[i + j] and dp[i + 1][j]: dp[i][j] = True if j < len(s2) and s2[j] == s3[i + j] and dp[i][j + 1]: dp[i][j] = True return dp[0][0]
public class Solution { private Boolean[][] dp; public boolean isInterleave(String s1, String s2, String s3) { int m = s1.length(), n = s2.length(); if (m + n != s3.length()) return false; dp = new Boolean[m + 1][n + 1]; return dfs(0, 0, 0, s1, s2, s3); } private boolean dfs(int i, int j, int k, String s1, String s2, String s3) { if (k == s3.length()) { return (i == s1.length()) && (j == s2.length()); } if (dp[i][j] != null) { return dp[i][j]; } boolean res = false; if (i < s1.length() && s1.charAt(i) == s3.charAt(k)) { res = dfs(i + 1, j, k + 1, s1, s2, s3); } if (!res && j < s2.length() && s2.charAt(j) == s3.charAt(k)) { res = dfs(i, j + 1, k + 1, s1, s2, s3); } dp[i][j] = res; return res; } }
class Solution { vector> dp; public: bool isInterleave(string s1, string s2, string s3) { int m = s1.length(), n = s2.length(); if (m + n != s3.length()) return false; dp = vector >(m + 1, vector (n + 1, -1)); return dfs(0, 0, 0, s1, s2, s3); } bool dfs(int i, int j, int k, string& s1, string& s2, string& s3) { if (k == s3.length()) { return (i == s1.length()) && (j == s2.length()); } if (dp[i][j] != -1) { return dp[i][j]; } bool res = false; if (i < s1.length() && s1[i] == s3[k]) { res = dfs(i + 1, j, k + 1, s1, s2, s3); } if (!res && j < s2.length() && s2[j] == s3[k]) { res = dfs(i, j + 1, k + 1, s1, s2, s3); } dp[i][j] = res; return res; } };
class Solution { /** * @param {string} s1 * @param {string} s2 * @param {string} s3 * @return {boolean} */ isInterleave(s1, s2, s3) { const m = s1.length, n = s2.length; if (m + n !== s3.length) return false; const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(-1)); const dfs = (i, j, k) => { if (k === s3.length) { return (i === s1.length) && (j === s2.length); } if (dp[i][j] !== -1) { return dp[i][j]; } let res = false; if (i < s1.length && s1[i] === s3[k]) { res = dfs(i + 1, j, k + 1); } if (!res && j < s2.length && s2[j] === s3[k]) { res = dfs(i, j + 1, k + 1); } dp[i][j] = res; return res; } return dfs(0, 0, 0); } }
3. Dynamic Programming (Bottom-Up)
Time & Space Complexity
- Time complexity: O(m∗n)
- Space complexity: O(m∗n)
class Solution: def maxCoins(self, nums): n = len(nums) new_nums = [1] + nums + [1] dp = [[0] * (n + 2) for _ in range(n + 2)] for l in range(n, 0, -1): for r in range(l, n + 1): for i in range(l, r + 1): coins = new_nums[l - 1] * new_nums[i] * new_nums[r + 1] coins += dp[l][i - 1] + dp[i + 1][r] dp[l][r] = max(dp[l][r], coins) return dp[1][n]
public class Solution { public boolean isInterleave(String s1, String s2, String s3) { int m = s1.length(), n = s2.length(); if (m + n != s3.length()) { return false; } boolean[][] dp = new boolean[m + 1][n + 1]; dp[m][n] = true; for (int i = m; i >= 0; i--) { for (int j = n; j >= 0; j--) { if (i < m && s1.charAt(i) == s3.charAt(i + j) && dp[i + 1][j]) { dp[i][j] = true; } if (j < n && s2.charAt(j) == s3.charAt(i + j) && dp[i][j + 1]) { dp[i][j] = true; } } } return dp[0][0]; } }
class Solution { public: bool isInterleave(string s1, string s2, string s3) { int m = s1.length(), n = s2.length(); if (m + n != s3.length()) { return false; } vector> dp(m + 1, vector (n + 1, false)); dp[m][n] = true; for (int i = m; i >= 0; i--) { for (int j = n; j >= 0; j--) { if (i < m && s1[i] == s3[i + j] && dp[i + 1][j]) { dp[i][j] = true; } if (j < n && s2[j] == s3[i + j] && dp[i][j + 1]) { dp[i][j] = true; } } } return dp[0][0]; } };
class Solution { /** * @param {string} s1 * @param {string} s2 * @param {string} s3 * @return {boolean} */ isInterleave(s1, s2, s3) { let m = s1.length, n = s2.length; if (m + n !== s3.length) { return false; } const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(false)); dp[m][n] = true; for (let i = m; i >= 0; i--) { for (let j = n; j >= 0; j--) { if (i < m && s1[i] === s3[i + j] && dp[i + 1][j]) { dp[i][j] = true; } if (j < n && s2[j] === s3[i + j] && dp[i][j + 1]) { dp[i][j] = true; } } } return dp[0][0]; } }
4. Dynamic Programming (Space Optimized)
Time & Space Complexity
- Time complexity: O(m∗n)
- Space complexity: O(min(m,n))
class Solution: def isInterleave(self, s1: str, s2: str, s3: str) -> bool: m, n = len(s1), len(s2) if m + n != len(s3): return False if n < m: s1, s2 = s2, s1 m, n = n, m dp = [False for _ in range(n + 1)] dp[n] = True for i in range(m, -1, -1): nextDp = [False for _ in range(n + 1)] nextDp[n] = True for j in range(n, -1, -1): if i < m and s1[i] == s3[i + j] and dp[j]: nextDp[j] = True if j < n and s2[j] == s3[i + j] and nextDp[j + 1]: nextDp[j] = True dp = nextDp return dp[0]
public class Solution { public boolean isInterleave(String s1, String s2, String s3) { int m = s1.length(), n = s2.length(); if (m + n != s3.length()) return false; if (n < m) { String temp = s1; s1 = s2; s2 = temp; int tempLength = m; m = n; n = tempLength; } boolean[] dp = new boolean[n + 1]; dp[n] = true; for (int i = m; i >= 0; i--) { boolean[] nextDp = new boolean[n + 1]; nextDp[n] = true; for (int j = n; j >= 0; j--) { if (i < m && s1.charAt(i) == s3.charAt(i + j) && dp[j]) { nextDp[j] = true; } if (j < n && s2.charAt(j) == s3.charAt(i + j) && nextDp[j + 1]) { nextDp[j] = true; } } dp = nextDp; } return dp[0]; } }
class Solution { public: bool isInterleave(string s1, string s2, string s3) { int m = s1.size(), n = s2.size(); if (m + n != s3.size()) return false; if (n < m) { swap(s1, s2); swap(m, n); } vectordp(n + 1); dp[n] = true; for (int i = m; i >= 0; --i) { vector nextDp(n + 1); nextDp[n] = true; for (int j = n; j >= 0; --j) { if (i < m && s1[i] == s3[i + j] && dp[j]) { nextDp[j] = true; } if (j < n && s2[j] == s3[i + j] && nextDp[j + 1]) { nextDp[j] = true; } } dp = nextDp; } return dp[0]; } };
class Solution { /** * @param {string} s1 * @param {string} s2 * @param {string} s3 * @return {boolean} */ isInterleave(s1, s2, s3) { let m = s1.length, n = s2.length; if (m + n !== s3.length) return false; if (n < m) { [s1, s2] = [s2, s1]; [m, n] = [n, m]; } let dp = Array(n + 1).fill(false); dp[n] = true; for (let i = m; i >= 0; i--) { let nextDp = Array(n + 1).fill(false); nextDp[n] = true; for (let j = n; j >= 0; j--) { if (i < m && s1[i] === s3[i + j] && dp[j]) { nextDp[j] = true; } if (j < n && s2[j] === s3[i + j] && nextDp[j + 1]) { nextDp[j] = true; } } dp = nextDp; } return dp[0]; } }
Conclusion
The Interleaving String problem is more than just a test of string manipulation skills; it’s a practical exercise in algorithmic thinking. It demonstrates the power of logical sequencing and helps build a strong foundation for solving real-world problems where multiple sequences or inputs need to be merged in specific ways.
By tackling problems like this, you sharpen your ability to think critically and design efficient solutions for complex challenges.