LeetCode 题集:回溯法和递归(一)数组相关问题

本文介绍 LeetCode 题集中,使用回溯法(递归)解决数组相关的问题。

LeetCode 其他有关回溯法的问题:
LeetCode 题集:回溯法和递归(二)字符串相关问题
LeetCode 题集:回溯法和递归(三)矩阵相关问题


46. Permutations(全排列)


问题描述

LeetCode 46 问题描述

思路与代码


本题的思路为,基于回溯法的思想,通过不断交换两个位置的数字,完成对所有可能排列的遍历,存储并输出结果。

代码如下:

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        def swap(list_num: List[int], i: int, j: int):
            list_num[i], tmp = list_num[j], list_num[i]
            list_num[j] = tmp

        def dfs(list_num: List[int], pos: int, list_pmt: List[List[int]]):
            """
            深度优先搜索的递归函数

            :param list_num:  当前数组
            :param pos:  当前交换点的位置
            :param list_pmt:  结果列表

            :return:  无
            """

            if pos == len(list_num) - 1:
                list_pmt.append(list_num.copy())  # 注意 copy
                return

            for i in range(pos, len(list_num)):
                swap(list_num=list_num, i=pos, j=i)  # 交换两个位置的数字
                dfs(list_num=list_num, pos=pos + 1, list_pmt=list_pmt)  # 交换点的位置右移一位
                swap(list_num=list_num, i=pos, j=i)  # 把两个位置的数字交换回来

        list_pmt_ = []
        dfs(list_num=nums, pos=0, list_pmt=list_pmt_)

        return list_pmt_

运行效果:
LeetCode 46 运行效果


47. Permutations II(全排列 II)


问题描述

LeetCode 47 问题描述

思路与代码


本题为前一题的变体,不同之处在于数组中可能有重复元素,但全排列的结果中不能存在重复,处理掉该情况即可解决问题。

具体代码如下:

class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        def swap(list_num: List[int], i: int, j: int):
            list_num[i], tmp = list_num[j], list_num[i]
            list_num[j] = tmp

        def dfs(list_num: List[int], pos: int, list_pmt: List[List[int]]):
            """
            深度优先搜索的递归函数

            :param list_num:  当前数组
            :param pos:  当前交换点的位置
            :param list_pmt:  结果列表

            :return:  无
            """

            if pos == len(list_num) - 1:
                list_pmt.append(list_num.copy())  # 注意 copy
                return

            for i in range(pos, len(list_num)):
                if_swap = True
                for j in range(pos, i):
                    if list_num[j] == list_num[i]:  # 若遍历过的位置中出现过当前位置的数字,则不交换
                        if_swap = False
                        break

                if if_swap:
                    swap(list_num=list_num, i=pos, j=i)  # 交换两个位置的数字
                    dfs(list_num=list_num, pos=pos + 1, list_pmt=list_pmt)  # 交换点的位置右移一位
                    swap(list_num=list_num, i=pos, j=i)  # 把两个位置的数字交换回来

        list_pmt_ = []
        dfs(list_num=nums, pos=0, list_pmt=list_pmt_)

        return list_pmt_

运行效果:
LeetCode 47 运行效果


39. Combination Sum(组合总和)


问题描述

LeetCode 39 问题描述 I
LeetCode 39 问题描述 II

思路与代码


本题的思路,将数组降序排列,然后从每个位置进行递归,对于以每个位置为起点的组合,只能从当前位置及其后面的数字中寻找,及确保组合的不重复性。

具体代码如下:

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        def backtrack(list_cdd: List[int], tgt: int, pos: int, group: List[int], list_group: List[List[int]]):
            if not tgt:
                list_group.append(group.copy())
                return

            for i in range(pos, len(list_cdd)):
                if list_cdd[i] > tgt:
                    continue

                group.append(list_cdd[i])
                backtrack(list_cdd=list_cdd, tgt=tgt - list_cdd[i], pos=i, 
                          group=group, list_group=list_group)  # 更新 target;以当前位置 i 为起始位置 pos 进行递归
                group.pop()

        list_cdd_ = sorted(candidates, reverse=True)  # 数组降序排列
        list_group_ = []
        backtrack(list_cdd=list_cdd_, tgt=target, pos=0, group=[], list_group=list_group_)

        return list_group_

运行效果:
LeetCode 39 运行效果


40. Combination Sum II(组合总和 II)


问题描述

LeetCode 40 问题描述

思路与代码


本题为前一题的变体,不同之处在于备选数组中存在重复元素,而在结果中,重复元素出现的次数不能超过备选数组中元素的重复次数。

代码直接在前一题的基础上进行修改即可,改动有两处,其一,在同一起始位置的循环过程中,跳过与前一位置数值相同的位置;其二,下次递归从当前位置的下一个位置开始,以保证当前位置的数字不被重复使用。

具体代码如下:

class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        def backtrack(list_cdd: List[int], tgt: int, pos: int, group: List[int], list_group: List[List[int]]):
            if not tgt:
                list_group.append(group.copy())
                return

            for i in range(pos, len(list_cdd)):
                if list_cdd[i] > tgt:
                    continue

                if i > pos and list_cdd[i] == list_cdd[i - 1]:  # 若所在位置与同次循环的前一位置数值相同,则跳过
                    continue

                group.append(list_cdd[i])
                backtrack(list_cdd=list_cdd, tgt=tgt - list_cdd[i], pos=i + 1, 
                          group=group, list_group=list_group)  # 更新 target;以下一个位置 i + 1 为起始位置 pos 进行递归
                group.pop()

        list_cdd_ = sorted(candidates, reverse=True)  # 数组降序排列
        list_group_ = []
        backtrack(list_cdd=list_cdd_, tgt=target, pos=0, group=[], list_group=list_group_)

        return list_group_

运行效果:
LeetCode 40 运行效果


77. Combinations(组合)


问题描述

LeetCode 77 问题描述

思路与代码


有了前面几题解决排列组合问题的经验,解决本题会相对简单(因为只是做一个简单的组合枚举),代码如下:

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        def backtrack(list_num: List[int], m: int, pos: int, group: List[int], list_group: List[List[int]]):
            if not m:
                list_group.append(group.copy())
                return

            for i in range(pos, len(list_num)):
                group.append(list_num[i])
                backtrack(list_num=list_num, m=m - 1, pos=i + 1, group=group, list_group=list_group)
                group.pop()

        list_num_ = list(range(1, n + 1))
        list_group_ = []
        backtrack(list_num=list_num_, m=k, pos=0, group=[], list_group=list_group_)

        return list_group_

但运行时发现,效率并不高:
LeetCode 77 运行效果 1

由于本题是单纯的组合枚举,因此其实可以通过非递归的循环来实现,思路类比进位制,具体代码如下:

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        group = [i for i in range(1, k + 1)]
        list_group = [group.copy()]

        while True:
            for i in range(k - 1, -1, -1):  # 从最末一位向前迭代,类似进位制
                if group[i] < n - (k - 1) + i:  # 当前位置的最大数字
                    group[i] += 1  # 当前位置的数字 +1
                    for j in range(i + 1, k):
                        group[j] = group[j - 1] + 1  # 当前位置后面的数字,在前一位的基础上 +1
                    list_group.append(group.copy())
                    break  # 彻底跳出 for 循环,下次循环从头开始

                elif i == 0:  # return 语句不放在外面的原因:无法跳出 while 循环
                    return list_group

运行效果:
LeetCode 77 运行效果 2


78. Subsets(子集)


问题描述

LeetCode 78 问题描述

思路与代码


本题的思路有两种:

其一, 延续前几题的通用思路,使用回溯法进行遍历。

可以和前面的思路一样,按位遍历:

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        subset = []
        list_subset = []

        def backtrack(pos: int):
            list_subset.append(subset.copy())

            for i in range(pos, len(nums)):
                subset.append(nums[i])
                backtrack(pos=i + 1)
                subset.pop()

        backtrack(pos=0)

        return list_subset

运行效果:
LeetCode 78 运行效果 1

也可以类比进位制,根据每个位置的数字是否选取进行遍历:

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        subset = []
        list_subset = []

        def backtrack(pos: int):
            if pos == len(nums):
                list_subset.append(subset.copy())
                return

            for b in [False, True]:
                if b:
                    subset.append(nums[pos])
                backtrack(pos=pos + 1)
                if b:
                    subset.pop()

        backtrack(pos=0)

        return list_subset

运行效果:
LeetCode 78 运行效果 2

其二, 由于子集的数量为 2 n 2^n 2n,因此可以使用二进制数进行循环遍历。

代码如下:

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        list_subset = []

        n = len(nums)
        s = 2 ** n  # 子集数量
        for i in range(s):
            list_bit = list(bin(i)[2:])  # 转为二进制数
            for j in range(n - len(list_bit)):  # 补齐二进制数
                list_bit = ['0'] + list_bit

            subset = []
            for j in range(len(list_bit)):
                if list_bit[j] == '1':
                    subset.append(nums[j])
            list_subset.append(subset.copy())

        return list_subset

运行效果:
LeetCode 78 运行效果 2


90. Subsets II(子集 II)


问题描述

LeetCode 90 问题描述

思路与代码


本题为前一题的变体,不同之处在于重复元素,处理方法可以参考前面 LeetCode 40 的做法,即:

  • 将输入的数组排序
  • 在同一起始位置的循环过程中,跳过与前一位置数值相同的位置

具体代码如下:

class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        nums.sort()  # 数组排序

        subset = []
        list_subset = []

        def backtrack(pos: int):
            list_subset.append(subset.copy())

            for i in range(pos, len(nums)):
                if i > pos and nums[i] == nums[i - 1]:  # 若所在位置与同次循环的前一位置数值相同,则跳过
                    continue

                subset.append(nums[i])
                backtrack(pos=i + 1)
                subset.pop()

        backtrack(pos=0)

        return list_subset

运行效果:
LeetCode 90 运行效果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值