1. 程式人生 > >[Leetcode] Backtracking回溯法解題思路

[Leetcode] Backtracking回溯法解題思路

一個 quad adr == should store .com 理解 force

碎碎念: 最近終於開始刷middle的題了,對於我這個小渣渣確實有點難度,經常一兩個小時寫出一道題來。在開始寫的幾道題中,發現大神在discuss中用到回溯法(Backtracking)的概率明顯增大。感覺如果要順利的把題刷下去,必須先要把做的幾道題題總結一下。


先放上參考的web:

  1. https://segmentfault.com/a/1190000006121957
  2. http://summerisgreen.com/blog/2017-07-07-2017-07-07-算法技巧-backtracking.html
  3. http://www.leetcode.com

回溯法是什麽

回溯法跟DFS(深度優先搜索)的思想幾乎一致,過程都是窮舉

所有可能的情況。前者是一種找路方法,搜索的時候走不通就回頭換條路繼續走,後者是一種開路策略,就是一條道先走到頭,再往回走移步換一條路都走到頭,直到所有路都被走遍。

既然說了,這是一種窮舉法,也就是把所有的結果都列出來,那麽這就基本上跟最優的方法背道而馳,至少時間復雜度是這樣。但是不排除只能用這種方法解決的題目。

不過,該方法跟暴力(brute force)還是有一點區別的,至少動了腦子。

回溯法通常用遞歸實現,因為換條路繼續走的時候換的那條路又是一條新的子路。

回溯法何時用

高人說,如果你發現問題如果不窮舉一下就沒辦法知道答案,就可以用回溯了。

一般回溯問題分三種:

  1. Find a path to success 有沒有解
  2. Find all paths to success 求所有解
  • 求所有解的個數
  • 求所有解的具體信息

3.Find the best path to success 求最優解

理解回溯:

回溯可以抽象為一棵樹,我們的目標可以是找這個樹有沒有good leaf,也可以是問有多少個good leaf,也可以是找這些good leaf都在哪,也可以問哪個good leaf最好,分別對應上面所說回溯的問題分類。

回溯問題解決

有了之前對三個問題的分類,接下來就分別看看每個問題的初步解決方案。

有沒有解

boolean solve(Node n) {
    if n is a leaf node {
        if the leaf is a goal node, return true
        else return false
    } else {
        for each child c of n {
            if solve(c) succeeds, return true
        }
        return false
    }
}

這是一個典型的DFS的解決方案,目的只在於遍歷所有的情況,如果有滿足條件的情況則返回True

求所有解的個數

void solve(Node n) {
    if n is a leaf node {
        if the leaf is a goal node, count++, return;
        else return
    } else {
        for each child c of n {
            solve(c)
        }
    }
}

列舉所有解

這是文章的重點:

sol = []
def find_all(s, index, path, sol):
    if leaf node: ## (via index)
        if satisfy?(path):
            sol.append(path)
        return
    else:
        for c in choice():
            find_all(s[..:..], ,index+/-1, path+c, sol)

對於尋找存在的所有解的問題,一般不僅需要找到所有解,還要求找到的解不能重復。

這樣看著思路雖然簡單,但是在實際運用過程中需要根據題目進行改動,下面舉一個例子:

eg1: 18. 4Sum

Given an array nums of n integers and an integer target, are there elements a, b, c, and d in nums such that a + b + c + d = target? Find all unique quadruplets in the array which gives the sum of target.

Example:

Given array nums = [1, 0, -1, 0, -2, 2], and target = 0.
A solution set is:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]]

下面的代碼就是一個實際的回溯操作。

這個回溯函數有5個參數,nums是剩余數據,target是需要達到的條件,N為每層的遍歷次數。

result當前處理的結果。results為全局結果。

這是一個典型的python解法,之後很多題都是套這個模版,我發現。

def fourSum(self, nums, target):
    def findNsum(nums, target, N, result, results):
        if len(nums) < N or N < 2 or target < nums[0]*N or target > nums[-1]*N:  # early termination
            return
        if N == 2: # two pointers solve sorted 2-sum problem
            l,r = 0,len(nums)-1
            while l < r:
                s = nums[l] + nums[r]
                if s == target:
                    results.append(result + [nums[l], nums[r]])
                    l += 1
                    while l < r and nums[l] == nums[l-1]:
                        l += 1
                elif s < target:
                    l += 1
                else:
                    r -= 1
        else: # recursively reduce N
            for i in range(len(nums)-N+1):
                if i == 0 or (i > 0 and nums[i-1] != nums[i]):
                    findNsum(nums[i+1:], target-nums[i], N-1, result+[nums[i]], results)

    results = []
    findNsum(sorted(nums), target, 4, [], results)
    return results

第一個if為初始條件判斷。

第二個if判斷是否為葉結點,且是否滿足條件

else後面是對於非葉結點??進行遞歸。可以看到,在遞歸式中,N-1為判斷??葉結點依據,result+[nums[i]]為將當前結點加到後續處理中。

eg2: Restore IP Addresses

Given a string containing only digits, restore it by returning all possible valid IP address combinations.

Example:

Input: "25525511135"

Output: ["255.255.11.135", "255.255.111.35"]

幾乎是一模一樣的解法,在遞歸式中,s為數據,index為判斷是否為葉結點的依據,也可以說是限制條件。path為當前結果,res為全局結果。

def restoreIpAddresses(self, s):
    res = []
    self.dfs(s, 0, "", res)
    return res
    
def dfs(self, s, index, path, res):
    if index == 4:
        if not s:
            res.append(path[:-1])
        return # backtracking
    for i in xrange(1, 4):
        # the digits we choose should no more than the length of s
        if i <= len(s):
            #choose one digit
            if i == 1: 
                self.dfs(s[i:], index+1, path+s[:i]+".", res)
            #choose two digits, the first one should not be "0"
            elif i == 2 and s[0] != "0": 
                self.dfs(s[i:], index+1, path+s[:i]+".", res)
            #choose three digits, the first one should not be "0", and should less than 256
            elif i == 3 and s[0] != "0" and int(s[:3]) <= 255:
                self.dfs(s[i:], index+1, path+s[:i]+".", res)

eg3:39. Combination Sum

Given a set of candidate numbers (candidates) (without duplicates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target.
The same repeated number may be chosen from candidates unlimited number of times.

Example:

Input: candidates = [2,3,6,7], target = 7,
A solution set is:
[
[7],
[2,2,3]]

這道題就是為學以致用的題了。思路跟前兩道一模一樣,連coding的方式都一模一樣。果然,總結還是有好處的。

class Solution:
    def combinationSum(self, candidates, target):
        """
        :type candidates: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        sol = []
        def dfs(cands, rest, path, sol):
            if rest == 0:
                sol.append(path)
                return 
            elif rest < 0:
                return 
            else:
                for i, s in enumerate(cands):
                    dfs(cands[i:], rest-s, path+[s], sol)
        dfs(candidates, target, [], sol)
        return sol

尋找最優解

void solve(Node n) {
    if n is a leaf node {
        if the leaf is a goal node, update best result, return;
        else return
    } else {
        for each child c of n {
            solve(c)
        }
    }
}

[Leetcode] Backtracking回溯法解題思路