【JAVA-排列组合】一个套路速解排列组合题

说明

在初遇排列组合题目时,总让人摸不着头脑,但是做多了题目后,发现几乎能用同一个模板做完所有这种类型的题目,大大提高了解题效率。本文简要介绍这种方法。

题目列表

所有题目均从leetcode查找,便于在线验证
46.全排列
47.全排列 II
78.子集
90.子集 II
39.组合总和
40.组合总和 II

模板代码

本文所有题目都可以用以下模板代码解决:

public class Template{private List<List<Integer>> res = new ArrayList<>();public List<List<Integer>> permute(int[] nums) {LinkedList<Integer> path = new LinkedList<>();dfs(nums, path);return res;}private void dfs(int[] nums, LinkedList<Integer> path) {if (path.size() == nums.length) { res.add(new ArrayList<>(path));return;}for (int i = 0; i < nums.length; i++) {path.addLast(nums[i]);dfs(nums,path);path.removeLast();}}
}

上述代码是求nums(无重复元素)的全排列,每个元素允许选择多次。以1,2,3为例,如下图所示,从上往下看,选择第一个元素的时候,
可以选择1,2,3,假设第一个选定为1(将选定的元素存入path中,即path=[1]),那么第二个元素也能选择1,2,3,同理,第二个元素也选择1,即path=[1,1]时,选择第三个元素,依然能选择1,2,3。当第三个元素选定后,此时path的长度等于nums的长度,一个排列结果就计算出来了,加入到结果res中去,接着回溯,按照同样的逻辑运行下去,最后得到全排列结果。
在这里插入图片描述

题解

46. 全排列

题目描述

给定一个不含重复数字的数组 nums ,返回其所有可能的全排列 。你可以按任意顺序返回答案。
示例 1:

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

示例 2:

输入: nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入: nums = [1]
输出:[[1]]

思路

和模板代码相比,只多一个限制:

  1. 一个元素只能选择一次。

还是以1,2,3为例,如下图,当path=[1],选择第二个元素时,由于已经选择了1,所以再选择1时,应该被剪掉(红叉表示)。
为了判断某个元素是否被使用过,可以定义一个used数组,维护方式如下:

  1. 当元素被加入path中时,该元素被使用,used[i]=1;
  2. 当元素被移除path时,该元素未被使用,used[i]=0;
    在计算时,如果发现元素已经被使用,则剪枝。

在这里插入图片描述

完整代码

package leetcode.plzh;import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;public class Permute_046 {private List<List<Integer>> res = new ArrayList<>();public List<List<Integer>> permute(int[] nums) {if(nums.length==0) return res;LinkedList<Integer> path = new LinkedList<>();int[] used = new int[nums.length];dfs(nums, path, used);return res;}private void dfs(int[] nums, LinkedList<Integer> path, int[] used) {if (path.size() == nums.length) {res.add(new ArrayList<>(path));return;}for (int i = 0; i < nums.length; i++) {if(used[i]==1) continue;//剪枝,同个元素不能选择多次path.addLast(nums[i]);used[i] = 1;dfs(nums,path,used);path.removeLast();used[i] = 0;}}
}

执行结果

在这里插入图片描述

小结

求数字数组(无重复元素)的全排列,在模板代码的基础上修改:

  1. 已经选择过的数字不能重复选择(使用used数组判断某个元素是否被使用过)

47.全排列 II

题目描述

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:

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

示例 2:

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

思路

和模板代码相比,多了以下限制:

  1. 一个元素只能选择一次。
  2. 可能存在重复元素

重复元素造成之前的全排列结果存在重复,现在的问题是怎么去重?
以1,1,2为例,如下图,我们第一个元素可以选择1,1,2,很明显选择第一个1的排列和选择第二个1的排列情况相同,所以选择第二个1的时候应该剪枝。为了判断重复,可以先将nums从小到大排序,如果:i>0&&nums[i]==nums[i-1],说明重复,应该剪枝(i等于0时,代表该元素第一次被选择,肯定不存在重复)。

在这里插入图片描述

需要注意的是,再上图绿色标记部分,此时path=[1], 选择第二个元素时,遍历i的范围为0,1,2。即第二个元素有可能加入nums[0],nums[1],nums[2]。

  1. i=0时,如果第二个元素选择nums[0],因为path中已经选择了第一个1,所以剪枝(used[i]==1)
  2. i=1时,path:[1,1]
  3. i=2时,path:[1,2]

上面的步骤2来看,满足条件:i>0&&nums[i]==nums[i-1],按照上面的逻辑,应该被剪枝,但是显然[1,1,2]是一个合法的排列结果,不应该被剪掉。仔细观察发现,只有同层存在相同元素时才应该剪枝,不同层则不应该剪。

  1. 对于应该被剪枝的部分(红x标记),回溯后,第一个1会被标记为未使用,即:nums[i-1]=0
  2. 对于不应该被剪枝的部分(绿色标记),第一个1会被标记为使用,即:nums[i-1]=1

现在我们只取情况1,所以判断条件可以改写为:nums[i]==nums[i-1]&&used[i-1]==0

完整代码

package leetcode.plzh;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;public class PermuteUnique_047 {private List<List<Integer>> res = new ArrayList<>();public List<List<Integer>> permuteUnique(int[] nums) {if (nums.length == 0) return res;List<Integer> path = new ArrayList<>();int[] used = new int[nums.length];Arrays.sort(nums);//排序,方便判断同层是否重复,nums[i-1]==nums[i]则重复dfs(nums, path, used);return res;}private void dfs(int[] nums, List<Integer> path, int[] used) {if (path.size() == nums.length) {res.add(new ArrayList<>(path));return;}for (int i = 0; i < nums.length; i++) {if (used[i] == 1) continue;//剪枝,同个元素不能选多次,if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == 0) continue;//剪枝,避免同层重复path.add(nums[i]);used[i] = 1;dfs(nums, path, used);path.remove(path.size() - 1);used[i] = 0;}}
}

执行结果

在这里插入图片描述

小结

求数字数组(有重复元素)的全排列,在模板代码的基础上修改:

  1. 已经选择过的数字不能重复选择(使用used数组判断某个元素是否被使用过)
  2. 使用nums[i]==nums[i-1]判断重复:对nums从小到大排序
  3. 同层重复剪枝(nums[i]==nums[i-1]&&used[i-1]==0)

78.子集

题目描述

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

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

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

示例 2:

输入: nums = [0]
输出: [[],[0]]

思路

和模板代码相比,多了以下限制:

  1. 一个元素只能选择一次。
  2. 求子集,其长度不一定是nums.length,而是在这个范围:[0,nums.length]
  3. 求的是组合,而非排列,即[1,2],[2,1]是同一种结果

对于限制2:

长度不再是nums.length,那么在向res加入path时,应该分别判断长度是0~nums.length时,加入结果。

对于限制3:
以1,2,3为例,如果将nums排序后,path后入的元素比上一个元素还要小时,应该剪枝。
在这里插入图片描述

完整代码

package leetcode.plzh;import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;public class Subsets_078 {private List<List<Integer>> res = new ArrayList<>();public List<List<Integer>> subsets(int[] nums) {if (nums.length == 0) return res;LinkedList<Integer> path = new LinkedList<>();int[] used = new int[nums.length];Arrays.sort(nums);//保证后入的元素一定大于先入的元素,所以排序for (int i = 0; i <= nums.length; i++) {dfs(nums, path, used, i);}return res;}private void dfs(int[] nums, LinkedList<Integer> path, int[] used, int len) {if (path.size() == len) {res.add(new ArrayList<>(path));return;}for (int i = 0; i < nums.length; i++) {if (used[i] == 1) continue;//剪枝,同个元素不能选多次,if (!path.isEmpty() && nums[i] < path.peekLast()) continue;//剪枝,选择的下个元素比上个元素还要小path.addLast(nums[i]);used[i] = 1;dfs(nums, path, used, len);path.removeLast();used[i] = 0;}}
}

执行结果

在这里插入图片描述

优化代码

可以优化如下:

dfs中的for循环不是固定从0开始,而是从传入的begin开始。第一个元素从0开始找,第二个元素就只能从1开始找。总是从排序数组的下个元素找,包含两个隐含信息,同一个元素不可能被同时选择多次;下一个总是大于上一个元素。所以之前的剪枝逻辑都可以去掉。

package leetcode.plzh;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;public class Subsets_078 {private List<List<Integer>> res = new ArrayList<>();public List<List<Integer>> subsets(int[] nums) {if (nums.length == 0) return res;List<Integer> path = new ArrayList<>();Arrays.sort(nums);dfs(nums, 0,path);return res;}private void dfs(int[] nums, int begin,List<Integer> path) {res.add(new ArrayList<>(path));for (int i = begin; i < nums.length; i++) {path.add(nums[i]);dfs(nums, i+1,path);//不能继续找当前元素,直接找下个元素,path中不可能选择到同一个元素,下一个也始终比上一个大path.remove(path.size() - 1);}}
}

优化执行结果

在这里插入图片描述
备注:后面的组合题,都可以使用这个模板

小结

求数组(无重复元素)的子集:
1.对nums排序
2.修改dfs中的for循环,让i从begin开始,下次遍历时用dfs(nums, i+1,path)

90.子集 II

题目描述

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:

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

示例 2:

输入: nums = [0]
输出: [[],[0]]

思路

和78 子集相比,多了以下限制:

  1. nums可能包含重复数组

去重逻辑:同层相同则剪枝,nums[i]==nums[i-1]&&used[i-1]==0
在这里插入图片描述

完整代码

package leetcode.plzh;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;public class SubsetsWithDup_090_02 {private List<List<Integer>> res = new ArrayList<>();public List<List<Integer>> subsetsWithDup(int[] nums) {if (nums.length == 0) return res;List<Integer> path = new ArrayList<>();int[] used = new int[nums.length];Arrays.sort(nums);dfs(nums, 0, path, used);return res;}private void dfs(int[] nums, int begin, List<Integer> path, int[] used) {res.add(new ArrayList<>(path));for (int i = begin; i < nums.length; i++) {if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == 0) continue;//同层相同,则剪枝path.add(nums[i]);used[i] = 1;dfs(nums, i + 1, path, used);path.remove(path.size() - 1);used[i] = 0;}}
}

执行结果

在这里插入图片描述

小结

求数组(有重复元素)的子集:
1.对nums排序
2.修改dfs中的for循环,让i从begin开始,下次遍历时用dfs(nums, i+1,path)
3. 增加同层相同元素的剪枝逻辑:i > 0 && nums[i] == nums[i - 1] && used[i - 1] == 0

39.组合总和

题目描述

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。
示例 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:

输入: candidates = [2], target = 1
输出: []

思路

和78 子集相比,多了以下限制:

  1. 一个元素可以选择多次
  2. 目标和要等于target

对于限制1:修改dfs中下一个遍历为:dfs(nums, i,path),
对于限制2:只有当目标和等于target时,才加入res中,为了避免死循环,比如一直选第一个元素,当path中的和大于target时,应该中止该分支的查找(不再向path中加入新的值),直接return。

完整代码

package leetcode.plzh;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;public class CombinationSum_039 {private List<List<Integer>> res = new ArrayList<>();public List<List<Integer>> combinationSum(int[] candidates, int target) {List<Integer> path = new ArrayList<>();Arrays.sort(candidates);dfs(candidates, 0, path, target);return res;}private static int cnt = 0;private void dfs(int[] candidates, int begin, List<Integer> path, int target) {int total = path.stream().reduce(0, Integer::sum);if (total > target) return;if (total == target) {res.add(new ArrayList<>(path));return;}for (int i = begin; i < candidates.length; i++) {path.add(candidates[i]);dfs(candidates, i, path, target);path.remove(path.size() - 1);}}
}

执行结果

在这里插入图片描述

优化代码

在上面的代码中,每次dfs都是要对path求和,效率低下,我们可以直接传入target,固定第一个元素后,找下一个元素,target应该要减去当前元素。比如要在2,3,5中找和为8的组合,那么固定第一个元素2,下面就应该时找等于8-2的组合。当target为0时,说明path的和就等于target,当target小于0时,说明path中的累加和已经超过了原来的target,此时return。

package leetcode.plzh;import java.util.*;public class CombinationSum_039 {private List<List<Integer>> res = new ArrayList<>();public List<List<Integer>> combinationSum(int[] candidates, int target) {List<Integer> path = new ArrayList<>();Arrays.sort(candidates);dfs(candidates, 0, path, target);return res;}private void dfs(int[] candidates, int begin, List<Integer> path, int target) {if(target<0) return;if (target == 0) {res.add(new ArrayList<>(path));return;}for (int i = begin; i < candidates.length; i++) {path.add(candidates[i]);dfs(candidates, i, path, target-candidates[i]);path.remove(path.size() - 1);}}
}

优化执行结果

时间由原来的21ms降低为3ms
在这里插入图片描述

40.组合总和 II

题目描述

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次 。

注意:解集不能包含重复的组合。

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]

思路

和39 组合总和相比,多了以下限制:

  1. 一个元素只能选择一次
  2. 可能存在重复元素

对于限制1

可以dfs中遍历时,查找下一个元素即可:dfs(candidates, i+1, path, target-candidates[i]);

对于限制2:

新增去重逻辑:同层相同则剪枝,nums[i]==nums[i-1]&&used[i-1]==0

完整代码

package leetcode.plzh;import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;public class CombinationSum2_040 {private List<List<Integer>> res = new ArrayList<>();public List<List<Integer>> combinationSum2(int[] candidates, int target) {Arrays.sort(candidates);LinkedList<Integer> path = new LinkedList<>();int[] used = new int[candidates.length];dfs(candidates, 0, path, target,used);return res;}public void dfs(int[] candidates, int begin, LinkedList<Integer> path, int target,int[] used) {if (target < 0) return;if (target == 0) {res.add(new ArrayList<>(path));}for (int i = begin; i < candidates.length; i++) {if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==0) continue; //同层相同剪枝path.addLast(candidates[i]);used[i] = 1;dfs(candidates, i + 1, path, target - candidates[i],used);path.removeLast();used[i] = 0;}}
}

执行结果

在这里插入图片描述

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

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

相关文章

C语言判断素数(ZZULIOJ1057:素数判定)

题目描述 输入一个正整数n&#xff0c;判断n是否是素数&#xff0c;若n是素数&#xff0c;输出”Yes”,否则输出”No”。 注意&#xff1a;1不是素数。 输入&#xff1a;输入一个正整数n(n<1000) 输出&#xff1a;如果n是素数输出"Yes"&#xff0c;否则输出"…

spark性能调优 | 默认并行度

Spark Sql默认并行度 看官网&#xff0c;默认并行度200 https://spark.apache.org/docs/2.4.5/sql-performance-tuning.html#other-configuration-options 优化 在数仓中 task最好是cpu的两倍或者3倍(最好是倍数&#xff0c;不要使基数) 拓展 在本地 task需要自己设置&a…

如何使用Matplotlib模块的text()函数给柱形图添加美丽的标签数据?

如何使用Matplotlib模块的text函数给柱形图添加美丽的标签数据&#xff1f; 1 简单引入2 关于text()函数2.1 Matplotlib安装2.2 text()引入2.3 text()源码2.4 text()参数说明2.5 text()两个简单示例 3 柱形图绘制并添加标签3.1 目标数据3.2 读取excel数据3.3 设置窗口大小和xy轴…

如何提升软件测试效率?本文为你揭示秘密

在软件开发中&#xff0c;测试是至关重要的一个环节。它能帮助我们发现并修复问题&#xff0c;从而确保我们提供的软件具有高质量。然而&#xff0c;测试过程往往费时费力。那么&#xff0c;有没有方法可以提升我们的软件测试效率呢&#xff1f;答案是肯定的。下面&#xff0c;…

骨传导耳机品牌排名前十,盘点最受欢迎的五款TOP级骨传导耳机

骨传导耳机品牌排名前十&#xff0c;最受欢迎的五款TOP级骨传导耳机是什么&#xff1f; 耳机市场上有很多品牌和型号的骨传导耳机&#xff0c;每个人对耳机的需求和使用场景也不尽相同。因此&#xff0c;在选择耳机时&#xff0c;确实不能盲目跟风或者仅仅看重品牌。为了帮助大…

spring cloud之配置中心

Config 统一配置中心(*) 1.简介 # 统一配置中心 - 官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.2.3.RELEASE/reference/html/#_spring_cloud_config_server- config 分为 config server 和 config client。用来统一管理所有微服务的配置统一配置…

ChatGPT 从零到一打造私人智能英语学习助手

近几年&#xff0c;随着智能化技术的发展和人工智能的兴起&#xff0c;越来越多的应用程序开始涌现出来。在这些应用中&#xff0c;语音识别、自然语言处理以及机器翻译等技术都得到了广泛的应用。其中&#xff0c;聊天机器人成为了最受欢迎的人工智能应用之一&#xff0c;它们…

element-china-area-data使用问题

使用CodeToText报错&#xff0c;下载的时候默认下载最新版本的&#xff0c; 稳定版本5.0.2版本才可以 npm install element-china-area-data5.0.2 -S

日志存档及解析

网络中的每个设备都会生成大量日志数据&#xff0c;日志数据包含有关网络中发生的所有活动的关键信息&#xff0c;存储所有这些数据并对其进行管理对组织来说是一项挑战&#xff0c;因此&#xff0c;这些日志文件被压缩并存储在效率较低的存储介质中&#xff0c;无法轻松检索。…

简单介绍二分类问题评价指标

正确率(Accuracy) Accuracy ​(TP TN)/(TP TN FP FN)精准率(Precision) 记忆&#xff1a;在识别出某标签中正确的比例&#xff1b; 比如识别为某标签的一共有105个&#xff0c;其中有95个是识别对的&#xff0c;那Precision就是95/105&#xff1b; TP/(TPFP)召回率(Recall…

浏览器插件在content_script和top窗口之间进行消息通信

为什么要进行消息通信&#xff1f; content_script和top窗口之间除了DOM共享之外&#xff0c;window对象是不共享的。如果content_script需要获得top窗口中window对象的数据&#xff0c;就需要使用到通信。反之&#xff0c;也是相同的情况。 1、自定义监听事件&#xff08;推荐…

【Kingbase FlySync】界面化管控平台:1.安装部署与用户创建

同步软件安装部署与用户创建 概述准备环境目标资源1.测试虚拟机下载地址包含node1,node22.KFS管控平台工具下载地址3.临时授权下载地址 实操&#xff1a;同步软件安装部署1.node1准备安装环境(1)增加flysync 用户并设置密码(2)调整flysync的最大文件句柄数&#xff08;open fil…

Django 配置 Email Admin 详细指南

概要 Django 是一个高级的 Python Web 框架&#xff0c;它鼓励快速开发和清洁、实用的设计。当你正在开发一个 Django 项目时&#xff0c;监控网站的运行情况是非常必要的。Django 提供了一个功能强大的 admin 界面&#xff0c;但同时也可以通过配置 email admin 来获取网站的…

十大热门骨传导蓝牙耳机排行榜,精选最佳的五款骨传导蓝牙耳机

排行榜十大热门骨传导耳机&#xff0c;哪些才是综合实力最强的骨传导耳机&#xff1f; 近年来&#xff0c;骨传导耳机越来越受欢迎。由于骨传导耳机不需要插入耳朵&#xff0c;用户能够同时感知周围环境的声音&#xff0c;不会完全隔绝外界&#xff0c;增加了使用时的安全性。…

Win10远程连接服务器失败,报错:出现了内部错误

背景&#xff1a;本地windows10专业版电脑远程Windows虚拟机报错&#xff0c;但实际检查控制台发现&#xff0c;虚拟机状态正常&#xff0c;只是本地远程连接莫名其妙断开&#xff0c;并报错出现了内部错误&#xff1a; 原因&#xff1a;win10客户端RDP兼容性的问题 解决方法&…

基于plc的柔性制造系统供料检测单元的设计(论文+源码)

1.系统设计 本次基于plc的柔性制造系统供料检测单元的设计&#xff0c;其系统结构框图如图2.1所示&#xff0c;系统采用西门子S7-200 型号的PLC作为主控制器&#xff0c;并结合温度传感器&#xff0c;重量传感器&#xff0c;限位开关&#xff0c;变频器等器件来构成整个系统&a…

【Vue-Demo】倒计时3秒后返回首页

首页path:/ 倒计时结束后要清除计时器&#xff0c;防止内存泄漏&#xff1a; if (this.count 0) {clearInterval(this.timer); }<!-- ErrorJump.vue --> <template><h2>Error&#xff1a;找不到页面&#xff01;</h2><h4>{{ count }}S后<R…

pdb restore in ADG database

Effect of PITR on Dataguard Environment (Standby MRP Crashed with ORA-39873) (Doc ID 1591492.1)​编辑To Bottom In this Document Symptoms Cause Solution APPLIES TO: Oracle Database Cloud Exadata Service - Version N/A and later Oracle Database Cloud Servic…

go语言学习之旅之Go语言数据类型

学无止境&#xff0c;今天学习Go 语言数据类型 Go&#xff08;或Golang&#xff09;是一种静态类型语言&#xff0c;这意味着变量的数据类型必须显式声明&#xff0c;并且在运行时不能更改。以下是Go中的一些基本数据类型&#xff1a; 这里仅介绍最常用的类型 数值类型: int: …