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

問題描述:【Math】60. Permutation Sequence
解題思路:

這道題是一個從 1 到 n 的數(shù)組,共有 n! 個全排列序列,找到第 k 個全排列序列。

剛開始沒仔細(xì)讀題就之間 DFS 回溯,很快寫好但超時了哈哈哈。再讀一下題目,發(fā)現(xiàn)如果 k 很大,要一個個找過去,所以會很慢。

實(shí)際上,這是一道數(shù)學(xué)題(找規(guī)律)。以 n = 5 舉例,共有 5!= 120 個排列,我們發(fā)現(xiàn):當(dāng) k <= 24,排列全部以 1 開頭;24 < k <= 48,排列全部以 2 開頭,以此類推。因此,我們可以一位一位的構(gòu)造答案,根據(jù) k 值判斷其落在哪個區(qū)間,找到開頭數(shù)字加入結(jié)果;然后,從數(shù)組中刪除該開頭數(shù)字,并確定 k 值位于當(dāng)前區(qū)間的第幾個,更新 k 值;按照上述方法進(jìn)行操作,直到得到一個全排列,就是最后的答案。

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

  • k ∈ (4!, 2*4!],則確定 ans = "2",數(shù)組中刪除 2(nums=[1,3,4,5]),k 更新為 k = 31 - 4! = 7 (k 位于該區(qū)間的第 7 個);
  • k ∈ (3!, 2*3!],則確定 ans = "23",數(shù)組中刪除 3(nums=[1,4,5]),k 更新為 k = 7 - 3! = 1 (k 位于該區(qū)間的第 1 個);
  • k ∈ (0, 1*2!],則確定 ans = "231",數(shù)組中刪除 1(nums=[4,5]),k 更新為 k = 1 - 0 = 1 (k 位于該區(qū)間的第 1 個);
  • k ∈ (0, 1*1!],則確定 ans = "2314",數(shù)組中刪除 4(nums=[4]),k 更新為 k = 1 - 0 = 1 (k 位于該區(qū)間的第 1 個);
  • k ∈ (0, 1*0!],則確定 ans = "23145",數(shù)組中刪除 5(nums=[]),k 更新為 k = 1 - 0 = 1 (k 位于該區(qū)間的第 1 個);
  • 得到一個全排列,最終結(jié)果 ans = "23145"
Python3 實(shí)現(xiàn):
class Solution:
    def getPermutation(self, n: int, k: int) -> str:
        ans = ""
        nums = [(i + 1) for i in range(n)]
        while len(ans) != n:  # 迭代方法,逐個字符加入到結(jié)果中
            nf = math.factorial(len(nums) - 1)
            for i in range(len(nums)):
                if k <= (i + 1) * nf:  # 找到k位于哪個區(qū)間
                    ans += str(nums[i])
                    del nums[i]
                    k -= i * nf  # k位于當(dāng)前區(qū)間的第幾個
                    break
        return ans

問題描述:【DFS】79. Word Search
解題思路:

這道題是給一個 m*n 的字符矩陣 board 和一個單詞 word,判斷 word 是否存在字符矩陣中。

這道題很明顯用 DFS 回溯法去解決。做法如下:

  • 在主函數(shù)中,遍歷字符矩陣的每個坐標(biāo) (i, j),如果發(fā)現(xiàn) board[i][j] == word[0],則將 board[i][j] 位置先改為 ""(因?yàn)槊總€位置字符只能使用一次),然后進(jìn)入回溯函數(shù) search(i, j, path, wind) (path 記錄單詞路徑,wind 為 word 索引)進(jìn)行深搜判斷。如果沒有在 search() 函數(shù)中找到,說明沒有以當(dāng)前字符 board[i][j] 開頭的單詞,要恢復(fù)原來該位置的字符。
  • 在回溯函數(shù)中,對于每個字符的上下左右四個位置進(jìn)行深搜(要保證不越界),如果 board 的下一個位置的字符匹配 word 的下一個字符,則修改 board 中當(dāng)前字符為 "" 進(jìn)行遞歸調(diào)用。遞歸調(diào)用結(jié)束后,要先恢復(fù)原來該位置的字符,再去判斷返回值是 True 還是 False。如果找到(返回值為 True,則返回 True),否則繼續(xù)查找下一個位置。
Python3 實(shí)現(xiàn):
class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        def search(i, j, path, wind):  # (i,j)是匹配word第一個字符的坐標(biāo),path記錄結(jié)果,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  # 先恢復(fù)原來的字符
                    if isfind == True:  # 再判斷返回結(jié)果,如果是True直接返回;如果是False繼續(xù)查找
                        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  # 如果沒有找到,恢復(fù)原來的字符
        return False

問題描述:【DFS】93. Restore IP Addresses
解題思路:

這道題是給一個數(shù)字字符串,返回所有可能的 IP 地址組合。

這道題和下面的 Leetcode 131 以及 Leetcode 842 做法是類似的,也是使用回溯法 DFS 對字符串前綴進(jìn)行劃分。注意該深搜函數(shù) search(s, path)(s 為后半部分字符串,path 為劃分的 IP 子段)的幾個出口:

  • 如果 len(path) > 4,不符合 IP 地址,提前終止,返回;
  • 如果 s 為空串,但是 len(path) < 3,說明劃分結(jié)束,但是不符合 IP 地址,也返回;
  • 如果 s 為空串,且 len(path) == 4,說明找到一組解,就加入到結(jié)果列表 ans 中。

在 for 循環(huán)中,還要注意去除前導(dǎo) 0 以及字符串前綴數(shù)字 > 255 的情況,才能進(jìn)行遞歸調(diào)用深搜。

Python3 實(shí)現(xiàn):
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
解題思路:

這道題是給一個字符串,將字符串分割成一些子串,使得所有子串都是回文串,求所有劃分的方案數(shù)。

一個子串是否是回文串可以使用 s == s[::-1] 來判斷。

方法1(Brute Force):

首先想到一種暴力解法,就是對于字符串的每個字符 s[i],依次將 s[i] 加入到回文串前綴列表中每個回文串前綴的后面,然后再判斷 s[i] 的加入能否形成新的回文串前綴。如果可以,拓展回文串前綴列表。最后,遍歷完所有字符后,列表中存儲的就是最終結(jié)果。

以 s = "abba" 舉例:

  • s[0] = a,a 本身是回文串,加入到結(jié)果列表 ans = [[a]];
  • s[1] = b,b 加入回文串前綴的后面,得到 ans = [[a,b]];
  • s[2] = b,b 先加入回文串前綴的后面,得到 ans = [[a,b,b]];然后發(fā)現(xiàn),b 的加入可以形成新的回文串 "bb"(從最后一個 b 開始往前形成子串 bb),因此拓展結(jié)果列表得到 ans = [[a,b,b], [a,bb]];
  • s[3] = a,a 先加入回文串前綴的后面,得到 ans = [[a,b,b,a], [a,bb,a]];然后發(fā)現(xiàn),a 的加入可以形成新的回文串 "abba"(注意到 ans 中兩個都可以構(gòu)成 "abba",所以還要判斷新加入的回文串是否之前已經(jīng)加入過),因此拓展結(jié)果列表得到 ans = [[a,b,b,a], [a,bb,a], [abba]]。

這種做法時間復(fù)雜度為 O(n^4),空間復(fù)雜度為 O(n^2),能夠 AC

Python3 實(shí)現(xiàn):

class Solution:
    def partition(self, s: str) -> List[List[str]]:
        ans = [[]]
        for i in range(len(s)):
            for a in ans:  # 將當(dāng)前字符加入到每個字符串前綴列表的后面
                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  # 從最后一個字符開始構(gòu)造新的子串
                    if st == st[::-1] and a[:j] + [st] not in ans:  # 判斷新的子串是否是回文串,且構(gòu)成的回文串前綴是否之前出現(xiàn)過
                        ans.append(a[:j] + [st])  # 將新的回文串前綴加入到結(jié)果列表中
        return ans

方法2(DFS):

其實(shí)這道題一看是求所有結(jié)果,很明顯用 DFS 回溯法求解,但是剛開始沒有思路,找不到如果求解的方法,最后看了別人的做法才明白。

使用回溯法的解題思路是對于字符串 s 的前綴進(jìn)行劃分,然后判斷前綴是否是回文子串。如果是,形成臨時結(jié)果,將 s 的后半部分和臨時結(jié)果傳入到下一層(深搜);如果不是,那就繼續(xù)劃分下一個前綴。最后,傳入的 s 會變成空串,這時形成的結(jié)果必定是回文串的一個劃分,加入到結(jié)果列表中即可。

以 s = "aab" 舉例,用 path 記錄一種劃分結(jié)果:

  • 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、因?yàn)?s = "",說明劃分完畢,將結(jié)果 path = [a,a,b] 加入到 ans 中,直接返回到步驟 3(沒有前綴,繼續(xù)返回)、步驟 2;
  • 5、在步驟 2 中,劃分 s = "ab" 前綴 ab,ab 不是回文串,繼續(xù)劃分下一個前綴;沒有前綴,返回步驟 1;
  • 6、在步驟 1 中,劃分 s = "aab" 前綴 aa,aa 是回文串,將其加入 path = [aa],并且同 s = "b" 傳入到下一層;
  • 7、劃分 s = "b" 前綴 b,b 是回文串,將其加入 path = [aa,b],并且同 s = "" 傳入到下一層;
  • 8、因?yàn)?s = "",說明劃分完畢,將結(jié)果 path = [aa,b] 加入到 ans 中,直接返回到步驟 7(沒有前綴,繼續(xù)返回)、步驟 6;
  • 9、在步驟 6 中,劃分 s = "aab" 前綴 aab,aab 不是回文串,繼續(xù)劃分下一個前綴;沒有前綴,結(jié)束。
  • 10、最終得到結(jié)果 ans = [[a,a,b], [aa,b]]。

優(yōu)化:可以通過用區(qū)間 DP 來計算任意 s[i:j] 之間是否是回文串 【區(qū)間DP、雙指針】647. Palindromic Substrings,并保存結(jié)果;然后再執(zhí)行DFS,如果發(fā)現(xiàn)某條子串不是回文,就可以直接退出,從而減少計算量。

Python3 實(shí)現(xiàn):

class Solution:
    def partition(self, s: str) -> List[List[str]]:
        def search(s, path):
            if not s:  # s 變?yōu)榭沾瑢⑿纬傻囊环N結(jié)果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])  # 將后半部分和臨時結(jié)果傳入到下一層
        
        ans = []
        search(s, [])
        return ans

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

這道題是給一個數(shù)字字符串 S,將其劃分成斐波那契序列。

很容易想到用 DFS 回溯法去找斐波那契序列的一種劃分。類似于上面的 Leetcode 131,對于數(shù)字字符串 S 的前綴進(jìn)行劃分,如果 S 的前綴合法(沒有前導(dǎo) 0 并且數(shù)字沒有越界),就將 S 的后半部分和臨時結(jié)果 path 傳入,進(jìn)行遞歸調(diào)用。

遞歸出口:

  • 如果臨時結(jié)果 path 長度大于等于 3,但是不滿足 f[i-2] + f[i-1] = f[i],返回 False;
  • 如果臨時結(jié)果 path 長度大于等于 3,且 S 為空串,說明劃分完畢,保存結(jié)果ans.extend(path),返回 True。

第一次提交時,WA 了,報錯如下:

檢查了一下發(fā)現(xiàn)沒什么問題啊?然后重新讀題,很敏感的發(fā)現(xiàn)斐波那契序列數(shù)值要求為 <= 2 ** 31 - 1(2147483647),然后發(fā)現(xiàn)報錯結(jié)果的最后一項(xiàng) 11336528511 > 2 ** 31 - 1,因此在代碼中加入了數(shù)值范圍的判斷,就 AC 了。

Python3 實(shí)現(xiàn):
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)  # 找到一種劃分,保存結(jié)果到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后半部分和臨時結(jié)果傳入進(jìn)行遞歸調(diào)用
                    return True
            return False  # S本身長度小于3
                
        ans = []
        search(S, [])
        return ans
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 官網(wǎng) 中文版本 好的網(wǎng)站 Content-type: text/htmlBASH Section: User ...
    不排版閱讀 4,462評論 0 5
  • 專業(yè)考題類型管理運(yùn)行工作負(fù)責(zé)人一般作業(yè)考題內(nèi)容選項(xiàng)A選項(xiàng)B選項(xiàng)C選項(xiàng)D選項(xiàng)E選項(xiàng)F正確答案 變電單選GYSZ本規(guī)程...
    小白兔去釣魚閱讀 9,055評論 0 13
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 6,523評論 0 17
  • 在C語言中,五種基本數(shù)據(jù)類型存儲空間長度的排列順序是: A)char B)char=int<=float C)ch...
    夏天再來閱讀 3,402評論 0 2
  • 早上老同學(xué)在朋友圈里發(fā)了這么一張圖片,并配文字:別在花一樣的年紀(jì)里,長成了一棵多肉植物。瘦下來就對了,就能遇見更好...
    言信閱讀 150評論 0 4