单词规律 II

Submission

运行时间: 27 ms

内存: 0.0 MB

class Solution:
    def wordPatternMatch(self, pattern: str, s: str) -> bool:
        # Backtracking
        # Inputs:
        #   dict1: char to word
        #   dict2: word to char
        #   remaining pattern and s
        n1, n2 = len(pattern), len(s)
        if n1 == 0 and n2 == 0:
            return True
        if n1 == 1 and n2 > 0:
            return True
        
        return self.backtracking(dict(), dict(), pattern, s)
        
    
    def backtracking(self, dict1, dict2, pattern, s):
        print(dict1, dict2, pattern, s)
        n1, n2 = len(pattern), len(s)
        if n1 == 0 and n2 == 0:
            return True
        elif n1 == 0 and n2 > 0:
            return False
        elif n1 > 0 and n2 == 0:
            return False
        else:
            c = pattern[0]
            if c in dict1:
                word1 = dict1[c]
                if (word1 not in dict2) or (dict2[word1] != c):
                    return False
                l = len(word1)
                word2 = s[:l]
                if word2 != word1:
                    return False
                else:
                    return self.backtracking(dict1, dict2, pattern[1:], s[l:])
            else:
                for l in range(1, n2-n1+2):
                    word = s[:l]
                    if word in dict2:
                        continue
                    dict1New, dict2New = dict1.copy(), dict2.copy()
                    dict1New[c] = word
                    dict2New[word] = c
                    flag = self.backtracking(dict1New, dict2New, pattern[1:], s[l:])
                    if flag:
                        return True
                return False



        

Explain

这是一道使用回溯法解决的问题。主要思路是将模式字符串pattern和单词字符串s同时进行匹配和拆分。使用两个字典dict1和dict2分别存储模式字符到单词、单词到模式字符的映射关系。在回溯过程中,如果当前模式字符已经有映射的单词,则检查映射是否一致;如果当前模式字符还没有映射单词,则枚举当前单词字符串的所有前缀作为可能的映射,递归处理剩余的模式字符串和单词字符串。如果匹配成功则返回True,否则继续尝试其他的映射方案,直到所有可能的映射组合都尝试完毕。

时间复杂度: O(m^n)

空间复杂度: O(n)

class Solution:
    def wordPatternMatch(self, pattern: str, s: str) -> bool:
        # Backtracking
        # Inputs:
        #   dict1: char to word
        #   dict2: word to char
        #   remaining pattern and s
        n1, n2 = len(pattern), len(s)
        if n1 == 0 and n2 == 0:
            return True
        if n1 == 1 and n2 > 0:
            return True
        
        return self.backtracking(dict(), dict(), pattern, s)
        
    
    def backtracking(self, dict1, dict2, pattern, s):
        print(dict1, dict2, pattern, s)
        n1, n2 = len(pattern), len(s)
        if n1 == 0 and n2 == 0:
            # Base case: both pattern and s are empty, match successfully
            return True
        elif n1 == 0 and n2 > 0:
            # Pattern is empty but s is not, match fails
            return False
        elif n1 > 0 and n2 == 0:
            # Pattern is not empty but s is empty, match fails
            return False
        else:
            c = pattern[0]
            if c in dict1:
                # Current pattern char is already mapped to a word
                word1 = dict1[c]
                if (word1 not in dict2) or (dict2[word1] != c):
                    # Mapping is inconsistent, match fails
                    return False
                l = len(word1)
                word2 = s[:l]
                if word2 != word1:
                    # Current substring doesn't match the mapped word, match fails
                    return False
                else:
                    # Current substring matches the mapped word, recursively match the remaining parts
                    return self.backtracking(dict1, dict2, pattern[1:], s[l:])
            else:
                # Current pattern char is not mapped, try all possible mappings
                for l in range(1, n2-n1+2):
                    word = s[:l]
                    if word in dict2:
                        # Current substring is already mapped to another pattern char, skip
                        continue
                    dict1New, dict2New = dict1.copy(), dict2.copy()
                    dict1New[c] = word
                    dict2New[word] = c
                    flag = self.backtracking(dict1New, dict2New, pattern[1:], s[l:])
                    if flag:
                        # Found a valid mapping, return True
                        return True
                # Tried all possible mappings but none works, return False
                return False

Explore

在递归过程中,每次尝试建立新的映射时,都会创建dict1和dict2的副本。这样,每个递归调用都有自己的字典副本,不会影响到其他递归分支的字典状态。当递归函数尝试并回退某一路径时,之前的字典状态因为使用副本,所以仍保持未更改的状态,从而确保每次递归中映射的一致性。

算法确实保证了模式字符到单词的双向唯一性映射。首先,通过检查当前单词是否已存在于dict2中,确保不会将一个单词映射到多个模式字符。其次,每次建立新映射时,同时更新dict1和dict2,确保从模式字符到单词和从单词到模式字符的映射都是一致的。如果任一映射已存在冲突,则不会尝试该映射,从而维持了双向唯一性。

在递归函数中提前返回False可以有效剪枝,即在确定当前路径不可能导致成功匹配的情况下,及时停止进一步的无效递归调用,从而减少计算量和提高算法效率。这种设计在处理复杂的回溯问题时尤为重要,因为它可以避免在已知失败的路径上浪费计算资源。

循环的范围`range(1, n2-n1+2)`是基于剩余模式字符和剩余单词长度的关系来确定的。这个范围确保了,即使每个剩余的模式字符至少映射一个单词字符,仍有足够的单词字符可供映射。例如,若剩余模式长度为1,那么应该考虑将所有剩余的单词字符串作为一个可能的映射,而如果剩余模式长度等于剩余单词长度,每个模式字符只能映射一个单词字符,因此这个范围可以动态调整映射尝试的长度,以适应不同的模式和单词长度情况。