全排列(Java代码)——蓝桥杯备赛笔记

心是比天高,能力却比纸还薄! 初识蓝桥杯,才知道算法的深奥!加油,奥利给!

算法——全排列

所谓全排列:即把元素按照“顺序”排列出来;
例如: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;
		}
	}
}

微信
第一次写博客,不足之处还请多多指教

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jeni成长小栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值