一、题目描述
小杨申请了一个保密柜,但是他忘记了密码。只记得密码都是数字,而且所有数字都是不重复的。
请你根据他记住的数字范围和密码的最小数字数量,帮他算下有哪些可能的组合。
规则如下:
- 输出的组合都是从可选的数字范围中选取的,且不能重复;
- 输出的密码数字要按照从小到大的顺序排列,密码组合需要按照字母顺序,从小到大的顺序排序。
- 输出的每一个组合的数字的数量要大于等于密码最小数字数量;
- 如果可能的组合为空,则返回"None"
二、输入输出描述
输入描述
- 第一行:可能的密码数字列表,数字间以半角逗号分隔
- 第二行:密码最小数字数量
输出描述
- 可能的密码组合,每种组合显示成一行,每个组合内部的数字以半角逗号分隔,从小到大的顺序排列。
- 输出的组合间需要按照字典序排序。比如:2,3,4放到2,4的前面
备注
字典序是指按照单词出现在字典的顺序进行排序的方法,比如:
- a排在b前
- a排在ab前
- ab排在ac前
- ac排在aca前
三、示例
|----|-----------------------------------------------|
| 输入 | 2,3,4 2 |
| 输出 | 2,3 2,3,4 2,4 3,4 |
| 说明 | 最小密码数量是两个,可能有三种组合: 2,3 2,4 3,4 三个密码有一种: 2,3,4 |
|----|---------------------------------|
| 输入 | 2,0 1 |
| 输出 | 0 0,2 2 |
| 说明 | 可能的密码组合,一个的有两种: 0 2 两个的有一个: 0,2 |
四、解题思路
- 核心思想
通过 **"数组升序排序 + DFS 递归回溯 + 树层去重"** 生成所有长度≥指定level的不重复升序组合,再对组合进行字典序排序后输出;核心是 "排序保证内部升序,递归回溯枚举所有组合,树层去重避免重复,字典序排序规范输出"。
- 问题本质分析
- 表层问题:找出数组中所有长度≥
level的不重复升序组合,按字典序排序输出; - 深层问题:
-
组合生成问题:本质是 "无重复元素的子集枚举"(子集长度≥level),需保证元素不重复选取且顺序升序;
-
去重问题:原数组存在重复元素时,需避免生成内容相同的组合,树层去重是高效解决方案;
-
顺序问题:需满足两个层级的顺序要求(组合内部数值升序、组合之间字典序);
-
边界问题:当数组长度 <
level时,无合法组合,需返回None。 -
核心逻辑
- 排序预处理:对原数组升序排序,一是保证生成的组合内部天然升序,二是为树层去重提供条件(重复元素相邻);
- 递归回溯枚举:以
index为遍历起始点,递归选取元素构建路径,回溯重置路径,枚举所有长度≥level的组合; - 树层去重:跳过相邻重复元素,避免生成重复组合;
- 结果处理:对合法组合进行字典序排序,按格式拼接输出,无结果则返回
None。
-
步骤拆解
-
输入解析与预处理
- 读取逗号分隔的整数数组和最小长度
level; - 对整数数组进行升序排序,为组合内部升序和树层去重做准备。
- 读取逗号分隔的整数数组和最小长度
-
DFS 递归生成不重复组合
- 初始化结果集合和路径容器(LinkedList,方便回溯移除末尾元素);
- 调用 DFS 方法,传入起始索引
0、空路径、结果集合:
- 递归终止条件(合法组合记录):若路径长度≥
level,将路径转为逗号分隔的字符串,存入结果集合; - 遍历数组:从当前
index开始,避免重复选取元素; - 树层去重:若当前元素与前一个元素重复,跳过该元素;
- 选择元素:将当前元素转为字符串加入路径;
- 递归深入:以
i+1为新的起始索引,继续生成组合; - 回溯重置:递归返回后,移除路径末尾的元素,准备选取下一个元素。
-
结果排序与输出
- 若结果集合非空:对集合按字符串字典序排序,再用换行符拼接所有组合,返回拼接后的字符串;
- 若结果集合为空:返回
None。
五、代码实现
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Scanner;
public class Main {
static int[] nums;
static int level;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
nums = Arrays.stream(sc.nextLine().split(",")).mapToInt(Integer::parseInt).toArray();
level = Integer.parseInt(sc.nextLine());
System.out.println(getResult());
}
public static String getResult() {
// 按照数值大小升序,这样后续形成的组合的内部就是按照数值大小升序的
Arrays.sort(nums);
// 求不重复组合
ArrayList<String> res = new ArrayList<>();
dfs(0, new LinkedList<>(), res);
if (res.size() > 0) {
// 组合间按照字典序排序
res.sort(String::compareTo);
return String.join("\n", res);
} else {
return "None";
}
}
public static void dfs(int index, LinkedList<String> path, ArrayList<String> res) {
if (path.size() >= level) {
// 如果path层数到达level层,则记录该组合
res.add(String.join(",", path));
}
for (int i = index; i < nums.length; i++) {
// 树层去重
if (i > 0 && nums[i] == nums[i - 1]) continue;
path.add(nums[i] + "");
dfs(i + 1, path, res);
path.removeLast();
}
}
}