【算法】蓝桥杯dfs深度优先搜索之排列组合总结

前言

  上一篇文章 → 《【算法】蓝桥杯dfs深度优先搜索之凑算式总结》

  为了重申感谢之意,再次声明下文的大部分灵感均来自于【CSDN】梅森上校《JAVA版本:DFS算法题解两个例子(走迷宫和求排列组合数)》
  强烈推荐大家去上面那篇文章看看,写的很好。
  下面我会列出蓝桥杯第六届B组省赛第7题、第七届第5题、第八届第4题,共3道题。

  因为他们都是:排列组合。

正文

【第一道题】

【算法】蓝桥杯dfs深度优先搜索之排列组合总结_第1张图片
  这道题可以强制转为昨天的“凑算式”类型。
  首先,强调一下题意,总共13种牌A到K,每种可以选0到4张,总共选出13张,两个13如果简单表示的话就是2 13,其中13也可以用大写的字母B表示,隐晦的透露了这道题的内涵。
  如果你还能想起来昨天“凑算式”的思路的话,那么上来第一件事肯定就是设置一个数组了
  下图是我昨天在最后一题做的总结,对于这道题来说,也适合。
【算法】蓝桥杯dfs深度优先搜索之排列组合总结_第2张图片
  第一件事,显然这个数组的长度为13,因为我们要存13种牌,数组中只存0到4之间的数。

public static int[] a = new int[13];

  第二件事,这里不涉及到数字重用与否,略过。
  第三件事,定义dfs方法,还是和昨天一样,就传一个index参数

public static void dfs(int index)

  第四件事,写递归结束条件,这里就是index == 13,越界,代表A到K我们已经取完了,接下来就是要统计一下总数是不是13张。如果是的话,就算一种,count++。

// 递归结束条件
if(index == 13) {
	int sum = 0;
	for(int i : a) {
		sum += i;
	}
	if(sum == 13) {
		count++;
	}
	return; //递归结束一定要有return啊,没有return不叫递归结束
}

  第五件事,还未凑齐,深搜。a[]数组总共13个位置,每个位置是0到4中的一个数。代码如下:

// 搜索
for(int i=0; i<=4; i++) {
	a[index] = i;
	dfs(index+1); 
}

【完整代码】

public class 牌型种数dfs {
	public static int count = 0 ;
	public static int[] a = new int[13];
	public static void dfs(int index) {
		if(index == 13) {
			int sum = 0;
			for(int i : a) {
				sum += i;
			}
			if(sum == 13) {
				count++;
			}
			return;
		}
		// 搜索
		for(int i=0; i<=4; i++) {
			a[index] = i;
			dfs(index+1); 
		}
	}

	public static void main(String[] args) {
		dfs(0);
		System.out.println(count); // 答案是: 3598180
	}

}

  其实我的这种解法,关键就在于对数组的使用是否熟练,用13个位置代表13个种类,每个位置只能填0到4,最后数组凑填满后,统计一下每个位置之和是否是13。
  如果你每天吃饭、睡觉、聊天都是讨论的和数组呀,dfs呀相关的,再加上看我写的文章,照着代码敲敲,那么用不了1天,准能掌握这种套路。

  这篇文章的标题是关于排列组合的,之所以开个新坑,就是想告诉大家,虽然我总结的步骤对大多数dfs类型的题有用,但是不要局限以为只有那样的模式才算是dfs。
  比如同样是这道题,同样是dfs算法,但是代码却不一样。下面的代码参考自【CSDN】h1021456873《蓝桥杯 牌型种数 (暴力||dfs)》

public static int count = 0 ;
public static void dfs(int type, int sum) {
	// 结束条件
	if(type == 13) { // A到K  13类
		if(sum == 13) { // 要凑够13张
			count++;
		}
		return;
	}
	// 搜索
	for(int i=0; i<=4; i++) {
		dfs(type+1, sum+i); // 此解法的关键,就在于sum+i 而不是sum+1
	}
}

public static void main(String[] args) {
	dfs(0,0);
	System.out.println(count);
}

  可以看到这个dfs方法传入了两个参数,上面的代码没有像我那样使用数组,如果看懂我的代码,这个也挺好理解的。
  之所以要说上面的代码是要引出来下面这道题

【第二道题】

【算法】蓝桥杯dfs深度优先搜索之排列组合总结_第3张图片
  这是一道填空题,给出的代码如下,其中的注释是我添加的

public class 抽签dfs {
	
	public static void f(int[] a, int k, int n, String s) {
		// 结束条件
		if (k == a.length) {
			if (n == 0)
				System.out.println(s);
			return;
		}
		// 搜索
		String s2 = s;
		for (int i = 0; i <= a[k]; i++) {
			_________________________// 填空位置
			s2 += (char) (k + 'A');
		}
	}

	public static void main(String[] args) {
		int[] a = { 4, 2, 2, 1, 1, 3 };
		f(a, 0, 5, "");
	}
}

  我还清楚的记得我第一次做这道题,当时我还不知道什么是dfs深度优先搜索,压根没看出来这代码什么意思,只是觉得应该递归。经过上篇文章的磨练,现在可以一眼看出这就是dfs的代码套路,只不过他传的参数有点多,4个。
  这道题13分,这种填空题一定不能莽撞,他给出了程序代码,自己填上答案之后,可以结合题意验证一下,比如这道题他有说明总共会输出101行结果,这就是一个检验条件。
  我第一次做的时候,完全是蒙的答案,如下:

f(a, k++, n, s2);  //错误示例

正确答案

f(a, k + 1, n - i, s2); 

  很显然,我当时没有搞懂dfs的搜索代码,即下列代码

for (int i = 0; i <= a[k]; i++) {
	_________________________// 填空位置
	s2 += (char) (k + 'A');
}

  既然他在main方法中调用了dfs算法,参数n传入的是5,那么就代表观察团的总人数要求是5人,这里的for循环进行搜索,一但选中 i 个人,那么接下来只能选 n - i 个人,所以参数应该是n - i
  还有一点就是对于深搜这种,下一个情况是k+1,而不能用k++,或++k。原因是数组会越界,至于为什么会越界,我自己分析了一下,没搞懂。最后就硬记住了,这就是套路,请按套路出牌。
  说实话,这道题如果不是填空题,而是一道大题,尽管我自认为理解了dfs算法,但还是写不对代码。还是要多理解理解这道题。

【第三道题】

  这篇文章的最后一道题
【算法】蓝桥杯dfs深度优先搜索之排列组合总结_第4张图片
  先说明,这道题到底怎么解,其实我也不知道,在这里写它的原因是看到了下面这篇文章,不过作者说的答案:216,作者明知11112233和33221111是同一种知道去重,却没说出来12233111 和 11133221这样之类的也是同一种,因此对于他的答案我不敢苟同。
【CSDN】sangjinchao《第八届蓝桥杯JAVAB组第四题》
  不过,就11112233全排列,这一单纯的知识点我是很感兴趣的。
  下面我想讨论一下使用dfs算法就给定数字全排列问题,比如上面的数字四个1两个2两个3进行全排列,我使用了标记法,写的代码如下

public class 全排列dfs {

	public static int[] a = new int[] { 1, 1, 1, 1, 2, 2, 3, 3 };
	public static int[] visited = new int[8];
	public static int[] result = new int[8];
	public static void dfs(int index) {
		// 结束条件
		if (index == 8) {
			for (int i : result) {
				System.out.print(i);
			}
			System.out.println();
			return;
		}
		// 搜索
		for(int i=0; i<8; i++) {
			if(visited[i]==0) {
				visited[i] = 1;
				result[index] = a[i];
				dfs(index+1);
				visited[i] = 0;
			}
		}
	}

	public static void main(String[] args) {
		dfs(0);
	}

}

  不过,有些情况11112233和33221111,还有11221133和33112211这类的都算重复的,所以需要去掉。目前我给出一个不太成熟的代码,只能想到这里了,如果有谁有优化的代码,一定要给我打call告诉我

public class 全排列dfs逆置去重 {

	public static int[] a = new int[] { 1, 1, 1, 1, 2, 2, 3, 3 };
	public static int[] visited = new int[8];
	public static int[] result = new int[8];
	public static int[] res = new int[33221112];
	public static int count = 0;
	public static void dfs(int index) {
		// 结束条件
		if (index == 8) {
			String s = "";
			String rev = "";
			StringBuilder sb= new StringBuilder();
			for (int i : result) {
				sb.append(i);
			}
			s = sb.toString();
			rev = sb.reverse().toString(); // 逆置
			if(res[Integer.parseInt(rev)] == 0) {// 去重
				res[Integer.parseInt(s)] = 1;
				System.out.println(s);
				count++;
			}
			return;
		}
		// 搜索
		for(int i=0; i<8; i++) {
			if(visited[i]==0) {
				visited[i] = 1;
				result[index] = a[i];
				dfs(index+1);
				visited[i] = 0;
			}
		}
	}

	public static void main(String[] args) {
		dfs(0);
		System.out.println(count);
	}

}

  这篇文章到这里就该结束了,我的初衷就是想告诉大家,dfs不仅仅是我在上篇文章里面写的那篇,只能计算“凑算式”,dfs身为一种暴力破解方法,有很多种变形,还需要大家多加练习。
  有些人会担心,都这个时候了复习蓝桥杯,迟吗?送你一句话:Latter Better Than Never!
  
  上一篇文章 → 《【算法】蓝桥杯dfs深度优先搜索之凑算式总结》

  下一篇文章预计会在周五更新 → 《【算法】蓝桥杯dfs深度优先搜索之图连通总结》
  


参考文章:

    【CSDN】h1021456873《蓝桥杯 牌型种数 (暴力||dfs)》

    【CSDN】豌豆苞谷《2017 第八届蓝桥杯 魔方状态》

    【CSDN】sangjinchao《第八届蓝桥杯JAVAB组第四题》

你可能感兴趣的:(我信仰自由与共享,算法)