算法:动态规划之字符串模式匹配

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

一、问题描述

二、常规算法

三、动态规划算法

总结


提示:以下是本篇文章正文内容,下面案例可供参考

一、问题描述

给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 '?' 和 '*' 的通配符匹配

'?' 可以匹配任何单个字符。
'*' 可以匹配任意字符串(包括空字符串)。

两个字符串完全匹配才算匹配成功。

s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。

输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。

输入:
s = "aa"
p = "*"
输出: true
解释: '*' 可以匹配任意字符串。

输入:
s = "cb"
p = "?a"
输出: false
解释: '?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。

输入:
s = "adceb"
p = "*a*b"
输出: true
解释: 第一个 '*' 可以匹配空字符串, 第二个 '*' 可以匹配字符串 "dce"。

输入:
s = "acdcb"
p = "a*c?b"
输出: false

二、常规算法

解题思路:

我们将这个题拆解一下,分为不包含*的场景和包含*的场景

1、不包含*的场景,我们逐位进行比对即可得出最终的结论

2、包含*的场景,我们无法确定起点位置,所以我们用s的每一个字符,去和p的每一个字符进行匹配,记录匹配的结果,最终相当于成了一道寻路题了,只不过限制了前进方向为向下或者向右,起点从首个字符能匹配到的地方开始算起。

代码示例:

public void test() {
    String s = "acdcb";
    String p = "a*c?b";
    // 模式匹配串中不包含*
    if (!p.contains("*")) {
        if (s.length() != p.length()) {
            System.out.println(false);
            return;
        }
        for (int i = 0; i < s.length(); i++) {
            if (p.charAt(i) == '?' || s.charAt(i) == p.charAt(i)) {
                continue;
            }
            System.out.println(false);
            return;
        }
        System.out.println(false);
        return;
    }
    // 模式匹配串中包含*, 我们用s中的每一个字符都去和p中的每个字符匹配, 结果保存在一个二维数组中, s中有几个字符, 就有几行数据
    int[][] array = new int[s.length()][p.length()];
    for (int i = 0; i < s.length(); i++) {
        for (int j = 0; j < p.length(); j++) {
            if (p.charAt(j) == '?' || p.charAt(j) == '*' || s.charAt(i) == p.charAt(j)) {
                // 单个字符匹配, 标记为1
                array[i][j] = 1;
                continue;
            }
            // 单个字符不匹配, 标记为0
            array[i][j] = 0;
        }
    }
    // 最后我们得到一个全部标记了0或1的二维数组, 起点是第一行中的某个元素, 要求从这个元素开始向下或者向右移动, 最终能抵达斜对角的位置
    for (int j = 0; j < p.length(); j++) {
        if (array[0][j] == 1) {
            boolean can = canReach(array, 0, j);
            if (!can) {
                continue;
            }
            System.out.println(true);
            return;
        }
    }
}

public boolean canReach(int[][] array, int row, int col) {
    // 如果当前位置已经到达右下角对角线,返回true
    if (row == array.length - 1 && col == array[0].length - 1) {
        return true;
    }

    // 如果当前位置超出矩阵范围或者元素为0,返回false
    if (row >= array.length || col >= array[0].length || array[row][col] == 0) {
        return false;
    }

    // 递归向下或向右移动
    return canReach(array, row + 1, col) || canReach(array, row, col + 1);
}

常规算法,胜于更易理解,将复杂问题拆解为简单的子问题,逐个破解。 

三、动态规划算法

解题思路:

最终的匹配结果依赖于最后一个字符的匹配结果,最后一个字符的匹配结果又依赖于前一个字符的匹配结果,依此类推,直至首个字符匹配成功。那么前一个字符的匹配结果在该字符的匹配结果的什么位置呢,答案是上方或者左侧。由于p中包含*字符,这个*可能代表0 ~ N个字符,所以可能是跳过该位置,有可能是跨了多个位置。我们约定横跨是向右,跳过是向下。

所以当出现p中某位是*时,它取的结果来自它的上方和左侧,如果有一个是通过,本位置即为通过。

代码示例: 

public void test() {
    String s = "aa";
    String p = "a";
    // 初始化动态规划数组
    boolean[][] dp = new boolean[s.length() + 1][p.length() + 1];
    // 当模式串为空时,只有当字符串也为空时才匹配成功
    dp[0][0] = true;
    // 处理模式串中的第一个字符
    for (int j = 1; j <= p.length(); j++) {
        if (p.charAt(j - 1) == '*') {
            dp[0][j] = dp[0][j - 1];
        }
    }
    // 动态规划过程
    for (int i = 1; i <= s.length(); i++) {
        for (int j = 1; j <= p.length(); j++) {
            if (p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)) {
                dp[i][j] = dp[i - 1][j - 1];
            } else if (p.charAt(j - 1) == '*') {
                dp[i][j] = dp[i - 1][j] || dp[i][j - 1];
            }
        }
    }
    System.out.println(JSON.toJSONString(dp));
    // 返回最终匹配结果
    System.out.println(dp[s.length()][p.length()]);
}

 动态规划算法,要找一下规律,代码比较简洁。


总结

解题过程已奉上,快快练起来,简单到有手就行!

你可能感兴趣的:(算法)