數(shù)據(jù)結(jié)構(gòu)與算法-DFS/回溯

回溯backtracking

回溯法思路的簡單描述是:把問題的解空間轉(zhuǎn)化成了圖或者樹的結(jié)構(gòu)表示吹缔,然后使用深度優(yōu)先搜索策略進行遍歷,遍歷的過程中記錄和尋找所有可行解或者最優(yōu)解。
基本思想類同于:
1.圖的深度優(yōu)先搜索
2.二叉樹的后序遍歷
分支限界法:廣度優(yōu)先搜索
思想類同于:圖的廣度優(yōu)先遍歷
二叉樹的層序遍歷



1. leetcode 39. Combination Sum

題目描述:給一個數(shù)組nums和一個目標數(shù)target,找出所有數(shù)組nums中的數(shù)組合和為target的組合沈矿,nums數(shù)組中的數(shù)可以
使用無限次,
Given a set of candidate numbers (C) (without duplicates) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.

The same repeated number may be chosen from C unlimited number of times.

Note:
All numbers (including target) will be positive integers.
The solution set must not contain duplicate combinations.
For example, given candidate set [2, 3, 6, 7] and target 7, 
A solution set is: 
[
  [7],
  [2, 2, 3]
]

code:

class Solution(object):
    def combinationSum(self, candidates, target):
        """
        :type candidates: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        self.resList = []
        candidates = sorted(candidates)
        self.dfs(candidates,[],target,0)
        return self.resList
    def dfs(self, candidates, sublist, target, last):
        if target == 0:
            self.resList.append(sublist[:])
        if target< candidates[0]:
            return 
        for n in candidates:
            if n > target:
                return
            if n < last: # not go back 
                continue
            sublist.append(n)
            self.dfs(candidates,sublist,target - n, n)
            sublist.pop()

if __name__ == '__main__':
    nums = [2,3,6,7]
    Solution = Solution()
    target = 7
    print(Solution.combinationSum(nums,target))

上面的方法咬腋,DFS傳參中需要傳入sublist,候選集,每次需要append進當前的元素睡互,因此最后需要pop()對數(shù)據(jù)出棧根竿。

自己的code2: 更易懂但效率低,每次要對path求和就珠,自然和為target則結(jié)果正確寇壳,添加到結(jié)果里,> target則return妻怎,否則繼續(xù)DFS

class Solution(object):
    def combinationSum(self, candidates, target):
        candidates.sort()
        res = []
        index = 0 
        self.dfs(candidates,index,target,[],res)
        return res
    
    def dfs(self,candidates,index,target,path,res):
        if sum(path) == target:
            res.append(path)
        if sum(path) > target:
            return 
        for i in range(index,len(candidates)):
            self.dfs(candidates,i,target,path+[candidates[i]],res)
            

更簡潔的寫法:摘自leetcode discuss:

class Solution(object):
    def combinationSum(self, candidates, target):
        res = []
        candidates.sort()
        self.dfs(candidates, target, 0, [], res)
        return res
        
    def dfs(self, nums, target, index, path, res):
        if target < 0:
            return  # backtracking
        if target == 0:
            res.append(path)
            return 
        for i in xrange(index, len(nums)):
            self.dfs(nums, target-nums[i], i, path+[nums[i]], res)

2.leetcode 40. Combination Sum II

題目和上題leetcode 39 基本類似壳炎,唯一不同是數(shù)組中的所有元素不是無限次使用,而是有多少只能用多少次

Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.

Each number in C may only be used once in the combination.

Note:
All numbers (including target) will be positive integers.
The solution set must not contain duplicate combinations.
For example, given candidate set [10, 1, 2, 7, 6, 1, 5] and target 8, 
A solution set is: 
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]

code1: 完全暴力逼侦,不考慮任何優(yōu)化匿辩,和leetcode 39的代碼的區(qū)別只在于dfs遞歸里的i變成了i+1(跳過本身),同時leetcode 39中數(shù)組是沒有重復元素的榛丢,leetcode 40里面可能有铲球,所以需要判斷重復,這里僅依靠一個字典d的O(1)判斷
可以ac leetcode的tests晰赞,但是速度很低

class Solution(object):
    def combinationSum2(self, candidates, target):
        candidates.sort()
        res = []
        index = 0 
        self.d = {}
        self.dfs(candidates,index,target,[],res)
        return res
    
    def dfs(self,candidates,index,target,path,res):
        if target == 0 :
            if tuple(path) not in self.d:
                res.append(path)
                self.d[tuple(path)] = 1
            return 
        if target < 0:
            return 
        for i in range(index,len(candidates)):
            self.dfs(candidates,i + 1,target - candidates[i],path+[candidates[i]],res)

code2:
考慮優(yōu)化:

  1. 當目前path為空稼病,即即將添加第一個元素的時候选侨,判斷之前res里是否已有答案是該元素開頭
比如target = 8, nums = [1, 1, 2, 5, 6, 7, 10]
則第一個1過完,到第二個1然走,path肯定已被第一個1的path所包含援制,就要跳過,否則
[1, 1, 6]
[1, 2, 5]
[1, 7]
[1, 2, 5]
[1, 7]
[2, 6]
可以看到出現(xiàn)了兩次(1芍瑞,2晨仑,5)和(1,7)

if i == index and nums[i] == res[-1][0]: # res[-1][0]是上一個答案的開頭元素
  1. 當目前的下標不在開頭啄巧,但是和之前的元素有重復寻歧,就直接跳過
比如 nums = [1,2,2,2,5] ,target = 5
答案會為
[1, 2, 2]
[1, 2, 2]
[1, 2, 2]
[5]
在遍歷過第一個2的時候就不應該再去看后面的兩個2了。
if i != index and candidates[i] == candidate[i-1]:
  continue

可以看出秩仆,優(yōu)化2的代碼其實也包含了優(yōu)化1码泛,同時由于不會重復,沒有必要再用字典d去保存已經(jīng)有的答案澄耍。

因此最終代碼為

class Solution(object):
    def combinationSum2(self, candidates, target):
        candidates.sort()
        res = []
        index = 0 
        self.dfs(candidates,index,target,[],res)
        return res
    
    def dfs(self,candidates,index,target,path,res):
        if target == 0 :噪珊、
            res.append(path)
            return 
        if target < 0:
            return 
        for i in range(index,len(candidates)):
            if i != index and candidates[i] == candidates[i-1]:
                continue
            self.dfs(candidates,i + 1,target - candidates[i],path+[candidates[i]],res)


二維數(shù)組的深度遍歷(參考leetcode17 Letter Combinations of a Phone Number

給一個數(shù)組[['1','2','3'], ['4','5','6'], ['7','8','9']]
生成排列的組合:(順序可以任意)
1,4,7,
1,4,8
1,4,9
1,5,7
1,5,8...
3,6,8,
3,6,9

def letter(nums):
    path = ''
    res = []
    index = 0
    dfs(nums,path,index,res)
    return res


def dfs(nums,path,index,res):
    if len(path) == len(nums):
        res.append(path)
        return

    for i in nums[index]:
        dfs(nums,path+i,index+1,res)


if __name__ == '__main__':
    nums = [['a','b','c'],['d','e','f'],['g','h','i','x']]
    # nums = ['a','']
    print(letter(nums))


leetcode22 Generate Parentheses

生成所有N個括號可能的組合
Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.

For example, given n = 3, a solution set is:

[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
分析:

  1. DFS返回正確結(jié)果條件,當前添加的path長度等于2 * n,且左邊括號數(shù)量要等于右邊括號
  2. DFS中途返回的條件:左邊或者右邊已經(jīng)添加的次數(shù)大于n
    或右邊比左邊添加的次數(shù)要多(如“())” 是不合法的)

方法1: 自己寫的:

class Solution(object):
    def generateParenthesis(self, n):
        """
        :type n: int
        :rtype: List[str]
        """
        nums = ['(',')']
        path, index, l, r = '', n, 0, 0
        res = []
        self.dfs(nums,path,index,res,l,r)
        return res
    def dfs(self,nums,path,index,res,l,r):
        if l > index or r > index or r > l:
            return 
        if len(path) == 2 * index:
            res.append(path)
        for j in nums:
            if j == '(':
                self.dfs(nums,path+j,index,res,l+1,r)
            else:
                self.dfs(nums,path+j,index,res,l,r+1)

方法2 leetcode discuss
l和r分別是長度為n的兩個棧齐莲,一個保存左括號痢站,一個保存右括號,分別出棧选酗,最后兩個都為空的時候為結(jié)果
如果右邊比左邊數(shù)量還少(已經(jīng)添加入path更多阵难,illegal),則中途退出,faster

class Solution(object):
    def generateParenthesis(self, n):
        """
        :type n: int
        :rtype: List[str]
        """
        #if not n:
        #    return []
        path,left,right,res ='',n,n,[]
        self.dfs(path,left,right,res)
        return res
    
    def dfs(self,path,left,right,res):
        if right < left:
            return 
        if not left and not right:
            res.append(path[:])
            return 
        if left > 0:
            self.dfs(path + '(',left - 1,right,res)
        if right > 0:
            self.dfs(path + ')',left ,right - 1,res)

按照這個思路也可以給方法1中的dfs的for訓練改成if

class Solution(object):
    def generateParenthesis(self, n):
        """
        :type n: int
        :rtype: List[str]
        """
        nums = ['(',')']
        path, index, l, r = '', n, 0, 0
        res = []
        self.dfs(nums,path,index,res,l,r)
        return res
    
    def dfs(self,nums,path,index,res,l,r):
        if r > l or l> index or r > index:
            return 
        if len(path) == 2 * index :
            res.append(path)
        if l <= index:
            self.dfs(nums,path+'(',index,res,l+1,r)
        if r <= index:
            self.dfs(nums,path+')',index,res,l,r+1)

全排列:

[1,2,3] 生成 123 132 213 231 312 321 六種全排列

  1. 網(wǎng)上常見的遞歸寫法:交換與開頭元素的位置
class Solution(object):
    def permute(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        end = len(nums)
        begin = 0
        global res 
        res = []
        self.perm(nums,begin,end)
        # res.append(perm(nums,begin,end))
        return res
    
    def perm(self,nums,begin,end):
    
        if begin >= end:
            tmp = nums[:]
            res.append(tmp) 
            
        else:
            for i in range(begin,end):
                nums[begin],nums[i] = nums[i],nums[begin]
                self.perm(nums,begin+1,end)
                nums[begin],nums[i] = nums[i],nums[begin]
  1. DFS寫法
    DFS是樹型結(jié)構(gòu)
    即第一個節(jié)點搜索1,2,3芒填,再每個節(jié)點下繼續(xù)搜索1呜叫,2,3殿衰,判斷return的原則一是碰到了重復元素(相當于剪枝)朱庆,二是得到正確結(jié)果
class Solution(object):
    def permute(self, nums):
        index,path = 0,[]
        res = []
        d = {}
        self.dfs(nums,index,path,res,d)

    def dfs(self,nums,index,path,res,d):
        if len(set(path)) != len(path):
            return
        if len(path) == len(nums): 
            print(path)
            return 
        for i in nums:
            self.dfs(nums,index,path+[i],res,d)

上面的方法中需要將python中的list轉(zhuǎn)化為set,比較長度來判斷l(xiāng)ist是否有重復來完成剪枝闷祥,list轉(zhuǎn)換set還是比較耗時的娱颊,因此另一種方法,直接在nums中去掉已經(jīng)添加的元素凯砍,代碼更簡潔也更高效
nums = nums[:i] + nums[i+1:]

class Solution(object):
    def permute(self, nums):
        path = []
        res = []
        length = len(nums)
        self.dfs(nums,path,length,res)
        return res
    
    def dfs(self,nums,path,length,res):
        if len(path) == length:
            res.append(path)
        for i in range(len(nums)):
            self.dfs(nums[:i] + nums[i+1:],path + [nums[i]],length,res)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末箱硕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子悟衩,更是在濱河造成了極大的恐慌颅痊,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件局待,死亡現(xiàn)場離奇詭異斑响,居然都是意外死亡菱属,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門舰罚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纽门,“玉大人,你說我怎么就攤上這事营罢∩土辏” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵饲漾,是天一觀的道長蝙搔。 經(jīng)常有香客問我,道長考传,這世上最難降的妖魔是什么吃型? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮僚楞,結(jié)果婚禮上勤晚,老公的妹妹穿的比我還像新娘。我一直安慰自己泉褐,他們只是感情好赐写,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著膜赃,像睡著了一般挺邀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上跳座,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天端铛,我揣著相機與錄音,去河邊找鬼躺坟。 笑死,一個胖子當著我的面吹牛乳蓄,可吹牛的內(nèi)容都是我干的咪橙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼虚倒,長吁一口氣:“原來是場噩夢啊……” “哼美侦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起魂奥,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤菠剩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后耻煤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體具壮,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡准颓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了棺妓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片攘已。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖怜跑,靈堂內(nèi)的尸體忽然破棺而出样勃,到底是詐尸還是另有隱情,我是刑警寧澤性芬,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布峡眶,位于F島的核電站,受9級特大地震影響植锉,放射性物質(zhì)發(fā)生泄漏辫樱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一汽煮、第九天 我趴在偏房一處隱蔽的房頂上張望搏熄。 院中可真熱鬧,春花似錦暇赤、人聲如沸心例。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽止后。三九已至,卻和暖如春溜腐,著一層夾襖步出監(jiān)牢的瞬間译株,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工挺益, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留歉糜,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓望众,卻偏偏與公主長得像匪补,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子烂翰,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

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