从小白开始刷算法 回溯法篇 leetcode.78

简介: 从小白开始刷算法 回溯法篇 leetcode.78

序言

虽然算法很难,但不应该就放弃。这是一个学习笔记,希望你们喜欢~

先自己尝试写,大概十几分钟仍然写不出来

看思路,再尝试跟着思路写

仍然写不出来,再看视频

b站up视频推荐:爱学习的饲养员

leetcode其他文章:

数组篇:

从小白开始刷算法 数组篇 leetcode.485

从小白开始刷算法 数组篇 leetcode.283

从小白开始刷算法 数组篇 leetcode.27

链表篇:

从小白开始刷算法 ListNode 链表篇 leetcode.203

从小白开始刷算法 ListNode 链表篇 leetcode.206

队列篇

从小白开始刷算法 ListNode 链表篇 leetcode.933

栈篇

从小白开始刷算法 Stack 栈篇 leetcode.20

从小白开始刷算法 Stack 栈篇 leetcode.496

哈希篇

从小白开始刷算法 Hash 哈希篇 leetcode.217

从小白开始刷算法 Hash 哈希篇 leetcode.705

树篇

从小白开始刷算法 Tree 树篇 先序遍历 leetcode.144

从小白开始刷算法 Tree 树篇 中序遍历 leetcode.94

从小白开始刷算法 Tree 树篇 后序遍历 leetcode.94

堆篇

从小白开始刷算法 Heap 堆篇 最大堆排序 leetcode.215

小白开始刷算法 Heap 堆篇 最小堆排序 leetcode.692

双指针篇

从小白开始刷算法 对撞双指针 leetcode.881

从小白开始刷算法 快慢双指针篇 leetcode.141

二分法篇

从小白开始刷算法 二分法篇 leetcode.704

从小白开始刷算法 二分法篇 leetcode.35

从小白开始刷算法 二分法篇 leetcode.162

从小白开始刷算法 二分法篇 leetcode.74

滑动窗口篇

从小白开始刷算法 滑动窗口篇 leetcode.209

从小白开始刷算法 滑动窗口篇 leetcode.1456

递归篇

从小白开始刷算法 递归篇 leetcode.509

从小白开始刷算法 递归篇 leetcode.206

分治法篇

从小白开始刷算法 分治法篇 leetcode.169

从小白开始刷算法 分治法篇 leetcode.53

回溯法篇

从小白开始刷算法 回溯法篇 leetcode.22

回溯法篇

难度:中等

题目:

78. 子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

输入:nums = [1,2,3]

输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:

输入:nums = [0]

输出:[[],[0]]

题目来源:力扣(LeetCode)

回溯法思路

能否写出:不能写出,需要思路。

时间:一个小时多

回溯法(backtrack)介绍:

从小白开始刷算法 回溯法篇 leetcode.22

里面有关于回溯法的介绍

思路:

首先,我们创建一个空的结果列表result,用于存储所有的子集。我们初始化result为一个空列表,表示空集也是nums的子集。

然后,我们遍历长度从1到nums.length的所有子集长度。在每个长度下,我们使用回溯法来生成所有符合要求的子集。

对于长度为length的子集,我们定义一个backtrack方法,其中传入的参数包括:

  1. nums数组
  2. 结果列表result
  3. 子集长度length
  4. 当前索引index
  5. 当前正在构建的子集subset

在backtrack函数中,我们首先检查当前subset的长度是否等于length。如果是,说明我们已经构建了一个符合要求的子集,将其添加到结果列表result中。

接下来,我们从索引index开始遍历nums数组,对于每个元素nums[i],我们将其添加到subset中,并递归调用backtrack函数来构建下一个元素。递归调用时,我们传入的索引index=i+1,表示下一个元素的索引,这样可以确保我们不重复使用已经使用过的元素。

当递归返回后,我们需要将subset中的最后一个元素移除,以便尝试下一个可能的元素。这样可以保证我们得到所有的子集。

最终,当遍历完所有长度的子集时,我们将得到nums的所有子集,存储在结果列表result中,并将其作为函数的返回值。

通过这种回溯法的思路,我们可以找出nums的所有子集。在回溯的过程中,我们逐步构建子集,并根据要求判断是否加入结果列表。通过递归的方式,我们尝试所有可能的选择,最终得到所有符合要求的子集。

subset的作用:

subset是一个用于暂存当前正在构建的子集的列表。它在回溯过程中起到了两个重要的作用:

  • 存储当前正在构建的子集元素:
  • 在回溯的过程中,每次选择一个元素加入到子集中,然后进行递归,直到满足生成子集的条件。subset列表用于存储当前正在构建的子集元素,通过不断地添加和删除元素,来构建出不同的子集。
  • 添加已生成的子集到结果列表:
  • 当满足生成子集的条件时(子集长度等于目标长度),需要将当前的子集加入到结果列表中。由于在回溯过程中,使用的是同一个subset列表,为了避免后续操作对已生成的子集造成影响,需要创建一个副本(new ArrayList<>(subset))并将其添加到结果列表中。

通过使用subset列表,可以在回溯的过程中动态地构建不同长度的子集,并将满足条件的子集添加到结果列表中,最终得到所有可能的子集组合。

// 仅是我的思路代码,leetcode上大神更厉害
class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        // 初始状态,加入空集
        result.add(new ArrayList<>()); 
        // 构建subset的长度
        for (int length = 1; length <= nums.length; length++) {
            backtrack(nums, result, length, 0, new ArrayList<>());
        }
        return result;
    }
    private void backtrack(int[] nums, List<List<Integer>> result, int length, int index, ArrayList<Integer> subset) {
        if (subset.size() == length) {
            // 构建符合要求的子集,加入结果列表
            result.add(new ArrayList<>(subset)); 
            return;
        }
        //
        for (int i = index; i < nums.length; i++) {
             // 添加当前元素到子集中
            subset.add(nums[i]);
            // 递归构建下一个元素
            backtrack(nums, result, length, i + 1, subset); 
            // 回溯,移除最后一个元素,尝试下一个选择
            subset.remove(subset.size() - 1); 
        }
    }
}

时间复杂度:

  • 在主函数中的 for 循环会执行 n 次,每次调用回溯函数。
  • 在回溯函数中,最坏情况下,每个元素都会被选中或不选中,因此回溯函数的时间复杂度为指数级别的 O(2^n)。
  • 综合起来,总时间复杂度为 O(n * 2^n)。

空间复杂度:

  • 结果列表 result 的空间复杂度为 O(2^n),因为最终结果的个数为 2^n 个。
  • backtracking 函数中使用的临时列表 subset 的空间复杂度为 O(n),因为在最坏情况下,递归的深度为 n,即最多会存储 n 个元素。
  • 综合起来,总空间复杂度为 O(2^n)。

其他思路:

DFS

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        List<Integer> subset = new ArrayList<>();
        dfs(nums, result, subset, 0);
        return result;
    }
    private void dfs(int[] nums, List<List<Integer>> result, List<Integer> subset, int index) {
        result.add(new ArrayList<>(subset));
        for (int i = index; i < nums.length; i++) {
            subset.add(nums[i]);
            dfs(nums, result, subset, i + 1);
            subset.remove(subset.size() - 1);
        }
    }
}

时间复杂度: O(2^n)

空间复杂度: O(n)



相关文章
|
2月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
43 0
|
1月前
|
存储 算法 Java
leetcode算法题-有效的括号(简单)
【11月更文挑战第5天】本文介绍了 LeetCode 上“有效的括号”这道题的解法。题目要求判断一个只包含括号字符的字符串是否有效。有效字符串需满足左括号必须用相同类型的右括号闭合,并且左括号必须以正确的顺序闭合。解题思路是使用栈数据结构,遍历字符串时将左括号压入栈中,遇到右括号时检查栈顶元素是否匹配。最后根据栈是否为空来判断字符串中的括号是否有效。示例代码包括 Python 和 Java 版本。
|
2月前
|
算法
每日一道算法题(Leetcode 20)
每日一道算法题(Leetcode 20)
29 2
|
4月前
|
算法 Java
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
57 6
|
4月前
|
人工智能 算法 Java
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
54 1
|
4月前
|
存储 算法 Java
LeetCode经典算法题:预测赢家+香槟塔java解法
LeetCode经典算法题:预测赢家+香槟塔java解法
65 1
|
4月前
|
存储 算法 Java
LeetCode经典算法题:二叉树遍历(递归遍历+迭代遍历+层序遍历)以及线索二叉树java详解
LeetCode经典算法题:二叉树遍历(递归遍历+迭代遍历+层序遍历)以及线索二叉树java详解
83 0
|
4月前
|
算法 Java
LeetCode初级算法题:子数组最大平均数+二叉树的最小深度+最长连续递增序列+柠檬水找零
LeetCode初级算法题:子数组最大平均数+二叉树的最小深度+最长连续递增序列+柠檬水找零
44 0
|
4月前
|
算法 Java
LeetCode初级算法题:环形链表+排列硬币+合并两个有序数组java解法
LeetCode初级算法题:环形链表+排列硬币+合并两个有序数组java解法
56 0
|
4月前
|
存储 算法 Java
LeetCode初级算法题:两数之和+斐波拉契数列多种java解法
LeetCode初级算法题:两数之和+斐波拉契数列多种java解法
45 0