深度优先搜索算法-1

2019-08-09  本文已影响0人  钢笔先生

Time: 20190809

DFS: 深度优先搜索

本类题型,题目写得越多,就越有把握。

面试或笔试,DFS是必然会考到的类型。

BFS是一分努力十分收获。
DFS是一分努力一分收获。

大纲

DFS主要会涉及到以下几个方面的知识点:

什么时候用DFS?

找所有方案,找最优的方案,但后者大部分用的是动态规划方法解题,前者占90%。

组合搜索问题

问题:求所有满足条件的组合。
判断条件:组合中的元素顺序无关。
时间复杂度:O(2^n)

用递归的方式实现DFS会比较简单,是最常见的方式。

递归实现的三要素:

案例

Lintcode 135. 数字组合

题目描述

给定一个候选数字的集合 candidates 和一个目标值 target. 找到 candidates 中所有的和为 target 的组合.

在同一个组合中, candidates 中的某个数字不限次数地出现.

样例
样例 1:

输入: candidates = [2, 3, 6, 7], target = 7
输出: [[7], [2, 2, 3]]
样例 2:

输入: candidates = [1], target = 3
输出: [[1, 1, 1]]
注意事项
所有数值 (包括 target ) 都是正整数.
返回的每一个组合内的数字必须是非降序的.
返回的所有组合之间可以是任意顺序.

思路

碰到找所有方案的题,一定是DFS,90%的DFS的题,要么是排列要么是组合。

时刻与subsets问题做区分,下面是两个主要的区别:

所以,针对每个元素可以选择多次的问题,我们在递归时,下标不要+1,仍然传递当前的index,在递归出口处做判断。

代码

class Solution:
    """
    @param candidates: A list of integers
    @param target: An integer
    @return: A list of lists of integers
    """
    def combinationSum(self, candidates, target):
        # write your code here
        results = []
        if candidates == None:
            return results
        # 排序
        candidates.sort()
        combination = []
        self.recur(candidates, 0, target, combination, results)
        return results
    
    def recur(self, candidates, startIndex, remainTarget, combination, results):
        
       # 1.递归的出口
        if  remainTarget == 0:
            results.append(combination + []) # 添加新的内存对象
            return 
        # 2.递归的拆解
        for i in range(startIndex, len(candidates)):
            if remainTarget < candidates[i]:
                break
            # [2,2,3,3,6,7]
            if i != 0 and candidates[i] == candidates[i-1]:
                continue # 跳过重复的元素
            combination.append(candidates[i])
            self.recur(candidates, i, remainTarget - candidates[i], combination, results)
            # 不重复选取的方式
            # self.recur(candidates, i, remainTarget - candidates[i], combination, results)
            combination.pop()

上面这就是能AC的代码了。

相关问题

屏幕快照 2019-08-09 下午3.36.12.png

Lintcode 153. 数字组合II

题目描述

给定一个数组 num 和一个整数 target. 找到 num 中所有的数字之和为 target 的组合.

样例
样例 1:

输入: num = [7,1,2,5,1,6,10], target = 8
输出: [[1,1,6],[1,2,5],[1,7],[2,6]]
样例 2:

输入: num = [1,1,1], target = 2
输出: [[1,1]]
解释: 解集不能包含重复的组合
注意事项
在同一个组合中, num 中的每一个数字仅能被使用一次.
所有数值 (包括 target ) 都是正整数.
返回的每一个组合内的数字必须是非降序的.
返回的所有组合之间可以是任意顺序.
解集不能包含重复的组合.

思路

和上面的代码骨架基本相同,可直接阅读代码来看差别。

代码

class Solution:
    """
    @param num: Given the candidate numbers
    @param target: Given the target number
    @return: All the combinations that sum to target
    """
    
    # [1,1,2]
    # [1', 2] 
    # [1'', 2]
    # 上面两个算重复的,所以需要对最后的结果去重
    # subsets II中用到了这种去重的方式
    
    def combinationSum2(self, nums, target):
        # write your code here
        results = []
        if nums == None:
            return results
        # 上来排个序总是要的
        nums.sort()
        combination = []
        self.recur(nums, 0, combination, target, results)
        return results
        
    def recur(self, nums, startIndex, combination, remainTarget, results):
        # 1.递归出口
        if remainTarget == 0:
            results.append(combination + [])
            return
        
        # 2.递归拆解
        for i in range(startIndex, len(nums)):
            if remainTarget < nums[i]:
                break
            # 这个去重的方式和subsets II中用到的是一样的
            if i != 0 and nums[i] == nums[i-1] and i != startIndex:
                continue
            
            combination.append(nums[i])
            self.recur(nums,i + 1, combination, remainTarget - nums[i], results )
            combination.pop()

过程中搜索去重的方法:选代表。

1 1 1 2 ==> target = 4

1' 1'' 2
1' 1''' 2

  1. 分割回文串

所有的切割问题都是组合问题。

题目描述

描述
中文
English
给定字符串 s, 需要将它分割成一些子串, 使得每个子串都是回文串.

返回所有可能的分割方案.

不同的方案之间的顺序可以是任意的.
一种分割方案中的每个子串都必须是 s 中连续的一段.
您在真实的面试中是否遇到过这个题?
样例
样例 1:

输入: "a"
输出: [["a"]]
解释: 字符串里只有一个字符, 也就只有一种分割方式 (就是它本身)
样例 2:

输入: "aab"
输出: [["aa", "b"], ["a", "a", "b"]]
解释: 有两种分割的方式.
1. 将 "aab" 分割成 "aa" 和 "b", 它们都是回文的.
2. 将 "aab" 分割成 "a", "a" 和 "b", 它们全都是回文的.

思路

所有的分割问题都是组合问题,组合问题属于DFS类问题。

n个字母的切割问题,就是n - 1个数字的组合问题。

"abc"
a1b2c
a b c -> [1,2]
a bc -> [1]
ab c -> [2]
abc -> []

这样转换之后,就可以使用subsets问题的解法了。

代码

class Solution:
    """
    @param: s: A string
    @return: A list of lists of string
    """
    def partition(self, s):
        # write your code here
        results = []
        if s == None or len(s) == 0:
            return results
            
        partition = []
        self.recur(s, 0, partition, results)
        return results
        
    def recur(self, s, startIndex, partition, results):
        # 递归出口
        if startIndex == len(s):
            print("递归出口:", startIndex)
            results.append(partition + [])
            return
        for i in range(startIndex, len(s)):
            subString = s[startIndex: i + 1]
            print("subString: ", subString)
            
            # 如果不是回文串则跳过去
            if not self.isPalindrome(subString):
                continue
            
            partition.append(subString)
            self.recur(s, i + 1, partition, results)
            partition.pop()
    
    def isPalindrome(self, s):
        for i in range(len(s)):
            if s[i] != s[len(s) - i - 1] :
                return False
        return True

END.

上一篇下一篇

猜你喜欢

热点阅读