全排列、子集、组合、子序列

news2024/11/15 8:45:11

全排列、子集、组合、子序列

  • 1、全排列
    • ①回溯(DFS)交换法
    • ②回溯(DFS)选择法
    • ③ 插入法
  • 2、子集
    • ①回溯选择法
    • ② 动态规划
  • ③ 位运算方式
  • 组合

1、全排列

全排列(Permutation):全排列是指给定一组元素,通过交换元素的位置,得到这组元素所有可能的排列方式。假设有n个元素,全排列将产生n!(n的阶乘)种不同的排列方式。
eg:对于集合{1, 2, 3}的全排列有6个,分别为:
{1, 2, 3}
{1, 3, 2}
{2, 1, 3}
{2, 3, 1}
{3, 1, 2}
{3, 2, 1}

算法思路:

①回溯(DFS)交换法

一个序列的全排列,可以理解为其中每几个数发生了交换形成。对于一个长度为n的序列,我们可以取[0, flag - 1]为已经确定下来的数,flag将要与[flag + 1, n - 1]中每个数发生交换,交换一次就会形成一个新的排列。(因此每次递归完成后,要将上一次交换后的两个数字交换回来)
每次交换完成后,flag位置发生变化,会形成新的[0, flag - 1],flag又将要与新的[flag + 1, n - 1]中每个数发生交换。在此进行回溯,示例图如下:来源于leetcode46
在这里插入图片描述
代码如下: 这也是一个经典的DFS模板

public static List<List<Integer>> permute(int[] nums) {
        //结果
        List<List<Integer>> result = new ArrayList<>();
        List<Integer> integers = new ArrayList<>(nums.length);
        //方便操作
        for (int num : nums) {
            integers.add(num);
        }
        //主要方法
        backtrack(integers, result, 0);
        return result;
    }
    //递归回溯
    public static void backtrack(List<Integer> nums, List<List<Integer>> result, int flag) {
        //flag到头,说明后面没有可交换的数了,结果集add
        if (flag == nums.size() - 1) {
            result.add(new ArrayList<>(nums));
        }
        for (int i = flag; i < nums.size(); i++) {
            //[0, flag-1]为左区间,[flag, nums.size-1]为右区间
            //交换flag与i
            Collections.swap(nums, flag, i);
            //flag右移,进入递归
            backtrack(nums, result, flag + 1);
            //递归完成,回溯时要把上一步位置交换回来
            Collections.swap(nums, flag, i);
        }
    }

②回溯(DFS)选择法

主要是选与不选,但是选择了1,就不能再次选了,只能选2或者3。相比上边的交换法很容易理解。
在这里插入图片描述
代码如下:

    List<List<Integer>> result = new ArrayList<>();
    //回溯选择法
    public void backtrack2(List<Integer> list, int[] nums){
        //最后一层也选完了
        if (list.size() == nums.length) {
            result.add(new ArrayList<>(list));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            //选择第i个,选完以后不能选了
            if (!list.contains(nums[i])){
                list.add(nums[i]);
                //进入下一层
                backtrack2(list, nums);
                //回溯后要留位置,把最后一个移除,让nums[i+1]进来,但是如果i已经循环完了,会再次回溯发生remove
                list.remove(list.size() - 1);
            }
        }
    }

最后的结果就是result。看一下这个代码:
1、参数分别为(list:每次选择的数,如果最后这个list的长度等于了原始数组长度,说明一次排列完成了。nums:原始数组)
2、循环nums,选择一个数放入list中,之后进入递归,再次循环nums[],但是取过的数是不能取了,i++,取另一个数。直到list长度等于了原始数组长度。一次递归完成。
3、回溯,把递归前add进来的nums[i]移除,空出一个位置来,供其他数选择。此时,如果i也到头了,会再次发生回溯,再空出一个位置。也就是list中的数字会同时受到i的影响和回溯的影响

举个例子:[1],[1,2],[1,2,3],递归完成,发生回溯,remove了[3],成为了[1,2],(正常来想,再进入递归,又会是[1,2,3]了,这不是死循环了吗)。但是成为了[1,2]后,因为i++发生过一次,i=2了,此时在进入递归,i++使得i成为了3,又发生return,回溯,成为了[1],这时候,因为i已经发生一次++,取过2了,因此只能取3。成为了[1,3],然后取2,结果是[1,3,2]。

但是这个代码时间复杂度达到了nn,因为每次都要循环n次,而且list.contains()也是一个n的复杂度。所以复杂度已经达到了(n*n)n。。。。。
第一种方式是n!的递归调用,因为i每次增加,循环就会减少。
优化方式:可以选一个额外的boolean数组保存状态原数组的index状态,如果被引用过,就置为true,循环时候进行判断即可,不需要contions。

③ 插入法

这个方法思想是:在[1]的基础上把2插进去有几个地方可以插,插完后得到一组数列比如[1,2],然后再把3一个一个插进去,得到每个结果就是一个排列。代码没有写,和第一种交换思想差不多,这个可能需要一些额外空间

2、子集

子集是指给定一组元素,从中选取0个或多个元素组成的集合。对于n个元素的集合,它将包含2^n个不同的子集,包括空集和全集。

一般题目会是这样:

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

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

①回溯选择法

算法思想:有点类似上面全排列的选择法,就是遍历原始数组,取或者不取当前位置,取了当前位置,那么下一个位置也会面临取或者不取。回溯时,需要复位。有点类似二叉树的遍历。
代码如下:

    //表示结果
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> temp = new ArrayList<>();
	
	//求子集
    public List<List<Integer>> subsets1(int[] nums) {
        backTrack(0, nums);
        return res;
    }

    //递归回溯
    public void backTrack(int cur, int[] nums) {
        if (cur == nums.length) {
            //cur到头
            res.add(new ArrayList<>(temp));
            return;
        }
        //取当前位置的
        temp.add(nums[cur]);
        //进入递归,下一个位置
        backTrack(cur + 1, nums);
        //回溯,需归位,不能写temp.remove(cur);,应该移动的是最后进入temp的,和cur没关系,cur只和nums的下标有关系
        temp.remove(temp.size() - 1);
        //不取当前位置的,那么直接进入下一个位置
        backTrack(cur + 1, nums);
    }

② 动态规划

子集可以是从空集开始[],遍历原始数组,空集基础上增加一个元素,成为[[ ], [1]],再在此基础上加第二个元素成为[[ ], [1],[2], [1,2]],再在此基础上增加下一个元素。后一个状态依赖于前一个状态。反过来想,就可以得到状态转移方程:i表示当前状态,i-1表示上一个状态

 List<List>[i] = List<List>[i - 1]中的每一个list.add(nums[i])

虽然是一个数组嵌套,但是是一个一维的动态规划,只有i这一种依赖。
代码如下:

public List<List<Integer>> subsets2(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        result.add(new ArrayList<>());
        //取第i个数
        for (int i = 0; i < nums.length; i++) {
            List<List<Integer>> temp = new ArrayList<>();
            //遍历List<List>[i - 1],获取每一个list
            for (List<Integer> list : result) {
                //每一个list.add(nums[i])
                List<Integer> news = new ArrayList<>(list);
                news.add(nums[i]);
                //中间变量保存
                temp.add(news);
            }
            //得到List<List>[i]
            result.addAll(temp);
        }
        return result;
    }

看着是两个for,但其实第二层for是动态变化的,而且每次扩容2倍,因此时间复杂度还是2n,相对于递归少了压栈空间

③ 位运算方式

有一种位运算的方式来模拟子集的形成,因为集合中的每个元素可以用一个二进制位来表示,1表示选择该元素,0表示不选择。通过遍历所有的二进制数,即可得到所有子集。但是我感觉遇到这个题是想不出这种方法来,所以没有写对应代码QAQ
位运算方式

组合

组合是指从一组元素中选取特定数量的元素,而不考虑它们的顺序。假设有n个元素,从中选择k个元素作为组合,可以用“C(n,k)”或者“n choose k”来表示,计算公式为 C ( n , k ) = n ! k ! ⋅ ( n − k ) ! C(n, k) = \frac{n!}{k! \cdot (n-k)!} C(n,k)=k!(nk)!n!
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

算法思想:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/833821.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【漏洞复现】Ruijie RG-BCR860 后台命令执行漏洞(CVE-2023-3450)

文章目录 前言声明一、简介二、漏洞概述三、影响版本四、环境搭建五、漏洞复现六、修复方式 前言 Ruijie RG-BCR860 2.5.13 版本存在操作系统命令注入漏洞&#xff0c;攻击者可通过该漏洞获取服务器敏感信息&#xff0c;导致服务器被沦陷。 声明 请勿利用文章内的相关技术从事…

git删除已经提交的大文件

当你不小心把一个巨大的二进制文件提交到git仓库的时候&#xff0c;此时删除再提交也没有用了&#xff0c;大文件已经在仓库中留底了。另外比如需要删除某个需要保密的文件&#xff0c;都是相同的解决办法。 我本来想着把dll放在三方库里面提交到仓库里&#xff0c;省得在不同…

入门级:路由器配置静态路由

软件&#xff1a;cicso packet tracer 8.0 拓扑图&#xff1a;路由器&#xff1a;Router-PT、连接线&#xff1a;Serial DTE、连接口&#xff1a;Serial口&#xff08;serial是串行口,一般用于连接设备,不能连接电脑&#xff09; 实验步骤&#xff1a; 1、构建拓扑图&#xf…

paddlenlp:社交网络中多模态虚假媒体内容核查

初赛之环境配置篇 一、背景二、任务三、数据集1、初赛阶段2、评分标准 四、环境操作五、写在最后 一、背景 随着新媒体时代信息媒介的多元化发展&#xff0c;各种内容大量活跃在媒体内中&#xff0c;与此同时各类虚假信息也充斥着社交媒体&#xff0c;影响着公众的判断和决策。…

re学习(27)攻防世界 re1-100

参考文章&#xff1a;攻防世界逆向高手题之re1-100_沐一 林的博客-CSDN博客 查壳&#xff1a; 用IDA打开&#xff0c;分析 编写脚本&#xff1a; d"{daf29f59034938ae4efd53fc275d81053ed5be8c}" d1d[1:11] d2d[11:21] d3d[21:31] d4d[31:41] print(d3d4d1d2) #…

我去,这是什么黑科技!用信号处理方法抑制瞬态噪声

对于语音增强来说&#xff0c;噪声一般可以分为稳态噪声&#xff08;如白噪声&#xff09;和瞬态噪声&#xff08;有的地方也叫非稳态噪声&#xff0c;如键盘声&#xff09;。如果对语音降噪有一定了解的读者会知道&#xff0c;一般的信号处理方法对稳态噪声比较有效&#xff0…

FreeIPA Server/Client不同版本组合,对podman rootless container的支持

FreeIPA Server/Client不同版本组合&#xff0c;对podman rootless container的支持 根据实验&#xff0c; CentOS 7.9 yum仓库自带的FreeIPA Server 4.6.8&#xff0c; ipa client版本支持CentOS 7.9 yum仓库自带的FreeIPA Client 4.6.8不支持subids&#xff0c;podman调用…

SqlServer2008如何解析Json—附详细代码

1、在数据库中创建存储过程parseJSON&#xff0c;具体文件请在如下链接下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1a-aNmSKk_yvv9wQTP3DCsg?pwdyxwx 提取码&#xff1a;yxwx 2、具体使用方法如下&#xff1a; DECLARE UserParameter NVARCHAR(MAX){"…

单元测试之 - Spring框架提供的单元/集成测试注解

Spring框架提供了很多注解来辅助完成单元测试和集成测试(备注&#xff1a;这里的集成测试指容器内部的集成测试&#xff0c;非系统间的集成测试)&#xff0c;先看看Spring框架提供了哪些注解以及对应的作用。RunWith(SpringRunner.class) / ExtendWith(SpringExtension.class)&…

无代码开发(BIP旗舰版-YonBuilder)

目录 我的应用 新建领域 菜单管理 应用构建 新建应用 对象建模 新增业务对象 新增业务实体 页面建模 新增页面 编辑页面 发布管理 我的应用 角色管理 yonbuilder开发平台&#xff0c;提供标准服务和专业开发服务&#xff1b; 本篇文章只演示标准服务的可视化应用…

软件外包开发的GO语言特点

Go语言&#xff08;也称为Golang&#xff09;是由Google开发的一种编程语言。它具有许多特点&#xff0c;使其成为许多项目范围的优秀选择。Go语言适用于需要高性能、并发和简洁易读的项目&#xff0c;特别是面向网络和分布式应用的项目。今天和大家分享项目的特点及适用的项目…

学习记录——TransNormerLLM、SRFormer、PLG-ViT、EfficientViT

关于Transformer Transformer 存在局限。首要的一点&#xff0c;它们有着对于序列长度的二次时间复杂度&#xff0c;这会限制它们的可扩展性并拖累训练和推理阶段的计算资源和时间效率。基于 Transformer的模型在提高窗口大小以优化性能的同时&#xff0c;也带来了相应的计算负…

刷完这个笔记,15K真的不能再少了....

大家好&#xff0c;最近有不少小伙伴在后台留言&#xff0c;得准备面试了&#xff0c;又不知道从何下手&#xff01;为了帮大家节约时间&#xff0c;特意准备了一份面试相关的资料&#xff0c;内容非常的全面&#xff0c;真的可以好好补一补&#xff0c;希望大家在都能拿到理想…

LVDS端口ESD静电放电保护电路图(经典)

Low Voltage Differential Signaling&#xff08;LVDS&#xff09;是一种低压差分信号技术接口&#xff0c;是美国NS公司为克服以TTL电平方式传输宽带高码率数据时功耗大、EMI电磁干扰大等缺点而研制的一种数字视频信号传输方式。LVDS端口电路包括两部分&#xff1a;驱动板侧的…

day50-springboot+ajax分页

分页依赖&#xff1a; <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency> 配置&#xff1a; …

spring — Spring Security 5.7与6.0差异性对比

1. spring security Spring Security 是一个提供身份验证、授权和针对常见攻击保护的框架。 凭借对保护命令式和反应式应用程序的一流支持&#xff0c;它成为基于Spring的标准安全框架。 Spring Security 在最近几个版本中配置的写法都有一些变化&#xff0c;很多常见的方法都…

Unity通过代码切换材质

效果展示 代码 using System.Collections; using System.Collections.Generic; using UnityEngine;public class MaterialSwitcher : MonoBehaviour {public Material newMaterial; // 新材质private Material oldMaterial; // 旧材质private Renderer renderer; // 渲染器组件…

C# Onnx Paddle模型 OCR识别服务

效果 项目 可运行程序exe下载 Demo&#xff08;完整源码&#xff09;下载

中小学分班查询系统如何制作?这个方法值得借鉴

暑假即将结束&#xff0c;新学年即将开始&#xff0c;学校面临着一个重要的任务&#xff0c;那就是学生的分班问题。这个问题涉及到新生入学的分班&#xff0c;以及低年级学生升入高年级时的分班。对于负责分班的老师们来说&#xff0c;这无疑增加了不少工作量和挑战。 在开学…

弹性布局,网格布局,JavaScript

弹性盒子布局&#xff08;Flexbox Layout&#xff09;&#xff1a;通过display: flex;设置容器为弹性盒子&#xff0c;可以实现更复杂的自适应和响应式布局。 网格布局&#xff08;Grid Layout&#xff09;&#xff1a;通过display: grid;设置容器为网格布局&#xff0c;可以将…