枚举法
枚举算法解题的基本思路:
- 确定枚举解的范围,以及判断条件
- 选取合适枚举方法,进行逐一枚举,此时应注意能否覆盖所有的可能的解,同时避免重复。
- 在枚举时使用判断条件检验,留下所有符合要求的解。
1. 简单型枚举
就是可以通过简单的 for 循环嵌套就可以解决的问题
42 点问题
题目描述:
众所周知在扑克牌中,有一个老掉牙的游戏叫做24点,选取4张牌进行加减乘除,看是否能得出24这个答案。 现在小蓝同学发明了一个新游戏,他**从扑克牌中依次抽出6张牌,注意不是一次抽出,进行计算,看是否能够组成 42 点,满足输出YES,反之输出 NO。 最先抽出来的牌作为第一个操作数,抽出牌做第二个操作数,运算结果在当作第一个操作数,继续进行操作。 除不尽的情况保留整数。 **请设计一个程序对该问题进行解答。
样例:
输入:
K A Q 6 2 3
输出:
YES
对于上面的样例我们进行了如下计算;
1. K*A=K 即 13*1=13
2. 13/12=1 保留整数
3. 1+6=7
4. 7*2=14
5. 14*3=42
普及扑克牌常识:
一副扑克牌共有54张牌,其中52张是正牌,另2张是副牌(大王和小王)。
52张正牌又均分为13张一组,并 黑桃 ,红桃 、 梅花 、方块四种花色表示各组,每组花色的牌包括从1-10( 1通常表示为A) 以及J、Q、K标示的13张牌
对应此题:J、Q、K代表数字11,12,13;就是1到13各有4张
题目解析:
可以依次枚举数字,然后在枚举数字间的符号即可;
创建 5 个 Vector ,分别用来存放 1-5 次的运算结果;
op1 赋值为 第一个数
op(op[1] op[2])
{
for op in [+ - * /]
ans = 第一个操作数op1 操作 第二个操作数op2
如果是第六个操作数,就检验是否符合要要求 ==42? 如果是就返回True
如果op(ans , op[3]) 返回 True,就返回True 因为找到了答案,否则就继续进行
没有找到答案返回False
}
代码实现(此题还是没有搞明白??)
package 枚举法;
import java.util.Scanner;
import java.util.Vector;
//依次抽出六张牌,进行五次运算(可以是 + - * / 中的一种) 看最后的结果是否为42
//创建 5 个 Vector ,分别用来存放 1-5 次的运算结果
public class SiShiErDian {
static int[] a = new int[10];
static Vector<Vector<Integer>> ans = new Vector<>();
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
for (int i = 0; i < 6; i++) {//循环实现抽六张牌,并将其对应的数字放到了a数组中保存
String c = in.next();
//调用String类的 char charAt(int index) 返回指定的在字符串中下标为index出的字符
if (c.charAt(0) == 'A')
a[i] = 1;
else if (c.charAt(0) == 'J')
a[i] = 11;
else if (c.charAt(0) == 'Q')
a[i] = 12;
else if (c.charAt(0) == 'K')
a[i] = 13;
else
a[i] = (c.charAt(0) - '0');
}
ans.addElement(new Vector<Integer>());//void java.util.Vector.addElement(Vector<Integer> obj)
ans.get(0).addElement(a[0]);
for (int i = 1; i <= 5; i++) {
ans.addElement(new Vector<Integer>());
for (int j = 0; j < ans.get(i - 1).size(); j++) {
ans.get(i).addElement(ans.get(i - 1).get(j) + a[i]);
ans.get(i).addElement(ans.get(i - 1).get(j) - a[i]);
ans.get(i).addElement(ans.get(i - 1).get(j) * a[i]);
ans.get(i).addElement(ans.get(i - 1).get(j) / a[i]);
}
}
/*K A Q 6 2 3*/
int flag = 0;
for (int i = 0; i < ans.get(5).size(); i++) {
if (ans.get(5).get(i) == 42) {
flag = 1;
break;
}
}
if (flag == 1)
System.out.println("YES");
else
System.out.println(" NO");
}
}
/*1. public class Vector<E> implements List<E> List集合中存储元素特点:有序可重复(有下标),有序指存进去的顺序与取出时相同
*2. Vector集合底层是一个数组,初始化容量是10
*3. public Vector(Collection<? extends E> c) {
Object[] a = c.toArray();
elementCount = a.length;
}
4. public synchronized void addElement(E obj) {
modCount++;
add(obj, elementData, elementCount);
}*/
2. 组合型枚举(参考递归实现组合算法)
就是排列组合, 就是让你在 n 个中,随机选出 m 个,问你有多少种方案,而且每一种方案选择了哪 m 个,这就是组合型枚举。
组合型枚举有固定的流程,即有着固定的算法模板:(以下是给定的数字中没有重复项进行组合)
import java.util.ArrayList;
import java.util.List;
public class ZuHe {
//暂时存储组合的一种情况
private static List<Integer> outArr = new ArrayList<Integer>();
/*outArr中存放的数量*/
private static int index=0;
/**
*
* @param inArr 待组合的数组
* @param n 表示inArr位置,如n=0则从inArr[0]到inArr[length-1]选取数字;n=1,则从inArr[1]到inArr[length-1]选取数字,length为数组长度
* @param num 要选出数字的数量
*/
public static void comNum(int[] inArr,int n,int num) {
if(num<=0) { //num=0 递归出口
//打印数组
for(int i:outArr) {
System.out.print(i);
}
System.out.println();
}else {
for(int i=n;i<=inArr.length-num;i++) {
if(outArr.size()<index+1) {
outArr.add(index++,inArr[i]);
} /*此处两行为防止集合越界空指针异常,如果使用数组存放也可,即此处实现了选取一个元素放进输出数组里,对应上述文字说明2的每一步的a操作*/
else {
outArr.set(index++, inArr[i]);
}
comNum(inArr,i+1,num-1); /*在选取的元素后面的元素中选取num-1个元素,对应文字说明2中每一步的b操作*/
index--; //重新给输出数组定位,选取一下个序号的元素 就是选择下一个放在第一位的数字
}
}
}
public static void main(String[] arg) {
int[] arr = {1,2,3,4,5};
comNum(arr,0,2);
}
}
/*
12
13
14
15
23
24
25
34
35
45
*/
例题:
题目描述:
小A的学校,蓝桥杯的参赛名额非常有限,只有m个名额,但是共有n个人报名其中m<=n。,作为老师非常苦恼,他不知道该让谁去,他在寻求一个绝对公平的方式。于是他准备让大家抽签决定,即m个签是去,剩下的是不去。 小A非常想弄明白最后的抽签结果是什么样子的,到底有多少种结果。 请设计一个程序帮助小A。最后输出各种情况的人名即可,一行一种情况,每种情况的名字按照报名即输入顺序排序。 第一行 输入 N M 第二行 到 第 N+1 行 共输入 N 个人名 每种情况输出 M 个人名,空格隔开。
样例:
输入: 3 2
xiaowang
xiaoA
xiaoli
输出:
xiaowang xiaoA
xiaowang xiaoli
xiaoA xiaoli
题目解析:
实际上还是组合型枚举,但是输出元素为人名,我们可以将人名存起来,输出的时候,根据数字下标找到提前存好的人名,直接输出即可。
代码实现:
package 枚举法;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class ZuHe3 {
// 暂时存储组合的一种情况
static List<Integer> chosen = new ArrayList<Integer>();
// 存储组合的下标/人数
static int index = 0;
// 存放人名
static List<String> name = new ArrayList<>();
//
static List<Integer> nameIndex = new ArrayList<>();
static int n;
static int m;// 选m个数
public static void comNum(List<Integer> nameIndex, int n, int m) {
if (m <= 0) { // num=0 递归出口
for(int i=0;i<chosen.size();i++) {
System.out.print(name.get(chosen.get(i))+" ");
}
System.out.println();
} else {
for (int i = n; i <= nameIndex.size() - m; i++) {
if (chosen.size() < index + 1) {
chosen.add(index++, nameIndex.get(i));
} /* 此处两行为防止集合越界空指针异常,如果使用数组存放也可,即此处实现了选取一个元素放进输出数组里 */
else {
chosen.set(index++, nameIndex.get(i));
}
comNum(nameIndex, i + 1, m - 1); /* 在选取的元素后面的元素中选取num-1个元素 */
index--; // 重新给输出数组定位,就是选择下一个放在第一位的数字
}
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
// 将所有人名保存
for (int i = 0; i < n; i++) {
String string = in.next();
name.add(string);
}
for (int i = 0; i < name.size(); i++) {
nameIndex.add(i);
}
comNum(nameIndex, 0, 2);
}
/*
3 2
xiaowang
xiaoA
xiaoli
xiaowang xiaoA
xiaowang xiaoli
xiaoA xiaoli
*/
}
3. 排列型枚举(参考全排列算法)
排列型枚举,就是 n 个的全排列,即从 n 个中选取 n 个但是关心内部的顺序。
相比较组合只关心有多少个集合,而排列是关心集合内的排列方式。
package 枚举法;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
//实现从n个数字里面取n个数字进行全排列(此时,n个数字不重复)
public class PaiLie {
static int count = 0;// 统计种数
// 存放要排列的全部元素
static List<Integer> list1 = new ArrayList<Integer>();
// 存放每一种排列的情况
static List<Integer> list2 = new ArrayList<Integer>();
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
// 使用List2集合保存每次取出的一种排列,但不是直接add添加,先将集合的长度进行固定,要通过添加元素的方式固定
for(int i = 0;i<n;i++)
{
list2.add(-1);
} // 实现集合的长度固定为n
//初始化要排列的数字
for(int i=0;i<n;i++) {
int number = scanner.nextInt();
list1.add(number);
}
sortAll(list2, list1, n);
System.out.println("共有"+count+"种排列方式");
}
//方法实现全排列
/**
* @param list2 存放要排列的全部元素
* @param list1 存放每一种排列的情况
* @param n 要排列的数字个数
*/
public static void sortAll(List<Integer> list2, List<Integer> list1, int n) {
// 出口
if (n == 0) {// 取出其中一种一种情况的排列组合
System.out.println(list2);
count++;
return;
}
for (int i = 0; i < list1.size(); i++) {
if (!list2.contains(list1.get(i))){// 将数组中的元素依次取出,若集合中不包含此元素,就添加到集合中
list2.set(list2.size() - n,list1.get(i));// set(下标,元素) 若n=3, 则list.size()-n=3-3=0
} else {
continue;
}
sortAll(list2, list1, n - 1);// 更新n
list2.set(list2.size() - n, -1);// 还原 特别注意:原来集合中只有n位-1
}
}
}
/*输出结果:
3
1
2
3
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]
共有6种排列方式
*/
例题:
题目描述:
小 A 的学校,老师好不容易解决了蓝桥杯的报名问题,现在老师又犯愁了。现在有 N 位同学参加比赛,但是老师想给他们排座位,但是排列方式太多了。老师非常想弄明白最后的排座次的结果是什么样子的,到底有多少种结果。 请设计一个程序帮助老师。 最后输出各种情况的人名即可,一行一种情况,每种情况的名字按照报名即输入顺序排序。 第一行 输入 N; 第二行 到 第N+1 行 共输入 N 个人名。 由于小 A 学校承办能力实在有限,所以其中 N 小于等于 10 人。
样例:
输入:
3
xiaowang
xiaoA
xiaoli
输出:
xiaowang xiaoA xiaoli
xiaowang xiaoli xiaoA
xiaoA xiaowang xiaoli
xiaoA xiaoli xiaowang
xiaoli xiaowang xiaoA
xiaoli xiaoA xiaowang
题目解析:
实际上还是排列型枚举,但是输出元素为人名,我们可以将人名存起来,输出的时候,根据数字下标找到提前存好的人名;
代码实现:
package 枚举法;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class PaiLie2 {
static int count = 0;// 统计种数
// 存放要排列的全部元素
static List<Integer> list1 = new ArrayList<Integer>();
// 存放每一种排列的情况
static List<Integer> list2 = new ArrayList<Integer>();
//存放所有同学的姓名
static List<String> name = new ArrayList<>();
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
// 使用List2集合保存每次取出的一种排列,但不是直接add添加,先将集合的长度进行固定,要通过添加元素的方式固定
for(int i = 0;i<n;i++)
{
list2.add(-1);
} // 实现集合的长度固定为n
//初始化名字集合
for(int i=0;i<n;i++) {
String string = scanner.next();
name.add(string);
}
//初始化要排列的名字所对应的下标,实际上先对各个下标进行排列,最后再输出对应的名字即可。
for(int i=0;i<name.size();i++) {
list1.add(i);
}
sortAll(list2, list1, n);
System.out.println("共有"+count+"种排列方式");
}
//方法实现全排列
/**
* @param list2 存放要排列的全部元素
* @param list1 存放每一种排列的情况
* @param n 要排列的数字个数
*/
public static void sortAll(List<Integer> list2, List<Integer> list1, int n) {
// 出口
if (n == 0) {// 取出其中一种情况的排列组合
for(int i=0;i<list2.size();i++) {
System.out.print(name.get(list2.get(i))+" ");
}
System.out.println();
count++;
return;
}
for (int i = 0; i < list1.size(); i++) {
if (!list2.contains(list1.get(i))){// 将数组中的元素依次取出,若集合中不包含此元素,就添加到集合中
list2.set(list2.size() - n,list1.get(i));// set(下标,元素) 若n=3, 则list.size()-n=3-3=0
} else {
continue;
}
sortAll(list2, list1, n - 1);// 更新n
list2.set(list2.size() - n, -1);// 还原 特别注意:原来集合中只有n位-1
}
}
}
/*结果测验:
3
xiaowang
xiaoA
xiaoli
xiaowang xiaoA xiaoli
xiaowang xiaoli xiaoA
xiaoA xiaowang xiaoli
xiaoA xiaoli xiaowang
xiaoli xiaowang xiaoA
xiaoli xiaoA xiaowang
共有6种排列方式
*/
*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!