Leetcode【60、79、93、131、842】

问题描述:【Math】60. Permutation Sequence
解题思路:

这道题是一个从 1 到 n 的数组,共有 n! 个全排列序列,找到第 k 个全排列序列。

刚开始没仔细读题就之间 DFS 回溯,很快写好但超时了哈哈哈。再读一下题目,发现如果 k 很大,要一个个找过去,所以会很慢。

实际上,这是一道数学题(找规律)。以 n = 5 举例,共有 5!= 120 个排列,我们发现:当 k <= 24,排列全部以 1 开头;24 < k <= 48,排列全部以 2 开头,以此类推。因此,我们可以一位一位的构造答案,根据 k 值判断其落在哪个区间,找到开头数字加入结果;然后,从数组中删除该开头数字,并确定 k 值位于当前区间的第几个,更新 k 值;按照上述方法进行操作,直到得到一个全排列,就是最后的答案。

以 n = 5,k = 31 为例(nums = [1,2,3,4,5]):

  • k ∈ (4!, 2*4!],则确定 ans = "2",数组中删除 2(nums=[1,3,4,5]),k 更新为 k = 31 - 4! = 7 (k 位于该区间的第 7 个);
  • k ∈ (3!, 2*3!],则确定 ans = "23",数组中删除 3(nums=[1,4,5]),k 更新为 k = 7 - 3! = 1 (k 位于该区间的第 1 个);
  • k ∈ (0, 1*2!],则确定 ans = "231",数组中删除 1(nums=[4,5]),k 更新为 k = 1 - 0 = 1 (k 位于该区间的第 1 个);
  • k ∈ (0, 1*1!],则确定 ans = "2314",数组中删除 4(nums=[4]),k 更新为 k = 1 - 0 = 1 (k 位于该区间的第 1 个);
  • k ∈ (0, 1*0!],则确定 ans = "23145",数组中删除 5(nums=[]),k 更新为 k = 1 - 0 = 1 (k 位于该区间的第 1 个);
  • 得到一个全排列,最终结果 ans = "23145"
Python3 实现:
class Solution:
    def getPermutation(self, n: int, k: int) -> str:
        ans = ""
        nums = [(i + 1) for i in range(n)]
        while len(ans) != n:  # 迭代方法,逐个字符加入到结果中
            nf = math.factorial(len(nums) - 1)
            for i in range(len(nums)):
                if k <= (i + 1) * nf:  # 找到k位于哪个区间
                    ans += str(nums[i])
                    del nums[i]
                    k -= i * nf  # k位于当前区间的第几个
                    break
        return ans

问题描述:【DFS】79. Word Search
解题思路:

这道题是给一个 m*n 的字符矩阵 board 和一个单词 word,判断 word 是否存在字符矩阵中。

这道题很明显用 DFS 回溯法去解决。做法如下:

  • 在主函数中,遍历字符矩阵的每个坐标 (i, j),如果发现 board[i][j] == word[0],则将 board[i][j] 位置先改为 ""(因为每个位置字符只能使用一次),然后进入回溯函数 search(i, j, path, wind) (path 记录单词路径,wind 为 word 索引)进行深搜判断。如果没有在 search() 函数中找到,说明没有以当前字符 board[i][j] 开头的单词,要恢复原来该位置的字符。
  • 在回溯函数中,对于每个字符的上下左右四个位置进行深搜(要保证不越界),如果 board 的下一个位置的字符匹配 word 的下一个字符,则修改 board 中当前字符为 "" 进行递归调用。递归调用结束后,要先恢复原来该位置的字符,再去判断返回值是 True 还是 False。如果找到(返回值为 True,则返回 True),否则继续查找下一个位置。
Python3 实现:
class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        def search(i, j, path, wind):  # (i,j)是匹配word第一个字符的坐标,path记录结果,wind为word索引
            isfind = False
            if path == word:  # 找到了该单词
                return True
            for p in pos:
                if 0 <= i+p[0] < m and 0 <= j+p[1] < n and board[i+p[0]][j+p[1]] == word[wind]:
                    tmp = board[i+p[0]][j+p[1]]
                    board[i+p[0]][j+p[1]] = ""
                    isfind = search(i+p[0], j+p[1], path+word[wind], wind+1)
                    board[i+p[0]][j+p[1]] = tmp  # 先恢复原来的字符
                    if isfind == True:  # 再判断返回结果,如果是True直接返回;如果是False继续查找
                        return True
            return False
        
        if len(word) == 0 or len(board) == 0:
            return False
        pos = [[-1,0], [1,0], [0,-1], [0,1]]  # 上下左右四个位置
        m = len(board)
        n = len(board[0])
        for i in range(m):
            for j in range(n):
                if board[i][j] == word[0]:
                    tmp = board[i][j]
                    board[i][j] = ""
                    if search(i, j, word[0], 1):
                        return True
                    board[i][j] = tmp  # 如果没有找到,恢复原来的字符
        return False

问题描述:【DFS】93. Restore IP Addresses
解题思路:

这道题是给一个数字字符串,返回所有可能的 IP 地址组合。

这道题和下面的 Leetcode 131 以及 Leetcode 842 做法是类似的,也是使用回溯法 DFS 对字符串前缀进行划分。注意该深搜函数 search(s, path)(s 为后半部分字符串,path 为划分的 IP 子段)的几个出口:

  • 如果 len(path) > 4,不符合 IP 地址,提前终止,返回;
  • 如果 s 为空串,但是 len(path) < 3,说明划分结束,但是不符合 IP 地址,也返回;
  • 如果 s 为空串,且 len(path) == 4,说明找到一组解,就加入到结果列表 ans 中。

在 for 循环中,还要注意去除前导 0 以及字符串前缀数字 > 255 的情况,才能进行递归调用深搜。

Python3 实现:
class Solution:
    def restoreIpAddresses(self, s: str) -> List[str]:
        def search(s, path):
            if len(path) > 4:  # 不满足题意
                return
            if not s and len(path) < 4:  # 不满足题意
                return
            if not s and len(path) == 4:  # 找到一组解
                ans.append('.'.join(path))
                return
            for i in range(1, len(s) + 1):
                pre = s[:i]  # 划分前缀
                if (len(pre) > 1 and pre[0] == '0') or int(pre) > 255:  # 不满足题意
                    continue
                search(s[i:], path + [pre])
        
        ans = []
        search(s, [])
        return ans

问题描述:【Brute Force、DFS】131. Palindrome Partitioning
解题思路:

这道题是给一个字符串,将字符串分割成一些子串,使得所有子串都是回文串,求所有划分的方案数。

一个子串是否是回文串可以使用 s == s[::-1] 来判断。

方法1(Brute Force):

首先想到一种暴力解法,就是对于字符串的每个字符 s[i],依次将 s[i] 加入到回文串前缀列表中每个回文串前缀的后面,然后再判断 s[i] 的加入能否形成新的回文串前缀。如果可以,拓展回文串前缀列表。最后,遍历完所有字符后,列表中存储的就是最终结果。

以 s = "abba" 举例:

  • s[0] = a,a 本身是回文串,加入到结果列表 ans = [[a]];
  • s[1] = b,b 加入回文串前缀的后面,得到 ans = [[a,b]];
  • s[2] = b,b 先加入回文串前缀的后面,得到 ans = [[a,b,b]];然后发现,b 的加入可以形成新的回文串 "bb"(从最后一个 b 开始往前形成子串 bb),因此拓展结果列表得到 ans = [[a,b,b], [a,bb]];
  • s[3] = a,a 先加入回文串前缀的后面,得到 ans = [[a,b,b,a], [a,bb,a]];然后发现,a 的加入可以形成新的回文串 "abba"(注意到 ans 中两个都可以构成 "abba",所以还要判断新加入的回文串是否之前已经加入过),因此拓展结果列表得到 ans = [[a,b,b,a], [a,bb,a], [abba]]。

这种做法时间复杂度为 O(n^4),空间复杂度为 O(n^2),能够 AC

Python3 实现:

class Solution:
    def partition(self, s: str) -> List[List[str]]:
        ans = [[]]
        for i in range(len(s)):
            for a in ans:  # 将当前字符加入到每个字符串前缀列表的后面
                a.append(s[i])
            for a in ans:
                N = len(a)
                st = a[-1]
                for j in range(N-2, -1, -1):
                    st = a[j] + st  # 从最后一个字符开始构造新的子串
                    if st == st[::-1] and a[:j] + [st] not in ans:  # 判断新的子串是否是回文串,且构成的回文串前缀是否之前出现过
                        ans.append(a[:j] + [st])  # 将新的回文串前缀加入到结果列表中
        return ans

方法2(DFS):

其实这道题一看是求所有结果,很明显用 DFS 回溯法求解,但是刚开始没有思路,找不到如果求解的方法,最后看了别人的做法才明白。

使用回溯法的解题思路是对于字符串 s 的前缀进行划分,然后判断前缀是否是回文子串。如果是,形成临时结果,将 s 的后半部分和临时结果传入到下一层(深搜);如果不是,那就继续划分下一个前缀。最后,传入的 s 会变成空串,这时形成的结果必定是回文串的一个划分,加入到结果列表中即可。

以 s = "aab" 举例,用 path 记录一种划分结果:

  • 1、划分 s = "aab" 前缀 a,a 是回文串,将其加入 path = [a],并且同 s = "ab" 传入到下一层;
  • 2、划分 s = "ab" 前缀 a,a 是回文串,将其加入 path = [a,a],并且同 s = "b" 传入到下一层;
  • 3、划分 s = "b" 前缀 b,b 是回文串,将其加入 path = [a,a,b],并且同 s = "" 传入到下一层;
  • 4、因为 s = "",说明划分完毕,将结果 path = [a,a,b] 加入到 ans 中,直接返回到步骤 3(没有前缀,继续返回)、步骤 2;
  • 5、在步骤 2 中,划分 s = "ab" 前缀 ab,ab 不是回文串,继续划分下一个前缀;没有前缀,返回步骤 1;
  • 6、在步骤 1 中,划分 s = "aab" 前缀 aa,aa 是回文串,将其加入 path = [aa],并且同 s = "b" 传入到下一层;
  • 7、划分 s = "b" 前缀 b,b 是回文串,将其加入 path = [aa,b],并且同 s = "" 传入到下一层;
  • 8、因为 s = "",说明划分完毕,将结果 path = [aa,b] 加入到 ans 中,直接返回到步骤 7(没有前缀,继续返回)、步骤 6;
  • 9、在步骤 6 中,划分 s = "aab" 前缀 aab,aab 不是回文串,继续划分下一个前缀;没有前缀,结束。
  • 10、最终得到结果 ans = [[a,a,b], [aa,b]]。

优化:可以通过用区间 DP 来计算任意 s[i:j] 之间是否是回文串 【区间DP、双指针】647. Palindromic Substrings,并保存结果;然后再执行DFS,如果发现某条子串不是回文,就可以直接退出,从而减少计算量。

Python3 实现:

class Solution:
    def partition(self, s: str) -> List[List[str]]:
        def search(s, path):
            if not s:  # s 变为空串,将形成的一种结果path加入到ans中
                ans.append(path[:])
            for i in range(1, len(s) + 1):
                pre = s[:i]  # 对于s的每一个前缀
                if pre == pre[::-1]:  # 如果前缀是回文串,则深搜
                    search(s[i:], path + [pre])  # 将后半部分和临时结果传入到下一层
        
        ans = []
        search(s, [])
        return ans

问题描述:【DFS】842. Split Array into Fibonacci Sequence
解题思路:

这道题是给一个数字字符串 S,将其划分成斐波那契序列。

很容易想到用 DFS 回溯法去找斐波那契序列的一种划分。类似于上面的 Leetcode 131,对于数字字符串 S 的前缀进行划分,如果 S 的前缀合法(没有前导 0 并且数字没有越界),就将 S 的后半部分和临时结果 path 传入,进行递归调用。

递归出口:

  • 如果临时结果 path 长度大于等于 3,但是不满足 f[i-2] + f[i-1] = f[i],返回 False;
  • 如果临时结果 path 长度大于等于 3,且 S 为空串,说明划分完毕,保存结果ans.extend(path),返回 True。

第一次提交时,WA 了,报错如下:

检查了一下发现没什么问题啊?然后重新读题,很敏感的发现斐波那契序列数值要求为 <= 2 ** 31 - 1(2147483647),然后发现报错结果的最后一项 11336528511 > 2 ** 31 - 1,因此在代码中加入了数值范围的判断,就 AC 了。

Python3 实现:
class Solution:
    def splitIntoFibonacci(self, S: str) -> List[int]:
        def search(S, path):
            if len(path) >= 3 and path[-3] + path[-2] != path[-1]:  # 不满足斐波那契条件
                return False
            if not S and len(path) >= 3:  # S为空串,说明可以划分
                ans.extend(path)  # 找到一种划分,保存结果到ans中,返回
                return True
            for i in range(1, len(S) + 1):
                pre = S[:i]  # 划分前缀
                if (len(pre) > 1 and pre[0] == '0') or (int(pre) > 2 ** 31 - 1):  # 不满足题意
                    continue
                if search(S[i:], path + [int(pre)]):  # 将S后半部分和临时结果传入进行递归调用
                    return True
            return False  # S本身长度小于3
                
        ans = []
        search(S, [])
        return ans
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 4,310评论 0 5
  • 专业考题类型管理运行工作负责人一般作业考题内容选项A选项B选项C选项D选项E选项F正确答案 变电单选GYSZ本规程...
    小白兔去钓鱼阅读 8,892评论 0 13
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,204评论 0 17
  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,076评论 0 2
  • 早上老同学在朋友圈里发了这么一张图片,并配文字:别在花一样的年纪里,长成了一棵多肉植物。瘦下来就对了,就能遇见更好...
    言信阅读 132评论 0 4