交错字符串

158 阅读3分钟

题目

动态规划

public class Main {
    public static void main(String[] args) {

        Main main = new Main();
        int [] nums = new int[] {3, 5, 1, 2, 4};
        main.isInterleave("aabcc", "dbbca", "aadbbcbcac");
    }

    public boolean isInterleave(String s1, String s2, String s3) {

        if (s1.length() + s2.length() != s3.length()) {
            return Boolean.FALSE;
        }

        // 首先s1.length + s2.length = s3.length 题目才有解题的必要
        // 由于是按顺序(即是从s1和s2的开头开始)交错进行
        // 假设s3可以由s1和s2交错得到 s3 = "abc" 假设s1和s2都只分割一次
        // s1和s2可能的组合就是是s1 = "", s2 = "abc";s1 = "a" s2 = "bc";s1 = "ab" s2 = "c"; s1 = "abc" s2 = ""
        // s2 = "a" s1 = "bc"; s2 = "ab" s1 = "c" 因为假定只分割一次, 所以这么多种可能只有一种和提供的s1和s2符合
        // 就说明s3的每个元素可能只来自s1, 也可能只来自s2, 也可能来自s1或者s2, 对应的就是取自s1和s2的元素个数不同
        // 因此可以枚举所有可能的组合, 当遍历到s3最后一个元素如果存在结果, 即正确

        // dp[i][j] 表示s1的前i个字符换 + s2的前j个字符串可以组成s3的前i + j个字符串
        boolean [][] dp = new boolean[s1.length() + 1][s2.length() + 1];
        dp[0][0] = Boolean.TRUE;

        for (int l = 1; l <= s3.length(); l ++) {
            // l 代表s3中前l长度的字符串
            char target = s3.charAt(l - 1);
            for (int i = 0; i <= l; i ++) {
                // j代表从s2中取的字符长度
                int j = l - i;
                if (i > s1.length() || j > s2.length()) {
                    continue;
                }
                // i代表从s1中取的字符长度
                if (i > 0) {
                    dp[i][j] = dp[i][j] || s1.charAt(i - 1) == target && dp[i - 1][j];
                }
                if (j > 0) {
                    dp[i][j] = dp[i][j] || s2.charAt(j - 1) == target && dp[i][j - 1];
                }
            }
        }
        return dp[s1.length()][s2.length()];
    }

}

基本思路

  1. 题目要求字符串交错, 没有规定每个字符串能取几个字符, 因此最终的结果一定是s1取一段, s2取一段(因为你第一个字符取自s1, 那么第二个字符如果还从s1取的话, 相当于你一次性从s1取了长度为2的字符串, 直到需要从s2取的时候)

  2. 如果s3能够由s1和s2交错得到, 那么s3.length = s1.length + s2.length一定成立, 同时它是一直成立的, 也就是说s3长度为1的时候, 它一定由s1长度为0的子串s2长度为1的子串, 或者s1长度为1的子串和s2长度为0的子串组成的. 同理s3为任意长度i + j的时候, 都是由s1长度为i的子串和s2长度为j的子串组成的.

  3. 根据2就可以将原问题拆解成子问题, 根据动态规划来解决.

  4. 对于s3每次子新增一个字符(新增前的长度为l1, 新增后的长度为l2 = l1 + 1), 该字符一定来自于s1或者s2, 我们可以通过枚举s1和s2子串的长度i和j, 来判断新增的字符到底来自s1还是s2. 例如认为l2 = i2 + j2, 即从s1得到i2长度的子串, 如果s1.charAt(i2 - 1)即i2长度子串的最后一个字符是否和新增的s3的字符相同, 如果相同说明该字符可以来自于s1, 同理比较s2.charAt(j2 - 1), 如果两个比较都失败了, 那么l2肯定不能由i2 + j2得到, 继续枚举i和j的可能值.

  5. 当假定的i和j, 存在最后的字符和s3新增的字符相同的情况, 例如l1 = i1 + j1; 当s1.charAt(i1 - 1)的时候, 这只是基本条件, 同时还需要dp[i1 - 1][j1] = true, 因为如果l1和由i1+j1得到, 那么l1-1一定可以由i1 - 1 + j1 得到(既不能违反条件4, 也代表了递归的状态转移联系, 就是为了保证这个组成是连续的情况, 不然当前状态和前面的状态就失去联系了).

  6. 如果s3新增的字符对于同一组i, j, 既可以从s1得到, 也能从s2得到, 满足其中任何一组的条件, dp[i][j] 都应该为true,