模糊匹配 -- 关于暴力与动态规划的思考 Leetcode 44. Wildcard Matching

0x00 题目

Given an input string (s) and a pattern (p), implement wildcard
pattern matching with support for '?' and '*'.

'?' Matches any single character.
'*' Matches any sequence of characters (including the empty sequence).

The matching should cover the entire input string (not partial).
Note:
s could be empty and contains only lowercase letters a-z.
p could be empty and contains only lowercase letters a-z, and
characters like ? or *.

Example 1:
Input:
s = "aa"
p = "a"
Output: false
Explanation: "a" does not match the entire string "aa".

Example 2:
Input:
s = "aa"
p = "*"
Output: true

Explanation: '*' matches any sequence.
题目链接:https://leetcode.com/problems/wildcard-matching/description/

相信大家应该是见过这个题目的,而且用暴力+回溯的方式解这道题目读者应该不在少数。
不过尴尬的是笔者在做完之后才发现暴力+回溯可以更快的得到答案。

0x01 动态规划粗暴版本

笔者用了动态规划的思路,先看下最粗暴的动态规划版本:
记 dp[i][j] 表示 p[0…i] 能否匹配 s[0…j],

初始状态:

  1. 显然 dp[0][0] = true,空串匹配空串是可以的。
  2. 同时,dp[0][1...s.size()] = false,空串不能匹配任何非空串。

状态转移

对于任意 i (1<= i <= p.size()) 和 j (0 <= j <= s.size()),考察 ch = p[i-1]

  1. 如果 ch == '*'dp[i][j] 为 true,取决于 dp[i-1][0...j] 中有没有 true,意思是用 * 表示 s[0...j-1] 的任意后缀进行尝试。
  2. 如果 ch == '?'dp[i][j] 为 true,取决于 dp[i-1][j-1] 是否为true
  3. 否则,dp[i][j] 为true,取决于 dp[i-1][j-1] && ch == s[j-1] 是否为 true

最终结果

dp[p.size()][s.size()]

显然,这样做可以得到答案,但是时间复杂度达到 O(len(s) * len(s) * len(p)),空间复杂度达到 O( (len(s) + 1) * (len(p) + 1) )

0x02 动态规划改进版本

注意到,整个状态转移过程中,dp[i][0...j] 的状态仅取决于 dp[i-1][0...j],这句话有两个含义:

  1. 从行的角度,第 i 行的求解仅取决于 第 i-1 行
  2. 从列的角度,第 j 列的求解仅取决于 第 0…j 列

因此,我们可以很方便的使用滚动数组将空间压缩至一维,只要将 j 的枚举顺序反过来即可,这样求解 dp[j] 时,dp[0...j]都是没有被修改过的旧值,数据依赖关系没有被破坏。
对于遇到 ‘*’ 时,对于 dp[j] ,只要 dp[0...j] 中有true ,即可认为匹配成功。这个过程显然是单调的,不需要每次都从 0 枚举到 j ,因此一路或到最后一个j 即可。
下面贴一段改进后的代码:

bool isMatch(string s, string p) {
        int n = s.size();
        vector<bool> tp(n + 1, false);
        tp[0] = true;
        for(int i=0;i<p.size();++i){
            char ch = p[i];
            if(ch == '*'){
                bool tmp=false;
                for(int j=0;j<=n;++j){
                    tmp |= tp[j];
                    tp[j] = tmp;
                }
                continue;
            }
            for(int j=n;j>0;--j){
                if(ch == '?' or ch == s[j-1]){
                    tp[j] = tp[j-1];
                }else{
                    tp[j] = false;
                }
            }
            tp[0]=false;
        }
        return tp[n];
    }

此时空间复杂度 O(s.size() + 1),时间复杂度 O( (s.size() + 1) * (p.size() + 1))
虽然经过了优化,但是这个方法仍然没有暴力+回溯更快。其实仔细分析可以看到动态规划在逐步递推求解的过程中,需要求解所有子问题cache子问题的答案,因此这是动态规划的本质决定的。

到这里基本分析出了问题的关键,如果暴力+回溯的过程中没有重复求解子问题,那么求解答案的速度可能会比动态规划更快。暴力+回溯的代码就不贴了,大家可以自行尝试或者阅读网上的其他作者的代码。

0x03 总结&题外话

事实上,任何方法,比如搜索(深度优先、广度优先等),在求解问题时只要不出现重复求解子问题,都有可能会比动态规划更快,尤其是在搜索过程中增加各种剪枝和优化。每个不同的求解方法都有自己特别擅长的问题,希望大家在解题或者面试时遇到问题不要迷恋一种解法,多思考。

给大家分享另外一个问题,这是一个搜索比动态规划更快的例子,大家可都试试。题目链接:https://leetcode.com/problems/combination-sum/description/

附笔者的代码,同样使用了滚动数组优化的思路,但是速度同样没有搜索更快:

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        int n = candidates.size();
        vector< vector< vector<int>>> tp(target+1, vector<vector<int>>());
        for(int i=0;i<n;++i){
            int can = candidates[i];
            for(int j=target;j>=0; --j){
                vector<vector<int>> tmp;
                for(int k=0;k*can <= j;++k){
                    if(k*can == j){
                        vector<int> x;
                        for(int kk=1; kk<=k;++kk){
                            x.push_back(can);
                        }
                        tmp.emplace_back(move(x));
                        continue;
                    }
                    auto & p = tp[j - k * can];
                    for(auto x: p){
                        for(int kk=1; kk<=k;++kk){
                            x.push_back(can);
                        }
                        tmp.emplace_back(move(x));
                    }
                }
                tp[j] = move(tmp);
            }
        }
        return tp[target];
    }
};

好了今天就写到这里吧,谢谢大家关注与支持~


欢迎大家关注作者公众号,一起进步~
这里写图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章