DFS算法-求集合的所有子集
1. 题目来源
牛客网,集合的所有子集(一)
https://www.nowcoder.com/practice/c333d551eb6243e0b4d92e37a06fbfc9

2. 普通方法
1. 思路
数学上排列组合中的组合,从N个元素的集合中拿出M(0≤ M ≤ N)个元素的可能数,标记为
。M从0开始遍历到N,就是所求的所有子集合。这里要结果不是数,而且取的元素集合。
这里具体在做的就是用遍历索引的方式取出想要的集合
- 比如数组为{1, 2, 3, 4, 5},要取0个数,那么所有可能就是{}。1个集合
- 比如数组为{1, 2, 3, 4, 5},要取1个数,那么所有可能就是{{1}, {2}, {3}, {4}, {5}}。5个集合
- 比如数组为{1, 2, 3, 4, 5},要取2个数,那么所有可能就是{{1, 2}, {1, 3}, {1, 4}, {1, 5}, {2, 3}, {2, 4}, {2, 5}, {3, 4}, {3, 5}, {4, 5}}。10个集合
- 比如数组为{1, 2, 3, 4, 5},要取3个数,那么所有可能就是{{1, 2, 3}, {1, 2, 4}, {1, 2, 5}, {1, 3, 4}, {1, 3, 5}, {1, 4, 5}, {2, 3, 4}, {2, 3, 5}, {2, 4, 5}, {3, 4, 5}}。10个集合
- 比如数组为{1, 2, 3, 4, 5},要取4个数,那么所有可能就是{{1, 2, 3, 4}, {1, 2, 3, 5}, {1, 2, 4, 5}, {1, 3, 4, 5}, {2, 3, 4, 5}}。5个集合
- 比如数组为{1, 2, 3, 4, 5},要取5个数,那么所有可能就是{{1, 2, 3, 4, 5}}。1个集合
2. 代码
import java.util.ArrayList;
/**
* @className SolutionTest
* @description
* @author liwei
* @date 2022/9/8 15:29
* @version V1.0
**/
public class SolutionTest {
public static void main(String[] args) {
int[] ints = {5, 4, 3, 2, 1};
System.out.println(subsets(ints));
}
public static ArrayList<ArrayList<Integer>> subsets(int[] ints) {
// 插入排序,升序排列
for (int i = 0, length = ints.length; i < length - 1; i++) {
int tmpValue = ints[i];
int tmpIndex = i;
for (int j = i + 1; j < ints.length; j++) {
if (tmpValue > ints[j]) {
tmpValue = ints[j];
tmpIndex = j;
}
}
if (i != tmpIndex) {
ints[tmpIndex] = ints[i];
ints[i] = tmpValue;
}
}
ArrayList<ArrayList<Integer>> lists = new ArrayList<>();
// 循环,需要从数组中取i个数,然后取出i个数的所有可能。
// 比如数组为{1, 2, 3, 4, 5},要取1个数,那么所有可能就是{{1}, {2}, {3}, {4}, {5}}
// 比如数组为{1, 2, 3, 4, 5},要取2个数,那么所有可能就是{{1, 2}, {1, 3}, {1, 4}, {1, 5}, {2, 3}, {2, 4}, {2, 5}, {3, 4}, {3, 5}, {4, 5}}
for (int number = 0; number <= ints.length; number++) {
lists.addAll(subsets(ints, number));
}
return lists;
}
/**
* 取多少个元素的子集集合
*
* @param ints
* 数组
* @param number
* 要取的元素个数
* @return java.util.ArrayList<java.util.ArrayList < java.lang.Integer>>
* @author liwei
* @date 2022/8/10 20:22
*/
public static ArrayList<ArrayList<Integer>> subsets(int[] ints, int number) {
ArrayList<ArrayList<Integer>> lists = new ArrayList<>();
// 特殊处理,如果需要的个数为0,那么直接添加一个空集合,然后返回
if (number <= 0) {
lists.add(new ArrayList<>());
return lists;
}
int length = ints.length;
// 特殊处理,如果需要的个数大于等于数组元素个数,那么直接添加一个所有元素的集合,然后返回
if (length <= number) {
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i1 : ints) {
arrayList.add(i1);
}
lists.add(arrayList);
return lists;
}
// 用于存储取数组元素的下标。例如数组为[6,7,8,9],要取2个数,那么这里的indexs数组长度就为2,下面初始化索引为最开始索引,indexs=[0,1]
int[] indexs = new int[number];
// 初始化索引
for (int j = 0; j < number; j++) {
indexs[j] = j;
}
// 循环,索引数组的第一个值索引+个数>数组个数,就跳出循环
while (indexs[0] + number <= length) {
// 最后一个索引循环,比如最初indexs=[0,1],数组元素个数为4,那么这里循环,indexs=[0,1]、indexs=[0,2]、indexs=[0,3],跳出循环indexs=[0,4]
for (int k = indexs[number - 1]; k < length; k++) {
ArrayList<Integer> arrayList = new ArrayList<>();
// 通过下标取数组中元素
for (int index : indexs) {
arrayList.add(ints[index]);
}
lists.add(arrayList);
// 最后一个索引往后移动
indexs[number - 1]++;
}
// 上面索引数组中最后一个值已经到了最后,这里是把倒数第二个值+1,如果倒数第二也到了最后,那么再往前找,直到第一个。
// 1、例如数组为[5,6,7,8,9],要取得元素个数为3,索引数组初始化为indexs=[0,1,2],经过上面最后一个索引循环变成indexs=[0,1,5],这里循环得作用就是把索引数组变成indexs=[0,2,3]
// 2、如果经过上面最后一个索引循环变成indexs=[0,4,5],这里循环得作用就是把索引数组变成indexs=[1,2,3]
for (int l = number - 2; l >= 0; l--) {
// 索引数组只修改一次就跳出循环
if (indexs[l] < indexs[l + 1] - 1) {
indexs[l]++;
for (int m = l + 1; m < number; m++) {
indexs[m] = indexs[m - 1] + 1;
}
break;
}
}
}
return lists;
}
}
3. 运行结果
[[], [1], [2], [3], [4], [5], [1, 2], [1, 3], [1, 4], [1, 5], [2, 3], [2, 4], [2, 5], [3, 4], [3, 5], [4, 5], [1, 2, 3], [1, 2, 4], [1, 2, 5], [1, 3, 4], [1, 3, 5], [1, 4, 5], [2, 3, 4], [2, 3, 5], [2, 4, 5], [3, 4, 5], [1, 2, 3, 4], [1, 2, 3, 5], [1, 2, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5], [1, 2, 3, 4, 5]]

3. DFS算法
Depth-First Search深度优先搜索算法
1. 概念
在深度优先搜索中,对于最新发现的顶点,如果它还有以此为顶点而未探测到的边,就沿此边继续探测下去,当顶点v的所有边都已被探寻过后,搜索将回溯到发现顶点v有起始点的那些边。
这一过程一直进行到已发现从源顶点可达的所有顶点为止。如果还存在未被发现的顶点,则选择其中一个作为源顶点,并重复上述过程。
整个过程反复进行,直到所有的顶点都被发现时为止。
比如数组{1,2,3,4,5}所有子集合,这里不包括空集合

2. 解题思路
如下图,探索和回溯,探索是实线箭头表示,回溯是虚线箭头表示。其中虚线箭头指向中间表示这个节点还有其他子节点没有探索完,虚线箭头指向右边便是这个节点已经探索完成。

在这里我们从最右开始,就会发现里面的递归。数组{1,2,3,4,5}的所有子集合划分为几部分:
- 含有元素1的所有集合。【发现在下面第2、3、4、5基础上加上空集合,然后每个集合都加上元素1,就是此时要求的所有集合】
【{1}, {1, 2}, {1, 2, 3}, {1, 2, 3, 4}, {1, 2, 3, 4, 5}, {1, 2, 3, 5}, {1, 2, 4}, {1, 2, 4, 5}, {1, 2, 5}, {1, 3}, {1, 3, 4}, {1, 3, 4, 5}, {1, 3, 5}, {1, 4}, {1, 4, 5}, {1, 5}】,16个集合
- 含有元素2且不包含元素1的所有集合。【发现在下面第3、4、5基础上加上空集合,然后每个集合都加上元素2,就是此时要求的所有集合】
【{2}, {2, 3}, {2, 3, 4}, {2, 3, 4, 5}, {2, 3, 5}, {2, 4}, {2, 4, 5}, {2, 5}】,8个集合
- 含有元素3且不包含元素1和元素2的所有集合。【发现在下面第4、5基础上加上空集合,然后每个集合都加上元素3,就是此时要求的所有集合】
【{3}, {3, 4}, {3, 4, 5}, {3, 5}】,4个集合
- 含有元素4且不包含元素1、元素2和元素3的所有集合。【发现在下面第5基础上加上空集合,然后每个集合都加上元素4,就是此时要求的所有集合】
【{4}, {4, 5}】,2个集合
- 含有元素5且不包含元素1、元素2、元素3和元素4的所有集合。【此时只有一个元素5,那么就只有一个集合,集合里面就一个元素5】
【{5}】,1个集合

3. 代码
import java.util.ArrayList;
/**
* @className FDSTest
* @description FDS算法在求集合的所有子集合,包括空集合时的应用
* @author liwei
* @date 2022/9/8 15:29
* @version V1.0
**/
public class FDSTest {
public static void main(String[] args) {
int[] ints = {5, 4, 3, 2, 1};
System.out.println(subsets(ints));
}
/**
* @description 求数组的所有子集合,包括空数组。按要求升序排序数组
* @author liwei
* @date 2022/9/9 11:45
* @param ints
* 数组
* @return java.util.ArrayList<java.util.ArrayList<java.lang.Integer>>
**/
public static ArrayList<ArrayList<Integer>> subsets(int[] ints) {
// 插入排序,升序排列
for (int i = 0, length = ints.length; i < length - 1; i++) {
int tmpValue = ints[i];
int tmpIndex = i;
for (int j = i + 1; j < ints.length; j++) {
if (tmpValue > ints[j]) {
tmpValue = ints[j];
tmpIndex = j;
}
}
if (i != tmpIndex) {
ints[tmpIndex] = ints[i];
ints[i] = tmpValue;
}
}
// 调用FDS
return subsetsFDS(ints);
}
/**
* @description FDS算法求解数组的所有子集合,包括空数组,内含递归
* @author liwei
* @date 2022/9/9 11:47
* @param ints
* 数组
* @return java.util.ArrayList<java.util.ArrayList<java.lang.Integer>>
**/
public static ArrayList<ArrayList<Integer>> subsetsFDS(int[] ints) {
ArrayList<ArrayList<Integer>> arrayList = new ArrayList<>();
if (null == ints || ints.length <= 0) {
// 递归到最后没有元素的话返回空集合
arrayList.add(new ArrayList<>());
return arrayList;
}
// 取第一个元素
int one = ints[0];
// 定义取完第一个元素后剩余数组长度
int[] twoArray = new int[ints.length - 1];
for (int i = 1; i < ints.length; i++) {
twoArray[i - 1] = ints[i];
}
// 递归,取出第一个元素后,递归得到不包含第一个元素的所有子集合
ArrayList<ArrayList<Integer>> listArrayList = subsetsFDS(twoArray);
for (ArrayList<Integer> integers : listArrayList) {
// 把集合复制一份保存到返回集合中
arrayList.add(new ArrayList<>(integers));
// 在集合第一位置添加第一个元素,然后保存到返回集合中
integers.add(0, one);
arrayList.add(integers);
}
return arrayList;
}
}
4. 运行结果
[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3], [4], [1, 4], [2, 4], [1, 2, 4], [3, 4], [1, 3, 4], [2, 3, 4], [1, 2, 3, 4], [5], [1, 5], [2, 5], [1, 2, 5], [3, 5], [1, 3, 5], [2, 3, 5], [1, 2, 3, 5], [4, 5], [1, 4, 5], [2, 4, 5], [1, 2, 4, 5], [3, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5], [1, 2, 3, 4, 5]]

4. 对比
- 普通方法的优势:如果是指定取M个数,如上面例子我们只取出只有2个元素的所有集合;
- DFS算法的优势:这里只是这个算法的一种体现,最重要的是算法思想。例如求2点间最短距离;
DFS算法-求集合的所有子集的更多相关文章
- 傻瓜方法求集合的全部子集问题(java版)
给定随意长度的一个集合.用一个数组表示,如{"a", "b","c"},求它的全部子集.结果是{ {a}, {b}, {c}, {a,b}, ...
- Java 求集合的所有子集
递归方法调用,求解集合的所有子集. package ch01; import java.util.HashSet; import java.util.Iterator; import java.uti ...
- 【SICP读书笔记(五)】练习2.32 --- 递归求集合子集
题目内容: 我们可以将一个集合表示为一个元素互不相同的表,因此就可以将一个集合的所有子集表示为表的表.例如,假定集合为(1,2,3),它的所有子集的集合就是( () (3) (2) (2 3) (1) ...
- DFS 算法总结
DFS 算法总结 这篇文章会对DFS进行一个总结,列举的题目则是从LeetCode上面选的: 适用场景: 有三个方面,分别是输入数据.状态转换图.求解目标: 输入数据:如果是递归数据结构,如单链表,二 ...
- [Leetcode] subsets 求数组所有的子集
Given a set of distinct integers, S, return all possible subsets. Note: Elements in a subset must be ...
- KM(Kuhn-Munkres)算法求带权二分图的最佳匹配
KM(Kuhn-Munkres)算法求带权二分图的最佳匹配 相关概念 这个算法个人觉得一开始时有点难以理解它的一些概念,特别是新定义出来的,因为不知道是干嘛用的.但是,在了解了算法的执行过程和原理后, ...
- Tarjan算法求割点
(声明:以下图片来源于网络) Tarjan算法求出割点个数 首先来了解什么是连通图 在图论中,连通图基于连通的概念.在一个无向图 G 中,若从顶点i到顶点j有路径相连(当然从j到i也一定有路径),则称 ...
- BFS/DFS算法介绍与实现(转)
广度优先搜索(Breadth-First-Search)和深度优先搜索(Deep-First-Search)是搜索策略中最经常用到的两种方法,特别常用于图的搜索.其中有很多的算法都用到了这两种思想,比 ...
- C++迪杰斯特拉算法求最短路径
一:算法历史 迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法.是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径问题.迪杰斯特拉算法主要特点是以 ...
随机推荐
- python小题目练习(十二)
题目:如下图所示 代码展示: """Author:mllContent:春节集五福Date:2020-01-17"""import rand ...
- 使用net core 6 c# 的 NPOI 包,读取excel..xlsx单元格内的图片,并存储到指定服务器
这个是记录,单元格的图片. 直接上代码,直接新建一个 net core api 解决方案,引用一下nuget包.本地创建一个 .xlsx 格式的excel文件 using ICSharpCode.Sh ...
- NC207028 第k小数
NC207028 第k小数 题目 题目描述 给你一个长度为 \(n\) 的序列,求序列中第 \(k\) 小数的多少. 输入描述 多组输入,第一行读入一个整数 \(T\) 表示有 \(T\) 组数据. ...
- 文件的下载,HttpMessageConverter原理
HttpMessageConverter<T> 1) HttpMessageConverter<T> 是 Spring3.0 新添加的一个接口,负责将请求信息转换为一个对象(类 ...
- AlterNats是如何做到高性能的发布订阅的?
前言 在过去的一些文章里面,我们聊了一些.NET平台上高性能编程的技巧,今天带大家了解一下AlterNats这个库是如何做到远超同类SDK性能的. NATS:NATS是一个开源.轻量级.高性能的分布式 ...
- NC20566 [SCOI2010]游戏
题目链接 题目 题目描述 lxhgww最近迷上了一款游戏,在游戏里,他拥有很多的装备,每种装备都有2个属性,这些属性的值用[1,10000]之间的数表示.当他使用某种装备时,他只能使用该装备的某一个属 ...
- 003 Jwt登录流程图
用户\角色\权限 用户是一个基本的单位 用户和角色的关系是多对多,所以要有一张保存用户和角色关系的中间表 角色也不能直接决定这个用户能做什么操作,有哪些权限, 需要再关联权限表决定 角色和权限也是多对 ...
- Jackson 解析 JSON 详细教程
点赞再看,动力无限. 微信搜「程序猿阿朗 」. 本文 Github.com/niumoo/JavaNotes 和 未读代码博客 已经收录,有很多知识点和系列文章. JSON 对于开发者并不陌生,如今的 ...
- “杀死” App 上的疑难崩溃
在移动应用性能方面,崩溃带来的影响是最为严重的,程序崩了可以打断用户正在进行的操作体验,造成关键业务中断.用户留存率下降.品牌口碑变差.生命周期价值下降等影响.很多公司将崩溃率作为优先级最高的技术指标 ...
- 选择语句-IF和标准if-else语句以及if-else语句的扩展
第二章 判断语句 2.1 判断语句1--if if语句的第一种格式:if if(关系表达式){ 语句体; } 执行流程 首先判断关系表达式看起结果是true还是false 如果是true就执行与具体 ...