Leetcode 936:戳印序列(超详细的解法!!!)

你想要用小写字母组成一个目标字符串 target

开始的时候,序列由 target.length'?' 记号组成。而你有一个小写字母印章 stamp

在每个回合,你可以将印章放在序列上,并将序列中的每个字母替换为印章上的相应字母。你最多可以进行 10 * target.length 个回合。

举个例子,如果初始序列为 “???”,而你的印章 stamp"abc",那么在第一回合,你可以得到 “abc??”、"?abc?"、"??abc"。(请注意,印章必须完全包含在序列的边界内才能盖下去。)

如果可以印出序列,那么返回一个数组,该数组由每个回合中被印下的最左边字母的索引组成。如果不能印出序列,就返回一个空数组。

例如,如果序列是 “ababc”,印章是 "abc",那么我们就可以返回与操作 “???” -> “abc??” -> “ababc” 相对应的答案 [0, 2]

另外,如果可以印出序列,那么需要保证可以在 10 * target.length 个回合内完成。任何超过此数字的答案将不被接受。

示例 1:

输入:stamp = "abc", target = "ababc"
输出:[0,2]
([1,0,2] 以及其他一些可能的结果也将作为答案被接受)

示例 2:

输入:stamp = "abca", target = "aabcaca"
输出:[3,0,1]

提示:

  1. 1 <= stamp.length <= target.length <= 1000
  2. stamptarget 只包含小写字母。

解题思路

我们首先想到的最简单的方法是通过倒退回到初始点。对于第一个例子的target,如果我们退回一步的话就变成了ab???,接着再退回一步就变成了?????(也就是初始状态)。

我们只要倒退的过程中记录stamptarget中的位置即可。依照这个思路去做的话,我们上来会碰到的问题就是如何替换stamp为?。一个非常简单的思路就是从头到尾的遍历和target找可以和stamp匹配的位置(?可以和所有字符匹配),使用的方法是最简单的逐个字符比较,如果匹配成功,我们还要检查target是不是都是?,如果都是的话,那么显然我们已经有结果了,不需要继续下去了。

我们还有一个边界情况需要考虑,也就是如果我们从头至尾没有匹配到任何的stamp的话,我们应该返回list()

class Solution:
    def movesToStamp(self, stamp: str, target: str) -> List[int]:
        t_len, s_len, target = len(target), len(stamp), list(target)
        
        def check(i):
            res = False
            for a, b in zip(target[i:i + s_len], stamp):
                if a != '?':
                    if a != b: 
                        return False
                    res = True
            return res

        done, moves = ['?']*t_len, []
        while target != done:
            move = False
            for i in range(t_len - s_len + 1):
                if check(i):
                    move = True
                    moves.append(i)
                    target[i:i + s_len] = done[:s_len]
            if not move: 
                return [] 
        return moves[::-1]

这个问题显然还可以通过递归去解决。例如:

target: a b a b c #
   	    i
stamp:  a b c #
   	    j

当我们发现target[i] == stamp[j]的时候,此时我们首先看target[i+1]stamp[j+1]是不是匹配,如果不可行(也就是没有解),那么需要在i+1的地方记录一个戳印。例如:

target: a b a b c #
            i
stamp: 	a b c #
   	        j

此时target[i] != stamp[j],我们需要在i的位置盖上一个戳印。

考虑边界问题,当i==len(target)并且j==len(stamp)的时候,此时说明有解,返回结果即可,否则返回空。当j==len(stamp),但i!=len(target)的时候,表示匹配一个stamp,例如:

target: a b c b c #
              i
stamp: 	a b c #
   	          j

此时,我们应该重新去匹配stamp,但是j的起始位置可以有k=range(len(stamp))个选择,并且对应的戳印位置为i-k。例如,如果此时j1起始,那么i-1的位置应该有一个戳印。

target: a b c b c #
   	        i
stamp: 	a b c #
   	      j

最后代码如下:

class Solution:
    def movesToStamp(self, stamp: str, target: str) -> List[int]:
        t_len, s_len = len(target), len(stamp)
        mem = dict()

        def dfs(i, j, paths):
            if (i, j) in mem:
                return mem[(i, j)]

            if i == t_len:
                return paths if j == s_len else []
            
            res = []
            if j == s_len:
                for k in range(s_len):
                    res = dfs(i, k, [i - k] + paths)
                    if res:
                        break
            elif target[i] == stamp[j]:
                res = dfs(i + 1, j + 1, paths) or dfs(i + 1, 0, paths + [i + 1])
            mem[(i, j)] = res
            return res

        return dfs(0, 0, [0])

reference:

https://leetcode.com/problems/stamping-the-sequence/discuss/189502/C++Python-12ms-Reverse-and-Greedy-O(N2M)

我将该问题的其他语言版本添加到了我的GitHub Leetcode

如有问题,希望大家指出!!!

你可能感兴趣的:(leetcode,Problems,leetcode解题指南)