package com.freetymekiyan.algorithms.level.hard; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; /** * Given s1, s2, s3, find whether s3 is formed by the interleaving of s1 and s2. * <p> * For example, * Given: * s1 = "aabcc", * s2 = "dbbca", * <p> * When s3 = "aadbbcbcac", return true. * When s3 = "aadbbbaccc", return false. * <p> * Tags: Dynamic Programming, String */ public class InterleavingString { private InterleavingString i; /** * DP, bottom-up, Time: O(nm), and Space: O(nm) * Quick check, length of s3 should be the sum of s1 and s2 * <p> * If s1 provides first i characters, s2 provides first j characters, to interleave s3 at length (i + j). * Think about the recurrence relation, if with (i - 1) characters from s1 and j characters from s2, we can * interleave s3 at length (i + j - 1), the only thing we need to interleave s3 at length (i + j) is making sure * that ith character of s1 is the same as (i + j)th character of s3. * <p> * 1. i == 0 && j == 0, dp[i][j] is true initially * 2. first row, i == 0, dp[i][j] = dp[i][j - 1] && s2.charAt(j - 1) * == s3.charAt(j - 1) * 3. first col, j == 0, dp[i][j] = dp[i - 1][j] && s1.charAt(i - 1) * == s3.charAt(i - 1) * 4. dp[i][j] = (dp[i][j - 1] && s2.charAt(j - 1) == s3.charAt(i + j * - 1))|| (dp[i - 1][j] && s1.charAt(i - 1) == s3.charAt(i + j - 1)) * <p> * final result should dp[a][b] */ public boolean isInterleave(String s1, String s2, String s3) { if (s1 == null || s2 == null || s3 == null) { return false; } int a = s1.length(); int b = s2.length(); if (s3.length() != a + b) { return false; } boolean[][] dp = new boolean[a + 1][b + 1]; for (int i = 0; i <= a; i++) { for (int j = 0; j <= b; j++) { if (i == 0 && j == 0) { dp[0][0] = true; } else if (i == 0) { dp[0][j] = dp[0][j - 1] && s2.charAt(j - 1) == s3.charAt(j - 1); } else if (j == 0) { dp[i][0] = dp[i - 1][0] && s1.charAt(i - 1) == s3.charAt(i - 1); } else { dp[i][j] = (dp[i - 1][j] && s1.charAt(i - 1) == s3.charAt(i + j - 1)) || (dp[i][j - 1] && s2.charAt(j - 1) == s3.charAt(i + j - 1)); } } } return dp[a][b]; } /** * DP, space optimized, O(m) * space could optimize since optimal[i+1][] only depends on optimal[i][], */ public boolean isInterleaveOptimal(String s1, String s2, String s3) { if (s1 == null || s2 == null || s3 == null) { return false; } int a = s1.length(); int b = s2.length(); if (s3.length() != a + b) { return false; } boolean[] dp = new boolean[b + 1]; /*i == 0 && j == 0*/ dp[0] = true; // s1 empty, s2 matches s3 for (int j = 0; j < b; j++) { if (dp[j] && s2.charAt(j) == s3.charAt(j)) { dp[j + 1] = true; } } for (int i = 0; i < a; i++) { // from /*nothing from s2*/ if (dp[0] && s1.charAt(i) == s3.charAt(i)) { dp[0] = true; } else { dp[0] = false; } for (int j = 0; j < b; j++) { // select jth char /*from s1 or from s2*/ if (dp[j + 1] && (s1.charAt(i) == s3.charAt(i + j + 1)) || (dp[j] && s2.charAt(j) == s3.charAt(i + j + 1))) { // dp[j+1] means dp[i][j+1], result of last row, dp[j] means dp[i+1][j], result of this row last col dp[j + 1] = true; } else { dp[j + 1] = false; } } } return dp[b]; } @Before public void setUp() { i = new InterleavingString(); } @Test public void testExamples() { String s1 = "aabcc"; String s2 = "dbbca"; String s3 = "aadbbcbcac"; Assert.assertTrue(isInterleave(s1, s2, s3)); s3 = "aadbbbaccc"; Assert.assertFalse(isInterleave(s1, s2, s3)); s1 = "ab"; s2 = "bc"; s3 = "babc"; Assert.assertTrue(isInterleave(s1, s2, s3)); } @After public void tearDown() { i = null; } }