同源字符串匹配 (深度优先搜索)

294 阅读3分钟

原题链接

题意解析

给定两个字符串,S1、S2,字符串中仅包含数字和小写字符,连续的数字串能代表相应数目的任意字符。

问两个串能否匹配?

解释下连续的数字串能代表相应数目的任意字母,如'ab12c'这个串,按照'12'这个数字子串的不同拆法,可以拆成1和2,或者12,(以字符?指代任意字母) 那么相应地可以匹配

  1. ab(?)(??)c
  2. ab(????????????)c

解法

回想字符串匹配问题,都是用动态规划解决的,那么这里是不是也可以这样解决呢?

动态规划问题最核心的是怎么表达状态。

传统的字符串匹配问题,都是用dp[i][j]代表字串S1[0:i]和子串S2[0:j]的匹配状态。那么这个问题依旧用(i,j)二元组代表状态可以吗?不可以,因为(i,j)的对应的子串有多种拆解方法(此处一个字符可以指代多个字符,如字符'9'可以指代9个字符,串'19'可以指代10个字符或者19个字符),(i,j)二元组并不足以表达一个完整的状态。

考虑加一维?那么新的一维代表什么呢?

i, j的范围都是40, 那么目前状态数已有1600,考虑到算法效率,第三维不会超过10000。

同时串中唯一存在多种拆法的子串只有纯数字部分,但数字子串代表任意字符,所以是无需关心具体是什么字符的。那么第三维可以表示成目前子串中末尾部分的数字串是多长。但如果两个串分开表示,需要增加两维,1000 * 1000,已经超过了10000的限制。不可行!

还可以在简化一下,我们并不关心两个串末尾到底都多长,我们只关心他们的长度是否匹配上。

因此第三维结合两个串的长度,第三维表示两个子串(S1_Prefix, S2_Prefix)目前末尾部分数字串的长度差。

代码(Python实现)

import icecream as ice


class Solution:
    def possiblyEquals(self, s1: str, s2: str) -> bool:
        n, m = len(s1), len(s2)
        status = {}
        ord1 = [ord(c) for c in s1]
        ord2 = [ord(c) for c in s2]
        zero_ord, nine_ord = ord('0'), ord('9')
        # i, j 分别是两个串当前位置的指针
        # diff是串1当前子串字符数-串2当前子串的字符数
        def dfs(i, j, diff):
            # ice.ic(i, j, diff)
            if i >= n and j >= m and diff == 0:
                return True
            if (i >= n and diff <= 0) or (j >= m and diff >= 0):
                return False
            if (i, j, diff) in status.keys():
                return status[(i, j, diff)]
            status_rep = (i, j, diff)
            if diff == 0 and ord1[i] not in range(zero_ord, nine_ord + 1) and ord2[j] not in range(zero_ord, nine_ord + 1):
                if ord1[i] != ord2[j]:
                    status[status_rep] = False
                    return False
                status[status_rep] = dfs(i + 1, j + 1, 0)
                return status[status_rep]
            current_status_available = False
            if diff <= 0:
                if i < n:
                    if ord1[i] in range(zero_ord, nine_ord + 1):
                        ti = i
                        num = 0
                        while ti < n and ord1[ti] in range(zero_ord, nine_ord + 1):
                            num = num * 10 + ord1[ti] - zero_ord
                            if dfs(ti + 1, j, diff + num):
                                current_status_available = True
                                break
                            ti += 1
                    else:
                        current_status_available = dfs(i + 1, j, diff + 1)
            else:
                if j < m:
                    if ord2[j] in range(zero_ord, nine_ord + 1):
                        tj = j
                        num = 0
                        while tj < m and ord2[tj] in range(zero_ord, nine_ord + 1):
                            num = num * 10 + ord2[tj] - zero_ord
                            if dfs(i, tj + 1, diff - num):
                                current_status_available = True
                                break
                            tj += 1
                    else:
                        current_status_available = dfs(i, j + 1, diff - 1)
            status[status_rep] = current_status_available
            return current_status_available
        return dfs(0, 0, 0)


def main():
    inputs = [
        ("ab", "1"),
        ("internationalization", "i18n"),
        ("l123e", "44"),
        ("a5b", "c5b"),
        ("112s", "g841"),
        ("ab", "a2"),
        ("7a59b6", "671a")
    ]
    outputs = [
        False,
        True,
        True,
        False,
        True,
        False,
        False
    ]
    sol = Solution()
    for idx, input in enumerate(inputs):
        expected = outputs[idx]
        actual = sol.possiblyEquals(input[0], input[1])
        ice.ic(actual, expected)

if __name__ == '__main__':
    main()
```