[LeetCode] Scramble String (非常规DP求解)

Implement regular expression matching with support for '.' and '*'.

'.' Matches any single character.
'*' Matches zero or more of the preceding element.

The matching should cover the entire input string (not partial).

The function prototype should be:
bool isMatch(const char *s, const char *p)

Some examples:
isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "a*") → true
isMatch("aa", ".*") → true
isMatch("ab", ".*") → true
isMatch("aab", "c*a*b") → true

这题的递归思路非常明显,大致思路:

对于s1和s2,每个都划分成两个子字符串。分别叫作s1_left, s1_right, s2_left, 和s2_right。如果s1和s2是scramble,那么只有两种情况:

1) s1_left和s2_left是scramble,并且s1_left和s2_left是scramble;

2) s1_left和s2_right是scramble,并且s1_right和s2_left是scramble.

考虑到递归这里会对子问题进行重复求解,于是只能得用DP。如果使用常规的DP方法,则需要3维的DP:dp[i][j][k]表示s1从i开始,s2从j开始,长度为k的两个子字符串是否为scramble。

不过以上解法非常的麻烦,而且其实这种3维的DP表里仍然会存储冗余的状态。自己想出了一种非常规的DP求解方法,发现可以用一个哈希表代替3维的DP表,减少对冗余状态的存储。另外这里的DP其实可以跟递归思路一样,自顶向下,而非所谓真正的自底向上的DP。

这里记录子问题求解结果的数据结构我用Map<String, Boolean>,前面其实偷了一下懒,理论上应该是类似于Pair<String, String>一样的东西,就是任意两个字符串看成一个pair,然后记录是否为scramble的结果。但是如果真的手动写个类,还有覆盖equals方法,太麻烦了,于是这里偷懒一下,把Pair<s1, s2>的形式替换成s1@s2,记成一种特殊字符串。
另外,这里提供了几个快速的递归出口,一个是如果两个字符串长度不相等,可以立刻返回false。另一个是如果两个字符串内容相等,则可以立刻返回true。

	// Memo for dp.
	private Map<String, Boolean> dp = new HashMap<String, Boolean>();
	public boolean isScramble(String s1, String s2) {
		String key = s1 + "@" + s2;
		if (dp.containsKey(key)) {
			return dp.get(key);
		}

		// Two quick recursion exits.
		if (s1.length() != s2.length()) {
			dp.put(key, false);
			return false;
		}
		if (s1.equals(s2)) {
			dp.put(key, true);
			return true;
		}

		// Partition and match recursively.
		for (int i = 1; i < s1.length(); ++i) {
			for (int j = 1; j < s2.length(); ++j) {
				// i and j are the partition indexes.
				String s1_left = s1.substring(0, i);
				String s1_right = s1.substring(i);
				String s2_left = s2.substring(0, j);
				String s2_right = s2.substring(j);

				if (isScramble2(s1_left, s2_right)
						&& isScramble2(s1_right, s2_left)
						|| isScramble2(s1_left, s2_left)
						&& isScramble2(s1_right, s2_right)) {
					return true;
				}
			}
		}

		dp.put(key, false);
		return false;
	}

其实这题除了使用DP,还可以使用 递归+剪枝的方法。简单的递归虽然会导致TLE,但是有效剪枝有时还可以达到比DP更好的效果。
这里的剪枝条件可以 简单设为,所有字符的ASCII的值之和必须相等,这是成为scramble的一个充分条件。代码如下
	public boolean isScramble2(String s1, String s2) {
		// Two quick recursion exits.
		if (s1.length() != s2.length())
			return false;
		if (s1.equals(s2))
			return true;

		// Prune candidates here to reduce candidate check times.
		int sum = 0;
		for (int i = 0; i < s1.length(); i++) {
			sum += s1.charAt(i) - 'a';
			sum -= s2.charAt(i) - 'a';
		}
		if (sum != 0)
			return false;

		// Partition and match recursively.
		for (int i = 1; i < s1.length(); ++i) {
			for (int j = 1; j < s2.length(); ++j) {
				// i and j are the partition indexes.
				String s1_left = s1.substring(0, i);
				String s1_right = s1.substring(i);
				String s2_left = s2.substring(0, j);
				String s2_right = s2.substring(j);

				if (isScramble2(s1_left, s2_right)
						&& isScramble2(s1_right, s2_left)
						|| isScramble2(s1_left, s2_left)
						&& isScramble2(s1_right, s2_right)) {
					return true;
				}
			}
		}

		return false;
	}

总结一下此题。一般而言,求最优解的题目一般就2种思路:
1)如果足够幸运,能够发现贪心性质,则应该优先选用贪心法;(这题显然没有贪心性质,可以忽略。)

2)否则,寻找递归性质,尝试递归求解。

对于2),这里首先看子问题是否会被重复求解。如果存在重复的子问题求解,那么其实可以有3中选择:

1)DP是常规思路;

2)递归+剪枝 (当然用DP的时候也是可以剪枝的啦);

3)DP和递归结合:自顶向下去递归求解,顺便弄个备忘录记录下遇到的子问题的结果。

这题从运行时间上看,发现递归+剪枝的效果最好,主要还是这题的递归程度太深了。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值