全排列(Java代码)——蓝桥杯备赛笔记
- 算法——全排列
- 法一:迭代法
- 代码实现:
- *法二:交换回溯*
- 代码实现:
- 法三:前缀法
- 代码实现
- 真题练习
- 2013JavaB组第9题
- 2014JavaB组第7题
- 2015JavaB组第5题
- 2016JavaB组第3题
- 2016JavaB组第6题
- 2017JavaB组第2题
- 2020模拟省赛第二题
心是比天高,能力却比纸还薄! 初识蓝桥杯,才知道算法的深奥!加油,奥利给!
算法——全排列
所谓全排列:即把元素按照“顺序”排列出来;
例如:ABC的全排列有6种——[ABC, ACB, BAC, BCA, CBA, CAB];
我们可以假设成在三个座位上放东西,第一个位置有三种选择[A,B,C],第二个位置有两种选择,剩下的一个元素放在最后一个位置。可见全排列的个数N就等于元素个数n的阶乘:N=n!
法一:迭代法
代码实现:
import java.util.ArrayList;
public class P1迭代法 {
public ArrayList<String> getPremutation(String A){
int n = A.length();
ArrayList<String> arr = new ArrayList<String>();
arr.add(A.charAt(0) + "");//初始化,包含第一个字符;
//charAt(int index) 返回指定索引处的 char 值。
for (int i = 1; i < n; i++) {//第二个字符插入到前面生成集合的每个元素里面;
ArrayList<String> arr_new = new ArrayList<String>();
char c = A.charAt(i);//新字符
for (String str : arr) {//访问上一趟集合中的每个字符串
//插入到每个位置,形成一个新串
String newStr = c + str;//加在前面
arr_new.add(newStr);
newStr = str + c;//加在后面
arr_new.add(newStr);
//加在中间
for (int j = 1; j < str.length(); j++) {
newStr = str.substring(0,j) + c + str.substring(j);
//substring(int beginIndex) 返回一个新的字符串,它是此字符串的一个子字符串。
arr_new.add(newStr);
}
}
arr = arr_new;//更新
}
return arr;
}
public static void main(String[] args) {
ArrayList<String> res = new P1迭代法().getPremutation("ABC");
System.out.println(res.size());
System.out.println(res);
}
}
法二:交换回溯
代码实现:
import java.util.ArrayList;
import java.util.Arrays;
/*交换法,但无法按字典序输出*/
public class P2回溯 {
/*多路递归,先纵后横*/
ArrayList<String> res = new ArrayList<String>();
public ArrayList<String> getPremutation(String A){
char[] arr = A.toCharArray();
// toCharArray() 将此字符串转换为一个新的字符数组char[] 。
Arrays.sort(arr);
getPremutationCore(arr,0);
return res;
}
private void getPremutationCore(char[] arr, int k) {
if(k == arr.length) {//排好了一种情况
res.add(new String(arr));
}
//从k位开始的每个字符,都尝试放在新排列的k这个位置上
for (int i = k; i < arr.length; i++) {
swap(arr,k,i);//把后面每个字符换到k位
getPremutationCore(arr,k+1);
swap(arr,k,i);//回溯
}
}
//交换位置
private void swap(char[] arr, int x, int y) {
char t = arr[x];
arr[x] = arr[y];
arr[y] = t;
}
public static void main(String[] args) {
ArrayList<String> res = new P2回溯().getPremutation("ABC");
System.out.println(res.size());
System.out.println(res);
}
}
法三:前缀法
法二通过交换再回溯来实现全排列的方法十分重要,但却无法满足全排列元素按照字典序输出,原因是:当C放置在第一个位置时,剩下的[AB],会先生成CBA,之后才CAB;
以下引用力扣(LeetCode)第60题来说明这个问题:
LeetCode60:第k个排列
给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。
按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:
"123"
"132"
"213"
"231"
"312"
"321"
给定 n 和 k,返回第 k 个排列。
说明:
给定 n 的范围是 [1, 9]。
给定 k 的范围是[1, n!]。
示例 1:
输入: n = 3, k = 3
输出: "213"
示例 2:
输入: n = 4, k = 9
输出: "2314"
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/permutation-sequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
代码实现
import java.util.Scanner;
public class P3前缀法 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
k = sc.nextInt();
int arr[] = new int[n];
for (int i = 0; i < arr.length; i++) {
arr[i] = i+1;
}
String s = "";
for (int i = 0; i < arr.length; i++) {
s += String.valueOf(arr[i]);
}
getPermutation("",s.toCharArray());
}
public static int k ,count = 0;
private static void getPermutation(String s, char[] arr) {
if(s.length() == arr.length) {//前缀的长度=字符集的长度,一个排列完成
System.out.println(s);
count++;
if(count == k) {
System.out.println("所求:"+s);
System.exit(0);//退出
}
}
//每次都从头扫描,只要该字符可用,我们就附加到前缀后面,前缀变长
for (int i = 0; i < arr.length; i++) {
char c = arr[i];
//这个字符可用:在字符集中出现的次数<源字符数组中出现的次数
if(getCount(s,c) < getCount(arr,c)) {
getPermutation(s+c, arr);
}
}
}
private static int getCount(String s, char c) {
char[] a = s.toCharArray();
int ans = getCount(a, c);
return ans;
}
private static int getCount(char[] arr, char c) {
int ans =0;
for (char d : arr) {
if(c == d) ans++;
}
return ans;
}
}
真题练习
以下题目来自蓝桥杯官网
2013JavaB组第9题
题目描述:
标题:带分数
100 可以表示为带分数的形式:100 = 3 + 69258 / 714
还可以表示为:100 = 82 + 3546 / 197
注意特征:带分数中,数字1~9分别出现且只出现一次(不包含0)。
类似这样的带分数,100 有 11 种表示法。
题目要求:
从标准输入读入一个正整数N (N<1000*1000)
程序输出该数字用数码1~9不重复不遗漏地组成带分数表示的全部种数。
注意:不要求输出每个表示,只统计有多少表示法!
例如:
用户输入:
100
程序输出:
11
再例如:
用户输入:
105
程序输出:
6
资源约定:
峰值内存消耗(含虚拟机) < 64M
CPU消耗 < 3000ms
代码实现
import java.util.Scanner;
public class _09带分数{
private static int N,ans=0;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
/*回溯法生成全排列*/
f(arr, 0);
System.out.println(ans);
}
//确认某一个排列的第k位
private static void f(int[] arr, int k) {
if (k == 9)//已生成一种全排列
{
check(arr);
}
//选定第k位,
for (int i = k; i < arr.length; i++) {
//将第i位和第k位交换
int t = arr[i];
arr[i] = arr[k];
arr[k] = t;
// 移交下一层去确认k+1位
f(arr, k + 1);
//回溯(换回来)
t = arr[i];
arr[i] = arr[k];
arr[k] = t;
}
}
/*枚举加号和除号的位置*/
private static void check(int[] arr) {
// ‘+’ 前的字符数可能是1---7
for (int i = 1; i <= 7; i++) {
int num1 = toSum(arr, 0, i);//+前面的一段整数
if (num1 >= N) continue;//如果此时+好的数额已经超过了N,没必要验算了
// ‘/’ 前面的字符数
for (int j = 1; j <= 8 - i; j++) {
int num2 = toSum(arr, i, j);
int num3 = toSum(arr, i + j, 9 - i - j);
if (num2 % num3 == 0 && num1 + num2 / num3 == N) {
ans++;
}
}
}
}
/*把加号前面的数组元素转换成数值*/
private static int toSum(int[] arr, int x, int y) {
int t = 1;
int sum = 0;
for (int i = x + y - 1; i >= x; i--) {//从低位开始往上加;
sum += arr[i] * t;
t *= 10;
}
return sum;
}
}
2014JavaB组第7题
标题:扑克序列
A A 2 2 3 3 4 4, 一共4对扑克牌。请你把它们排成一行。
要求:两个A中间有1张牌,两个2之间有2张牌,
两个3之间有3张牌,两个4之间有4张牌。
请填写出所有符合要求的排列中,字典序最小的那个。
例如:22AA3344 比 A2A23344 字典序小。
当然,它们都不是满足要求的答案。
“A”一定不要用小写字母a,也不要用“1”代替。
本题有重复元素,可利用Set集合去重;
import java.util.HashSet;
import java.util.Set;
public class P7扑克排序 {
public static void main(String[] args) {
char[] a = {'A', 'A', '2', '2', '3', '3', '4', '4'};
//递归法全排列
f(a,0);
//迭代输出Set集合
for (String s : set) {
System.out.println(s);
}
}
/*去除重复元素用Set集合;Set接口无序不可重复*/
public static Set<String> set = new HashSet<String>();
/*【!!!全排列】*/
private static void f(char[] a, int k) {
if(k == a.length) {
String s = new String(a);
if(test(s))
//System.out.println(s);//此输出有重复元素
set.add(s);//去重
}
for(int i = k; i < a.length; i++) {
char t = a[k];
a[k] = a[i];
a[i] = t;
f(a,k+1);
t = a[k];
a[k] = a[i];
a[i] = t;
}
}
/*判断题目条件*/
//int -->lastIndexOf(String str) 返回指定子字符串在此字符串中最右边出现处的索引。
//int -->indexOf(String str) 返回指定子字符串在此字符串中第一次出现处的索引。
private static boolean test(String s) {
if( s.lastIndexOf('A')-s.indexOf('A') == 2 &&
s.lastIndexOf('2')-s.indexOf('2') == 3 &&
s.lastIndexOf('3')-s.indexOf('3') == 4 &&
s.lastIndexOf('4')-s.indexOf('4') == 5)
return true;
return false;
}
}
2015JavaB组第5题
该题是代码填空题
九数组分数:
1,2,3...9 这九个数字组成一个分数,其值恰好为1/3,如何组法?
public class P5九数组分数 {
public static void test(int[] x)
{
int a = x[0]*1000 + x[1]*100 + x[2]*10 + x[3];
int b = x[4]*10000 + x[5]*1000 + x[6]*100 + x[7]*10 + x[8];
if(a*3==b) System.out.println(a + " " + b);
}
public static void f(int[] x, int k)
{
if(k>=x.length){//形成一个排列
test(x);//检查
return;
}
/*递归回溯全排列*/
for(int i=k; i<x.length; i++){
{int t=x[k]; x[k]=x[i]; x[i]=t;}//交换,确定这一位
f(x,k+1);
{int t=x[k]; x[k]=x[i]; x[i]=t;};// 填空题
}
}
public static void main(String[] args)
{
int[] x = {1,2,3,4,5,6,7,8,9};
f(x,0);
}
}
2016JavaB组第3题
凑算式
B DEF
A + --- + ------- = 10
C GHI
这个算式中A~I代表1~9的数字,不同的字母代表不同的数字。
比如:
6+8/3+952/714 就是一种解法,
5+3/1+972/486 是另一种解法。
这个算式一共有多少种解法?
public class P3凑算式 {
public static void main(String[] args) {
int a[] = new int[9];
for (int i = 0; i < a.length; i++) {
a[i] = i+1;
}
f(a,0);
System.out.println(ans);//29
}
private static void f(int[] a, int x) {
if(x == a.length) {
test(a);
}
for (int i = x; i < a.length; i++) {
int t = a[i];
a[i] = a[x];
a[x] = t;
f(a,x+1);
t = a[i];
a[i] = a[x];
a[x] = t;
}
}
public static int ans = 0;
private static void test(int[] a) {
int x = a[3]*100+a[4]*10+a[5];
int y = a[6]*100+a[7]*10+a[8];
//(y*a[1]+x*a[2])/(y*a[2])一定要写成通分形式,否则会有强转时的错误
if((y*a[1]+x*a[2])%(y*a[2])==0 && (y*a[1]+x*a[2])/(y*a[2]) == 10-a[0]) {
ans++;
}
}
}
2016JavaB组第6题
方格填数
如下的10个格子
+--+--+--+
| | | |
+--+--+--+--+
| | | | |
+--+--+--+--+
| | | |
+--+--+--+
填入0~9的数字。要求:连续的两个数字不能相邻。
(左右、上下、对角都算相邻)
一共有多少种可能的填数方案?
我的test方法测试时用的是笨方法,,,优化方法有点难哦。。。。。。
public class P6方格填数 {
public static void main(String[] args) {
int a [] = new int[10];
for (int i = 0; i < a.length; i++) {
a[i] = i;
}
f(a,0);
System.out.println(ans);
}
private static void f(int[] a, int x) {
if(x == a.length) {
test(a);
}
for (int i = x; i < a.length; i++) {
int t = a[i];
a[i] = a[x];
a[x] = t;
f(a,x+1);
t = a[i];
a[i] = a[x];
a[x] = t;
}
}
public static int ans = 0;
private static void test(int[] a) {
//根据表格抽象成代码,,,
if( Math.abs(a[0]-a[1]) != 1 &&
Math.abs(a[0]-a[3]) != 1 &&
Math.abs(a[0]-a[4]) != 1 &&
Math.abs(a[0]-a[5]) != 1 &&
Math.abs(a[1]-a[2]) != 1 &&
Math.abs(a[1]-a[4]) != 1 &&
Math.abs(a[1]-a[5]) != 1 &&
Math.abs(a[1]-a[6]) != 1 &&
Math.abs(a[2]-a[5]) != 1 &&
Math.abs(a[2]-a[6]) != 1 &&
Math.abs(a[3]-a[4]) != 1 &&
Math.abs(a[3]-a[7]) != 1 &&
Math.abs(a[3]-a[8]) != 1 &&
Math.abs(a[4]-a[5]) != 1 &&
Math.abs(a[4]-a[7]) != 1 &&
Math.abs(a[4]-a[8]) != 1 &&
Math.abs(a[4]-a[9]) != 1 &&
Math.abs(a[5]-a[6]) != 1 &&
Math.abs(a[5]-a[8]) != 1 &&
Math.abs(a[5]-a[9]) != 1 &&
Math.abs(a[6]-a[9]) != 1 &&
Math.abs(a[7]-a[8]) != 1 &&
Math.abs(a[8]-a[9]) != 1 ) {
ans++;
}
}
}
2017JavaB组第2题
标题:纸牌三角形
A,2,3,4,5,6,7,8,9 共9张纸牌排成一个正三角形(A按1计算)。要求每个边的和相等。
下图就是一种排法。
A
9 6
4 8
3 7 5 2
这样的排法可能会有很多。
如果考虑旋转、镜像后相同的算同一种,一共有多少种不同的排法呢?
public class P2纸牌三角形 {
/*1-9全排列*/
static int[] a = {1,2,3,4,5,6,7,8,9};
static int ans = 0;
public static void main(String[] args) {
f(0);
System.out.println(ans/6);//旋转镜像算一种;
}
private static void f(int x) {
if(x == 9) {
int x1 = a[0] + a[1] + a[3] + a[5];
int x2 = a[0] + a[2] + a[4] + a[8];
int x3 = a[5] + a[6] + a[7] + a[8];
if(x1 == x2 && x2 == x3)
ans++;
}
for (int j = x; j < 9; j++) {
int t = a[x];
a[x] = a[j];
a[j] = t;
f(x+1);
t = a[x];
a[x] = a[j];
a[j] = t;
}
}
}
2020模拟省赛第二题
问题描述
将LANQIAO中的字母重新排列,可以得到不同的单词,如LANQIAO、AAILNOQ等,注意这7个字母都要被用上,单词不一定有具体的英文意义。
请问,总共能排列如多少个不同的单词。
本题考点就是——带有重复元素的全排列问题
import java.util.HashSet;
import java.util.Set;
public class P2带重复元素全排列{
private static int ans = 0;
public static Set<String> set = new HashSet<String>();
public static void main(String[] args) {
char arr[] = {'L','A','N','Q','I','A','O'};
f(arr,0);
for (String s : set) {
ans++;
System.out.println(s);
}
System.out.println(ans);//2520
}
private static void f(char[] arr, int x) {
if(arr.length == x) {
String s = new String(arr);
set.add(s);
}
for (int i = x; i < arr.length; i++) {
char t = arr[x];
arr[x] = arr[i];
arr[i] = t;
f(arr,x+1);
t = arr[x];
arr[x] = arr[i];
arr[i] = t;
}
}
}