[Leetcode] 291. Word Pattern II

本篇博客详细介绍了LeetCode中的291题,即Word Pattern II的解决方案。这是一个使用回溯(backtrack)算法的典型问题。在递归过程中,会有一个指针在模式(pattern)上逐步移动,同时另一个指针在输入字符串(str)上按步长n移动。解题思路分为两种情况:1. 如果当前模式字符已有匹配的子字符串,就继续匹配;2. 若无匹配子字符串,则尝试所有可能的子字符串进行匹配。整个过程采用深度优先搜索(DFS)策略,并通过patternMap记录模式字符与字符串子串的映射,同时用visited避免重复匹配。最后,代码中还加入了剪枝操作,避免无效的匹配尝试。

这一题其实就是一道典型的backtrack的题目,每一个递归层的前进都伴随着一个指针在pattern上前进一格,然后另一个指针在str上前进n格(n = 1, 2, 3...到字符串结尾)。譬如pattern = "abab", str = "redblueredblue",第一个递归层往下走之后,对应pattern的指针只往前走一个,对应str的指针可以推进任意格造成任意的子字符串对应pattern第一个字符a。

每往下走一层,就分以下几种情况
1. 当前的pattern指针所对应的字母是否有对应的子字符串,有子字符串的话,你就知道str那边应该取多长的字符串与之匹配,如果配得上,就pattern指针往下走一格,str对应的指针往下走对应的格数,否则直接返回FALSE
2. 如果没有对应的子字符串,就按照上面的描述那样让当前的pattern指针指向的字符去匹配所有可能的子字符串并且往下走递归层。

 

思路并不复杂,可以得到代码如下:

    public boolean wordPatternMatch(String pattern, String str) {
        HashMap<Character, String> patternMap = new HashMap<>(); 
        return _wordMatch(patternMap, new HashSet<>(), pattern, str, 0, 0);
    }
    
    private boolean _wordMatch(HashMap<Character, String> patternMap, HashSet<String> visited, String pattern, String str, int pPos, int sPos) {
        if (pPos == pattern.length() && sPos == str.length()) {
            return true;
        } else if (pPos == pattern.length() || sPos == str.length()) {
            return false;
        } else {
            char pCh = pattern.charAt(pPos);
            if (patternMap.containsKey(pCh)) {
                String matchedStr = patternMap.get(pCh);
                if (str.length() - sPos < matchedStr.length() || !matchedStr.equals(str.substring(sPos, sPos + matchedStr.length()))) {
                    return false;
                } else {
                    return _wordMatch(patternMap, visited, pattern, str, pPos + 1, sPos + matchedStr.length());
                }
            } else {
                for (int i = sPos + 1; i <= str.length(); i++) {
                    String subStr = str.substring(sPos, i);
                    if (visited.contains(subStr)) continue;

                    patternMap.put(pCh, subStr);
                    visited.add(subStr);
                    if (_wordMatch(patternMap, visited, pattern, str, pPos + 1, i)) {
                        return true;
                    }
                    visited.remove(subStr);
                    patternMap.remove(pCh);
                }
                
                return false;
            }
        }
    }

在上述代码中,patternMap就是记录你pattern中的每个字符在当前的情况下对应着什么字符串。所以每取一个子字符串放进map中就往下走一层,走完回归的时候就记得要删除。这其实就是一个DFS的思路。另外,我们还需要visited的原因是,pattern的字符和str的子字符串是一一对应的。譬如说如果pattern里a对应了red,那么b就不能对应red,所以如果该子字符串已经被对应过了,就不能再被pattern的其他字符对应了,所以我们就要跳过。

上面那个代码其实还可以优化一下,就是做一个简单的节支,在for循环里。如果你做完当前的匹配后,子字符串剩余的长度不足pattern字符串后续的长度,就无论如何都无法匹配下去了。譬如"abcde"和"applebanana",如果你"a"匹配了"applebana",那么pattern那边还有"bcde",但str那边就只剩下"na"了,怎么样匹配不上了。就可以直接略过

    public boolean wordPatternMatch(String pattern, String str) {
        HashMap<Character, String> patternMap = new HashMap<>(); 
        return _wordMatch(patternMap, new HashSet<>(), pattern, str, 0, 0);
    }
    
    private boolean _wordMatch(HashMap<Character, String> patternMap, HashSet<String> visited, String pattern, String str, int pPos, int sPos) {
        if (pPos == pattern.length() && sPos == str.length()) {
            return true;
        } else if (pPos == pattern.length() || sPos == str.length()) {
            return false;
        } else {
            char pCh = pattern.charAt(pPos);
            if (patternMap.containsKey(pCh)) {
                String matchedStr = patternMap.get(pCh);
                if (str.length() - sPos < matchedStr.length() || !matchedStr.equals(str.substring(sPos, sPos + matchedStr.length()))) {
                    return false;
                } else {
                    return _wordMatch(patternMap, visited, pattern, str, pPos + 1, sPos + matchedStr.length());
                }
            } else {
                for (int i = sPos + 1; i <= str.length() && str.length() - i >= pattern.length() - pPos - 1; i++) {
                    String subStr = str.substring(sPos, i);
                    if (visited.contains(subStr)) continue;

                    patternMap.put(pCh, subStr);
                    visited.add(subStr);
                    if (_wordMatch(patternMap, visited, pattern, str, pPos + 1, i)) {
                        return true;
                    }
                    visited.remove(subStr);
                    patternMap.remove(pCh);
                }
                
                return false;
            }
        }
    }

 

以下是对于这段代码的逐行注释与解释: ```cpp class Solution { // 定义了一个名为Solution的类 public: // public关键字表示接下来的内容是可以被外部访问的成员函数或变量 bool wordPattern(string pattern, string str) { // 声明并定义了公有的wordPattern函数,返回值类型为bool,参数是一个字符串pattern和另一个字符串str unordered_map<string, char> str2ch; // 创建一个unordered_map映射表str2ch用于存储从单词到字符的关系 unordered_map<char, string> ch2str; // 另外创建一个unordered_map映射表ch2str用于存储从字符到单词的关系 int m = str.length(); // 获取输入字符串str的长度,并赋值给整型变量m int i = 0; // 初始化索引i为0,用于遍历字符串str for (auto ch : pattern) { // 使用foreach循环迭代模式串pattern中的每一个字符 if (i >= m) { // 如果当前处理的位置已经超出str的实际范围,则匹配失败直接返回false return false; } int j = i; // 将j初始化为当前位置i,准备寻找下一个空格分隔符的位置 while (j < m && str[j] != ' ') { // 循环找到当前完整的单词位置(遇到空格为止) j++; // 移动指针直到遇到空格或者到达字符串末尾 } const string &tmp = str.substr(i, j - i); // 截取出当前单词存入临时引用tmp中,i是起始位置,j-i代表截取长度 if (str2ch.count(tmp) && str2ch[tmp] != ch) { // 若该单词已被关联过其他字符 或者 当前对应的字符不符合预期则返回false return false; } if (ch2str.count(ch) && ch2str[ch] != tmp) { // 同理检查如果这个字符已经被关联到了别的单词上也应返回false return false; } str2ch[tmp] = ch; // 更新双向哈希表建立新一对一关系 ch2str[ch] = tmp; i = j + 1; // 跳转至下一段未扫描部分继续查找新的单词片段 } return i >= m; // 最终判断整个字符串是否完全匹配完毕才成功否则仍需返回错误状态 } }; ``` ### 功能概述 此段程序是用来验证两个序列之间是否存在一种固定的一一对应关系。即通过`pattern`(由单个字母组成的短字符串)作为模板去比照目标句子`srt`里的各个词项之间的相互联系规则是否一致。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值