leetcode 97. Interleaving String(交叉的字符串)

257 篇文章 17 订阅

Given s1s2s3, find whether s3 is formed by the interleaving of s1 and s2.

For example,
Given:
s1 = "aabcc",
s2 = "dbbca",

When s3 = "aadbbcbcac", return true.
When s3 = "aadbbbaccc", return false.

这一题是hard题,有一定的思维难度。

我是用 DP 来做的。令下面的 i,j 都是从1开始。(因为这样使得 dp[0][1] 可以表示 s1的前0个字符串和s2的前1个字符串,dp[0][0]表示两者都不取。)

若 dp[i][j] = true,那么 s1的前 i 个字符(s1[0] ~ s[i - 1]) 和 s2的前 j 的字符(s1[0] ~ s2[j -1]) 可以构成s3的前 i + j 的字符(s3[0] ~ s3[i + j -1])

例如, s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac",
例如, dp[1][1], 表示s1取前1位a, s2取前1位d,是否能组成s3的前两位aa, 显然false, ad ≠ aa.

先来固定i为0,看j。
dp[0][1], 表示s1取前0位, s2取前1位d,是否能组成s3的前1位, 显然false, d ≠ a.
dp[0][2], 表示s1取前0位, s2取前2位db,是否能组成s3的前2位,
显然false, db≠ aa.
.......

再来固定j为0,看i。
dp[i][0], 表示s1取前i位, s2取前0位,是否能组成s3的前i位

那么递推表达式如何写呢?
当前新加字符(来自s1或者s2),等于s3里面对应的位( i + j 位),并且dp[i][j-1] = true或者dp[i-1][j]=true。 这两个条件都具备的情况下,才有dp[i][j] = true.

package leetcode;

public class Interleaving_String_97 {

	public boolean isInterleave(String s1, String s2, String s3) {
		int m = s1.length();
		int n = s2.length();
		if (m == 0) {
			return s2.equals(s3);
		}
		if (n == 0) {
			return s1.equals(s3);
		}
		if ((m + n) != s3.length()) {
			return false;
		}
		// dp[i][j] = true,
		// 表示s1的前i个字符(s1[0] ~ s[i - 1])和s2(s1[0] ~ s2[j -1])的前j的字符。
		// 可以构成s3的前i + j(s3[0] ~ s3[i + j -1])的字符。
		boolean dp[][] = new boolean[m + 1][n + 1];
		if (s1.charAt(0) == s3.charAt(0)) {
			dp[1][0] = true;
		}
		for (int i = 2; i <= m; i++) {
			if (s1.charAt(i - 1) == s3.charAt(i - 1)) {
				dp[i][0] = dp[i - 1][0];
			}
		}
		if (s2.charAt(0) == s3.charAt(0)) {
			dp[0][1] = true;
		}
		for (int i = 2; i <= n; i++) {
			if (s2.charAt(i - 1) == s3.charAt(i - 1)) {
				dp[0][i] = dp[0][i - 1];
			}
		}

		for (int i = 1; i <= m; i++) {
			for (int j = 1; j <= n; j++) {
				if (dp[i - 1][j] == true && s1.charAt(i - 1) == s3.charAt(i + j - 1)) {
					dp[i][j] = true;
				}
				if (dp[i][j - 1] == true && s2.charAt(j - 1) == s3.charAt(i + j - 1)) {
					dp[i][j] = true;
				}
			}
		}
		return dp[m][n];
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Interleaving_String_97 i = new Interleaving_String_97();
		System.out.println(i.isInterleave("a", "b", "a"));
		System.out.println(i.isInterleave("aabc", "abad", "aabadabc"));
		System.out.println(i.isInterleave("ab", "bc", "babc"));
	}

}
这道题大神有令人眼前一亮的特殊思维。

s1, s2只有两个字符串,因此可以展平为一个二维地图,判断是否能从左上角走到右下角。
当s1到达第i个元素,s2到达第j个元素:
地图上往右一步就是 s2[j-1] 匹配 s3[i+j-1]。
地图上往下一步就是 s1[i-1] 匹配 s3[i+j-1]。

示例:s1="aa",s2="ab",s3="aaba"。标1的为可行。最终返回右下角。

     0   a   b
 
0   1    1    0

a   1    1    1

a   1    0    1

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        int m = s1.size();
        int n = s2.size();
        if(m+n != s3.size())
            return false;
        vector<vector<bool> > path(m+1, vector<bool>(n+1, false));
        for(int i = 0; i < m+1; i ++)
        {
            for(int j = 0; j < n+1; j ++)
            {
                if(i == 0 && j == 0)
                // start
                    path[i][j] = true;
                else if(i == 0)
                    path[i][j] = path[i][j-1] & (s2[j-1]==s3[j-1]);
                else if(j == 0)
                    path[i][j] = path[i-1][j] & (s1[i-1]==s3[i-1]);
                else
                    path[i][j] = (path[i][j-1] & (s2[j-1]==s3[i+j-1])) || (path[i-1][j] & (s1[i-1]==s3[i+j-1]));
            }
        }
        return path[m][n];
    }
};
这道题有solutions: https://leetcode.com/problems/interleaving-string/solution/

Solution

Approach #2 Recursion with memoization [Accepted]

Algorithm

在暴力的递归方法中,我们考虑交错两个给定字符串形成的每个可能的字符串。 但是,会遇到这样的情况,其中s1 s2的相同部分已经被处理过了,但是以不同的顺序排列(置换)。 但是无论处理的顺序如何,如果形成的结果字符串与所需的字符串(s3)匹配,最终结果仅依赖于s1 s2的剩余部分,但不依赖于已处理的部分。 因此,递归方法导致冗余计算。

这种冗余可以通过使用记忆和递归来消除。 为此,我们保留3个指针i,j,k,它们分别对应于当前字符s1,s2,s3的索引。 此外,我们还维护一个二维记忆数组,以跟踪迄今处理的子串。 memo[i][j]存储1或0或-1,取决于字符串s1的0~ith的部分和字符串s2的0~jth的部分是否已经被计算了。之后,我们选择s1 的当前字符(被i指向),如果它等于s3的当前字符(被k指向),我们就将它包含在处理后的字符串中,并递归地调用 is_Interleave(s1, i+1, s2, j, s3, k+1,memo)

public class Solution {
    public boolean is_Interleave(String s1, int i, String s2, int j, String s3, int k, int[][] memo) {
        if (i == s1.length()) {
            return s2.substring(j).equals(s3.substring(k));
        }
        if (j == s2.length()) {
            return s1.substring(i).equals(s3.substring(k));
        }
        if (memo[i][j] >= 0) {
            return memo[i][j] == 1 ? true : false;
        }
        boolean ans = false;
        if (s3.charAt(k) == s1.charAt(i) && is_Interleave(s1, i + 1, s2, j, s3, k + 1, memo)
                || s3.charAt(k) == s2.charAt(j) && is_Interleave(s1, i, s2, j + 1, s3, k + 1, memo)) {
            ans = true;
        }
        memo[i][j] = ans ? 1 : 0;
        return ans;
    }
    public boolean isInterleave(String s1, String s2, String s3) {
        int memo[][] = new int[s1.length()][s2.length()];
        for (int i = 0; i < s1.length(); i++) {
            for (int j = 0; j < s2.length(); j++) {
                memo[i][j] = -1;
            }
        }
        return is_Interleave(s1, 0, s2, 0, s3, 0, memo);
    }
}

Complexity Analysis

  • Time complexity : O(2​(m+n))m is the length of s1 and n is the length of s2.

  • Space complexity :O(m+n). The size of stack for recursive calls can go upto m+n.

Approach #3 Using 2-d Dynamic Programming [Accepted]

Algorithm

public class Solution {
    public boolean isInterleave(String s1, String s2, String s3) {
        if (s3.length() != s1.length() + s2.length()) {
            return false;
        }
        boolean dp[][] = new boolean[s1.length() + 1][s2.length() + 1];
        for (int i = 0; i <= s1.length(); i++) {
            for (int j = 0; j <= s2.length(); j++) {
                if (i == 0 && j == 0) {
                    dp[i][j] = true;
                } else if (i == 0) {
                    dp[i][j] = dp[i][j - 1] && s2.charAt(j - 1) == s3.charAt(i + j - 1);
                } else if (j == 0) {
                    dp[i][j] = dp[i - 1][j] && s1.charAt(i - 1) == s3.charAt(i + j - 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[s1.length()][s2.length()];
    }
}

Complexity Analysis

  • Time complexity : O(mn). dp array of size mn is filled.

  • Space complexity : O(mn). 2-d DP of size (m+1)(n+1) is required. m and n are the lengths of strings s1 and s2 repectively.

Approach #4 Using 1-d Dynamic Programming [Accepted]:

Algorithm

This approach is the same as the previous approach except that we have used only 1-d dp array to store the results of the prefixes of the strings processed so far. Instead of maintaining a 2-d array, we can maintain a 1-d array only and update the array's element dp[i] when needed using dp[i1] and the previous value of dp[i].

public class Solution {
    public boolean isInterleave(String s1, String s2, String s3) {
        if (s3.length() != s1.length() + s2.length()) {
            return false;
        }
        boolean dp[] = new boolean[s2.length() + 1];
        for (int i = 0; i <= s1.length(); i++) {
            for (int j = 0; j <= s2.length(); j++) {
                if (i == 0 && j == 0) {
                    dp[j] = true;
                } else if (i == 0) {
                    dp[j] = dp[j - 1] && s2.charAt(j - 1) == s3.charAt(i + j - 1);
                } else if (j == 0) {
                    dp[j] = dp[j] && s1.charAt(i - 1) == s3.charAt(i + j - 1);
                } else {
                    dp[j] = (dp[j] && s1.charAt(i - 1) == s3.charAt(i + j - 1)) || (dp[j - 1] && s2.charAt(j - 1) == s3.charAt(i + j - 1));
                }
            }
        }
        return dp[s2.length()];
    }
}
(说实话这个一维dp方法我不是太看得懂啊。。。)

Complexity Analysis

  • Time complexity : O(mn). dp array of size n is filled m times.

  • Space complexity : O(n)n is the length of the string s1.


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值