剑指Offer and Leetcode刷题总结之思想2:回溯算法

目录

 

回溯法题目总结(转自:从全排列问题开始理解「回溯」算法(深度优先遍历 + 状态重置 + 剪枝))

Leetcode22:括号生成

Leetcode46:全排序(不含重复)

Leetcode47:全排序(含重复) 

Leetcode39:组合总和(以下几题题解参考汇总)

Leetcode40:组合总和II

Leetcode78:子集/Subset

Leetcode90:子集II/SubsetII

Leetcode200:岛屿数量


回溯法题目总结(转自:从全排列问题开始理解「回溯」算法(深度优先遍历 + 状态重置 + 剪枝))

题目 提示
47. 全排列 II

思考一下,为什么造成了重复,如何在搜索之前就判断这一支会产生重复,从而“剪枝”。

17 .电话号码的字母组合  
22. 括号生成

这是字符串问题,没有显式回溯的过程。这道题广度优先遍历也很好写,可以通过这个问题理解一下为什么回溯算法都是深度优先遍历,并且都用递归来写。

39. 组合总和 使用题目给的示例,画图分析。
40. 组合总和 II  

51. N皇后

其实就是全排列问题,注意设计清楚状态变量。

60. 第k个排列

利用了剪枝的思想,减去了大量枝叶,直接来到需要的叶子结点。

77. 组合

组合问题按顺序找,就不会重复。并且举一个中等规模的例子,找到如何剪枝,这道题思想不难,难在编码。

78. 子集

为数不多的,解不在叶子结点上的回溯搜索问题。解法比较多,注意对比。

90. 子集 II

剪枝技巧同 47 题、39 题、40 题。

93. 复原IP地址

 

784. 字母大小写全排列

 

Leetcode22:括号生成

题目:数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

基本思路:回溯算法,左边'('有剩余的情况下可以继续增加'(';')'剩余数大于左括号剩余数时可添加右括号;

class Solution(object):
    def generateParenthesis(self, n):
        """
        :type n: int
        :rtype: List[str]
        """

        def dfs(path, left, right): # left and right表示左右括号剩余的个数
            if left == 0 and right == 0:
                ans.append(path)
                return
            if left > 0:
                dfs(path + '(', left - 1, right)
            if right > left:
                dfs(path + ')', left, right - 1)
        ans = []
        path = ''
        dfs(path, n, n)
        return ans

Leetcode46:全排序(不含重复)

题目:给定一个 没有重复 数字的序列,返回其所有可能的全排列。eg. [1, 2, 3]

基本思路:回溯法,track记录路线,元素之前不在track中,就append上,接着继续dfs;记住最后要pop();满了就返回return;

剑指Offer and Leetcode刷题总结之思想2:回溯算法_第1张图片

class Solution(object):
    def permute(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        def dfs(nums, track):
            if len(track) == len(nums):
                ans.append(track[:])
                return
            for i in nums:
                if i in track: continue
                track.append(i)
                dfs(nums, track)
                track.pop()     # pop掉最后一个数
        ans = []
        track = []
        dfs(nums, track)
        return ans

Leetcode47:全排序(含重复) 

题目:给定一个可包含重复数字的序列,返回所有不重复的全排列。

基本思路:需要有一个剪枝的操作,同时利用一个used()记录该元素是否被使用过,同时还需要对nums进行sort();

class Solution(object):
    def permuteUnique(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        def dfs(nums, ans, path, used):
            if len(nums) == len(path):
                ans.append(path[:])
                return
            else:
                for i in range(len(nums)):
                    if used[i] or (i > 0 and nums[i] == nums[i-1] and not used[i-1]):
                        continue # 剪枝操作
                    path.append(nums[i])
                    used[i] = True
                    dfs(nums, ans, path, used)
                    used[i] = False
                    path.pop()
        ans = []
        path = []
        used = [False for _ in range(len(nums))]
        nums.sort()              # 一定要进行sort()
        dfs(nums, ans, path, used)
        return ans

Leetcode39:组合总和(以下几题题解参考汇总)

题目:给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取说明:所有数字(包括 target)都是正整数;解集不能包含重复的组合 eg. input: candidates = [2, 3, 6, 7], target = 7;output: [[7], [2, 2, 3]]

基本思路:回溯算法的套路;同时为了避免重复,要记录一个start index,每次从start index处开始查找;

class Solution(object):
    def combinationSum(self, candidates, target):
        """
        :type candidates: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        def dfs(ans, candidates, remain, path, start):
            if remain < 0: return
            if remain == 0:
                ans.append(path[:])
            else:
                for i in range(start, len(candidates)):
                    path.append(candidates[i])
                    dfs(ans, candidates, remain-candidates[i], path, i) # not i + 1 because we can reuse same elements
                    path.pop()
        path = []
        ans = []
        candidates.sort()
        dfs(ans, candidates, target, path, 0)
        return ans

Leetcode40:组合总和II

题目: 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次

基本思路:相对于可以重复的leetcode39,仅有2点变化,提前剪枝 + start从i+1开始

class Solution(object):
    def combinationSum2(self, candidates, target):
        """
        :type candidates: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        def dfs(ans, path, candidates, remain, start):
            if remain < 0: return
            if remain == 0:
                ans.append(path[:])
            else:
                for i in range(start, len(candidates)):
                    if i > start and candidates[i] == candidates[i-1]: # skip duplicate
                        continue
                    path.append(candidates[i])
                    dfs(ans, path, candidates, remain - candidates[i], i+1) # 不能重复,变为i+1
                    path.pop()
        ans = []
        path = []
        candidates.sort()
        dfs(ans, path, candidates, target, 0)
        return ans

Leetcode78:子集/Subset

题目:给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。

基本思路:1. 没有条件要求,直接返回path;2. 记录start;3. 不能用重复元素,所以start从i+1开始;

class Solution(object):
    def subsets(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        def dfs(ans, path, nums, start):
            ans.append(path[:]) # 不用判定条件,直接append
            for i in range(start, len(nums)):
                path.append(nums[i])
                dfs(ans, path, nums, i + 1) # 不能重复使用,则用i+1
                path.pop()
        ans = []
        path = []
        nums.sort()
        dfs(ans, path, nums, 0)
        return ans

Leetcode90:子集II/SubsetII

题目:给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。

基本思路:1. 没有条件要求,直接返回path;2. 记录start;3. 不能用重复元素,所以start从i+1开始;4. 有重复元素,skip duplicate.

class Solution(object):
    def subsetsWithDup(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        def dfs(ans, nums, path, start):
            ans.append(path[:])
            for i in range(start, len(nums)):
                if i > start and nums[i] == nums[i-1]: # 有重复元素,用于去重
                    continue
                path.append(nums[i])
                dfs(ans, nums, path, i+1) # 不能重复用同一个元素,从i+1开始计算
                path.pop()
        ans = []
        path = []
        nums.sort()
        dfs(ans, nums, path, 0)
        return ans

Leetcode200:岛屿数量

题目:给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。

基本思路:方法1:bfs;方法2:dfs;

class Solution(object):
    """
    方法1:bfs
    """
    def numIslands(self, grid):
        def bfs(grid, i, j):
            queue = [[i, j]]
            while queue:
                [i, j] = queue.pop(0)
                if not len(grid) > i >= 0 or not len(grid[0]) > j >= 0 or grid[i][j] == '0':continue  # 不满足边界条件or值为‘0’,则跳过
                grid[i][j] = '0'                                  # 将当前的值为‘1’的格子变为‘0’               
                queue += [[i, j-1], [i-1, j], [i, j+1], [i+1, j]] # 将接壤的值为‘1’的格子都变为‘0’
        ans = 0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j] == '1':     # 找到了一个为‘1’的格子
                    bfs(grid, i, j)       # 将接壤的值为‘1’的格子都变为‘0’
                    ans += 1
        return ans

    """
    方法2:dfs
    """
    def numIslands(self, grid):
        def dfs(grid, i, j):
            grid[i][j] = '0'
            tmp = [(i, j-1), (i-1, j), (i, j+1), (i+1, j)]
            for (i, j) in tmp:
                if not len(grid) > i >= 0 or not len(grid[0]) > j >= 0 or grid[i][j] == '0':continue
                dfs(grid, i, j)

        ans = 0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j] == '1':
                    dfs(grid, i, j)
                    ans += 1
        return ans

Leetcode695:岛屿的最大面积

题目:给定一个包含了一些 0 和 1 的非空二维数组 grid 。一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)
基本思路:跟上题找岛屿数量的思路相同;注意每次的限制条件以及沉岛思想;

class Solution(object):
    def maxAreaOfIsland(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        def bfs(grid, i, j):
            if not 0 <= i < m or not 0 <= j < n or grid[i][j] == 0:
                return 0
            grid[i][j] = 0 # 沉岛思想
            cnt = 1
            cnt += bfs(grid, i-1, j)
            cnt += bfs(grid, i+1, j)
            cnt += bfs(grid, i, j-1)
            cnt += bfs(grid, i, j+1)
            return cnt

        m, n = len(grid), len(grid[0])
        ans = 0
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 1:
                    ans = max(ans, bfs(grid, i, j))
        return ans

Leetcode386:字典序排数

题目:给定一个整数 n, 返回从 到 的字典顺序。

示例:给定 n =13,返回 [1,10,11,12,13,2,3,4,5,6,7,8,9] 。

基本思路:dfs;从1..10,再在每个数进行dfs;主要终止条件以及append有效值即可;

时间复杂度:O(n) --  每个数都要遍历一遍;

空间复杂度:O(1) -- 不考虑递归调用函数栈;

相关面试题:1. n个数求安字典序排序第m小的数

class Solution(object):
    def lexicalOrder(self, n):
        """
        :type n: int
        :rtype: List[int]
        """
        if n < 1: return []
        def dfs(ans, cur):
            if cur > n: return # 终止条件,剪枝操作
            ans.append(cur)
            for j in range(10):
                dfs(ans, cur * 10 + j)
        ans = []
        for i in range(1, 10):
            dfs(ans, i)
        return ans

 

你可能感兴趣的:(剑指Offer and Leetcode刷题总结之思想2:回溯算法)