想要精通算法和SQL的成长之路 - 子集问题

想要精通算法和SQL的成长之路 - 子集问题

前言

想要精通算法和SQL的成长之路 - 系列导航

子集问题和 全排列 问题很像,只不过:

  • 全排列问题的结果集元素个数和给定的数组元素个数是一样的。
  • 子集问题的结果集元素个数则是 <= 给定数组元素个数。

上面这个不同的本质是啥呢?

  • 全排列问题在进行遍历的时候,每一层都是从头开始遍历。
  • 子集问题在进行遍历的时候,每一层的遍历位置依赖于上一层。

废话不多说,我们先来看下最基础的子集问题。

一. 子集

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

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

对于该题目,回溯需要考虑的几个点:

  1. 递归终止的条件判断:数组遍历完毕。
  2. 递归要做的事情:把当前遍历到的数字放到临时集合中,并将其加入到结果集中。作为一个新的子集。
  3. 由于数组元素不会有重复,因此子集不用考虑去重操作。

1.1 递归终止判断

if(index >= nums.length){
	return;
}

其实这里可以不用加,因为当满足index >= nums.length的时候,for循环也就停止了。

我们加入递归终止判断的目的也就是为了提前终止for循环。因此在这里没有必要写。’

1.2 递归要做的事情

// res是最终的结果集。tmp是临时结果集,就是遍历过程中得到的子集
res.add(new ArrayList<>(tmp));
// index则是通过函数传入的,每层开始的下标依赖于上层传入的参数
for (int i = index; i < nums.length; i++) {
    tmp.add(nums[i]);
    backtrack(i + 1, nums, tmp);
    // 回溯
    tmp.remove(tmp.size() - 1);
}

最终结果:

public class Test78 {
    List<List<Integer>> res = new ArrayList<>();

    public List<List<Integer>> subsets(int[] nums) {
        backtrack(0, nums, new ArrayList<>());
        return res;
    }

    public void backtrack(int index, int[] nums, ArrayList<Integer> tmp) {
    	// 加入到结果集,空集也算(第一次遍历的时候)
        res.add(new ArrayList<>(tmp));
        for (int i = index; i < nums.length; i++) {
        	// 标准的:1.加入集合中。2.下一层递归。3.回溯(从集合中剔除)
            tmp.add(nums[i]);
            backtrack(i + 1, nums, tmp);
            // 回溯
            tmp.remove(tmp.size() - 1);
        }
    }
}

二. 子集II

原题链接

在第一个子集问题的基础上,多了一个条件就是:

  • 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)

那么就是在问题1的基础上,我们多一个子集的去重操作喽。

去重,我们就要判断,在添加子集的时候,遍历的前后两个元素是否有相同的元素,有的话跳过,也就是剪枝。

然后数组回溯里面的剪枝要记住一点:剪枝的前提就是数组要有序。 那么我们在原本代码基础上,会多这么一行代码:

Arrays.sort(nums);

for循环的时候,进行剪枝:

if (i > index && nums[i - 1] == nums[i]) {
    continue;
}

最终结果如下:

public class Test90 {
    List<List<Integer>> res = new ArrayList<>();

    public List<List<Integer>> subsetsWithDup(int[] nums) {
        // 剪枝的前提,有序
        Arrays.sort(nums);
        backtrack(nums, 0, new ArrayList<>());
        return res;
    }

    public void backtrack(int[] nums, int index, List<Integer> tmp) {
        res.add(new ArrayList<>(tmp));
        for (int i = index; i < nums.length; i++) {
            // 跳过当前树层使用过的、相同的元素
            if (i > index && nums[i - 1] == nums[i]) {
                continue;
            }
            tmp.add(nums[i]);
            backtrack(nums, i + 1, tmp);
            tmp.remove(tmp.size() - 1);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zong_0915

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值