秋招算法备战第29天 | 491.递增子序列、46.全排列、47.全排列 II

491. 递增子序列 - 力扣(LeetCode)

这个问题可以使用回溯算法来解决。回溯法主要是解决一个全排列问题,通过不断试错,然后回退状态、再次试错的方式,搜索答案的过程。对于本题,我们需要找到所有的递增子序列,为了避免重复,我们需要一些特殊的处理。

我们可以首先定义一个结果列表res来保存所有的子序列,然后定义一个回溯函数,这个函数接受一个起始位置和一个当前的子序列。在函数中,我们首先检查当前的子序列是否为递增子序列,如果是,就将其添加到结果中(这里注意我们要从第二个元素开始添加,因为题目要求至少有两个元素)。然后我们从起始位置开始,遍历到数组的结尾,如果当前元素大于等于子序列的最后一个元素,我们将其添加到子序列中,并递归调用回溯函数。为了避免重复,我们需要在每一轮的开始时记录下当前的子序列的长度,然后在添加元素后,如果子序列的长度没有增加,我们就跳过这个元素。

下面是Python代码:

def findSubsequences(nums):
    res = []
    def backtrack(start, path):
        if len(path) > 1:
            res.append(path[:])
        uniq = set()
        for i in range(start, len(nums)):
            if nums[i] in uniq:
                continue
            if not path or nums[i] >= path[-1]:
                uniq.add(nums[i])
                backtrack(i + 1, path + [nums[i]])
    backtrack(0, [])
    return res

在这个代码中,我们首先定义了一个结果列表res,然后定义了一个回溯函数backtrack。在backtrack中,我们首先检查当前的子序列是否满足条件,如果满足就添加到结果中。然后我们从起始位置开始,遍历到数组的结尾,对于每一个元素,我们首先检查它是否已经被添加过,如果已经被添加过,我们就跳过。然后如果这个元素大于等于子序列的最后一个元素,我们就将其添加到子序列中,并递归调用backtrack。

46. 全排列 - 力扣(LeetCode)

这个问题可以用回溯算法来解决。算法的主要步骤是,从输入数组中选择一个数字,将其添加到当前的排列中,然后从剩余的数字中继续选择,直到所有的数字都被选择为止。每次选择完成后,需要进行回溯,将选择的数字从当前排列中移除,然后选择其他的数字。

下面是Python中的一个解决方案:

def permute(nums):
    def backtrack(first = 0):
        # 所有数都填完了
        if first == n:  
            res.append(nums[:])
            return
        for i in range(first, n):
            # 动态维护数组
            nums[first], nums[i] = nums[i], nums[first]
            # 继续递归填下一个数
            backtrack(first + 1)
            # 撤销操作
            nums[first], nums[i] = nums[i], nums[first]

    n = len(nums)
    res = []
    backtrack()
    return res

在这个函数中,我们首先定义了一个回溯函数。在这个函数中,我们首先检查是否所有的数字都已经被选择,如果是,就将当前的排列添加到结果中。然后,我们遍历从first到n的所有数字,将它们一个个添加到当前的排列中,然后递归调用backtrack。在递归调用结束后,我们需要将之前的选择撤销,以便在下一次迭代中选择其他的数字。

如果我们不希望使用交换来实现,可以选择通过维护路径和选择列表的方式来进行。代码如下:

def permute(nums):
    def backtrack(nums, path):
        # 结束条件
        if not nums:
            res.append(path)
            return 
        # 选择列表
        for i in range(len(nums)):
            # 做选择
            backtrack(nums[:i]+nums[i+1:], path+[nums[i]])

    res = []
    backtrack(nums, [])
    return res

这个版本的实现中,我们的backtrack函数接受两个参数,nums代表当前可选择的数字,path代表已经选择的路径。在每次调用backtrack时,我们遍历所有nums中的数字,对于每个数字,我们将其加入到path中,然后在nums中移除它,递归调用backtrack。这样,我们就不需要使用交换来动态维护数组了。

47. 全排列 II - 力扣(LeetCode)

这个问题是之前问题的扩展,区别在于数组中可以包含重复的数字。解决这个问题的基本思路与之前的相同,也是使用回溯算法,但是我们需要处理重复的数字。

在处理重复数字时,我们需要对数组进行排序,这样相同的数字就会被放在一起。然后在每次从剩余数字中选择数字时,我们需要跳过与前一个数字相同的数字。

下面是使用Python实现的解决方案:

def permuteUnique(nums):
    def backtrack(nums, path):
        # 结束条件
        if not nums:
            res.append(path)
            return 
        # 选择列表
        for i in range(len(nums)):
            # 去重
            if i > 0 and nums[i] == nums[i-1]:
                continue
            # 做选择
            backtrack(nums[:i]+nums[i+1:], path+[nums[i]])

    nums.sort()  # 首先排序
    res = []
    backtrack(nums, [])
    return res

在这个解决方案中,我们首先对数组进行了排序,然后定义了一个回溯函数。在这个函数中,我们首先检查是否所有的数字都已经被选择,如果是,就将当前的排列添加到结果中。然后,我们遍历所有剩余的数字,如果当前的数字和前一个数字相同,我们就跳过这个数字。对于每个数字,我们将其加入到当前的排列中,然后在剩余的数字中移除它,然后递归调用回溯函数。

组合与排列的区别

组合问题和排列问题是组合数学中的两类重要问题,它们有以下的主要区别:

  1. 顺序的重要性

    • 在排列问题中,元素的顺序是重要的。也就是说,对于相同的元素,不同的顺序被视为不同的排列。例如,[1, 2]和[2, 1]被视为两个不同的排列。
    • 在组合问题中,元素的顺序是不重要的。也就是说,对于相同的元素,不论它们的顺序如何,都被视为相同的组合。例如,[1, 2]和[2, 1]被视为同一个组合。
  2. 求解方法

    • 对于排列问题,我们通常使用回溯法进行求解,每次选择一个元素添加到当前的排列中,然后从剩余的元素中继续选择。
    • 对于组合问题,我们也通常使用回溯法进行求解,不过在每次回溯时,我们只考虑当前元素之后的元素,这样可以避免产生重复的组合。
  3. 问题的实例

    • 排列问题的典型实例是全排列问题,即从n个不同的元素中取出m(m≤n)个元素,按照一定的顺序排成一列。
    • 组合问题的典型实例是组合数问题,即从n个不同的元素中取出m(m≤n)个元素,不考虑其顺序。

总的来说,排列和组合是解决问题的两种不同的思路,应用于不同的场景。在解决实际问题时,需要根据问题的具体要求来选择使用排列还是组合。

总结

Summary

本文介绍了三个使用回溯算法解决的问题,包括寻找递增子序列、全排列、和包含重复数字的全排列。回溯算法通过不断试错、回退状态、再次试错的方式搜索答案,适用于排列和组合问题。

Facts

  • 491. 递增子序列 - 力扣(LeetCode)

    • 使用回溯算法找到所有递增子序列。
    • 定义结果列表res保存所有子序列,定义回溯函数接受起始位置和当前子序列。
    • 检查当前子序列是否为递增子序列,若是,将其添加到结果中(注意从第二个元素开始添加)。
    • 从起始位置开始,遍历数组,若当前元素大于等于子序列的最后一个元素,将其添加到子序列中,并递归调用回溯函数。
    • 避免重复:记录每轮开始时当前子序列的长度,添加元素后若长度未增加,则跳过该元素。
  • 46. 全排列 - 力扣(LeetCode)

    • 使用回溯算法找到数组的所有排列。
    • 定义回溯函数,从输入数组中选择一个数字添加到当前排列中,然后继续选择剩余数字,直到所有数字都被选择。
    • 每次选择完成后进行回溯,撤销选择的数字,然后选择其他数字。
    • 可以使用交换操作或维护路径和选择列表来实现。
  • 47. 全排列 II - 力扣(LeetCode)

    • 是全排列问题的扩展,数组可以包含重复的数字。
    • 使用回溯算法解决,需要对数组进行排序,使相同数字相邻。
    • 在选择数字时跳过与前一个数字相同的数字,以处理重复数字。

以上是使用Python实现的解决方案,涵盖了回溯算法在不同问题上的应用。

你可能感兴趣的:(算法,python,开发语言)