7.2逐步生成结果类问题之 非数值型 ---- 第7章 深入递归(深搜,回溯,剪枝等)----求合法括号、子集、全排列

例1 合法括号

方法一:递归


import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

public class Main {
	/*
	 * Set集合去重复。注意:集合是引用类型!!不能用等号赋值!!
	 * */
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		Set<String> ans = getAns(n);
		System.out.println(ans);
	}

	private static Set<String> getAns(int n) {
		Set<String> a = new HashSet<String>();
		
		if(n==1) {//只有一个括号
			a.add("()");
			return a;
		}
		Set<String> newa = getAns(n-1);//获取n-1个括号时的组合数
		for (String str : newa) {//在n-1个括号的基础上加括号。有三种可能
			a.add("()"+str);//左边加括号
			a.add(str+"()");//右边加括号
			a.add("("+str+")");//两边加括号
		}
		return a;
	}
	
	
}


方法二:迭代


import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

public class Main {
	/*
	 * Set集合去重复
	 * */
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		Set<String> ans = getAns(n);
		System.out.println(ans);
	}

	private static Set<String> getAns(int n) {
		Set<String> a = new HashSet<String>();
		a.add("()");
		if(n==1) return a;
		
		for(int i=2;i<=n;i++) {//括号数目累积到n
			Set<String> newa = new HashSet<String>();
			for (String str : a) {//每次添加括号时,更新每个集合中括号的组合
				newa.add("()"+str);
				newa.add(str+"()");
				newa.add("("+str+")");
			}
			a=newa;//更新当前括号集合a
		}	

		return a;
	}

	
}


例题2 子集生成

(非空子集)

类似上题加括号,是加左、加右、加外的问题

子集生成就是针对某些子集,选和不选的问题

两种方法:递归或迭代


import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

public class Main {
	/*
	 * Set集合去重复
	 * */
	public static void main(String[] args) {
		int[] a = {1,2,3};//获取数组a中不重复的子集组合
		int n = a.length;//总的元素个数
		int cur=n-1;//当前加入的元素下标
		Set<Set<Integer>> set = getSubs1(a,n,cur);//方法一:递归
		System.out.println(set);
		System.out.println(getSubs2(a, n));//方法二:迭代
	}

	/*方法一:递归
	 * a:获取数组a中所有的子集,包含空寂
	 * n:总的元素个数
	 * cur:当前加入的元素a[cur]
	 * */
	private static Set<Set<Integer>> getSubs1(int[] a, int n, int cur) {
		Set<Set<Integer>> newSet = new HashSet<>();
		if(cur==0) {//处理第一个元素a[0]
			Set<Integer> s1 = new HashSet<>();//1.空集
			Set<Integer> s2 = new HashSet<>();
			s2.add(a[0]);
			newSet.add(s1);//分别将空集和只含第一个元素的集合加入
			newSet.add(s2);
			return newSet;
		}
		Set<Set<Integer>> oldSet = getSubs1(a, n, cur-1);//加入前n-1个元素之后的集合
		for (Set<Integer> set : oldSet) {
			newSet.add(set);//将当前的集合set加入新集合newSet
			Set<Integer> clone = (Set<Integer>) ((HashSet)set).clone();//克隆当前集合set中的内容
			clone.add(a[cur]);//将当前元素a[cur]加入当前集合set
			newSet.add(clone);//将加入当前元素的集合 加入到 大集合中
		}
		return newSet;
	}
	
	/*
	 * 方法二:迭代
	 * */
	private static Set<Set<Integer>> getSubs2(int[] a, int n) {
		Set<Set<Integer>> ans = new HashSet();
		ans.add(new HashSet<>());//将大集合初始化为只含 一个空集的集合
		
		for(int i=0;i<n;i++) {//从第一个元素开始处理
			
			Set<Set<Integer>> newSet = new HashSet();//存放当前新的大集合
			
			for (Set<Integer> set : ans) {//遍历之前的集合,全部克隆一遍。并将当前元素加入每个小集合
				newSet.add(set);//将当前集合加入
				Set<Integer> clone = (Set<Integer>) ((HashSet)set).clone();//再将当前元素加入集合后,放入大集合newSet
				clone.add(a[i]); 
				newSet.add(clone);//加入新的大集合
			}
			ans = newSet;
		}
		return ans;
	}
	
}


 

例3 子集生成之二进制法(获取子集的第三种方法)

 

 


import java.util.ArrayList;
import java.util.Arrays;

public class Main {
	/*
	 * Set集合去重复
	 * */
	public static void main(String[] args) {
		int[] a = {1,2,4};//获取数组a中不重复的子集组合
		int n = a.length;//总的元素个数
		ArrayList<ArrayList<Integer>> ans= getAns(a,n);
		System.out.println(ans);
	}
	/*
	 * 方法三:子集生成之二进制法
	 *     对于含有n个元素的集合,可生成的子集个数为2^n(包含空集)
	 *     因此只需判断从0~n-1位上是否为1,如果第i位为1 表示a[i]在子集中
	 *     
	 * */

	private static ArrayList<ArrayList<Integer>> getAns(int[] a, int n) {
		
		Arrays.sort(a);//对数组a进行升序排列。由于下面是从高位开始检查,故得到的结果集是逆序
		ArrayList<ArrayList<Integer>> res = new ArrayList();
		for(int i=(int) Math.pow(2, n)-1;i>=0;i--) {//较大的数字~0 (最多有2^n个子集)
			ArrayList<Integer> s = new ArrayList<Integer>();//针对每一个i建立一个集合
			for(int j=n-1;j>=0;j--) {//检查哪一位上的数字为1,从高位开始检查
				if(((i>>j)&1)==1) {
					s.add(a[j]);
				}
			}
			res.add(s);
		}
		return res;
	}
	
}


例4 全排列

对n个数进行全排列,可能的组合数目:n!

方法一:


import java.util.ArrayList;

public class Main {
	/*
	 * Set集合去重复
	 * */
	public static void main(String[] args) {
		String str="ABC";
		ArrayList<String> res = getPermutation1(str);//对串str进行全排列
		System.out.println(res);
	}

	/*方法一:逐步生成大法--迭代法
	 * (1)初始化集合只包含第一个字符
	 * (2)依次向集合中加入剩余的字符
	 *    (2.1)依次遍历集合中已有的排列
	 *        (2.1.1)向当前排列的 前面位置加入当前字符
	 *        (2.1.2)向当前排列的 后面位置加入当前字符
	 *        (2.1.3)向当前排列的 所有可能的中间位置加入当前字符
	 *     
	 * */
	private static ArrayList<String> getPermutation1(String str) {
		int len = str.length();
		ArrayList<String> res = new ArrayList<>();
		res.add(str.charAt(0)+"");//(1)初始化集合只包含第一个字符
		
		//(2)依次向集合中加入剩余的字符
		for(int i=1;i<len;i++) {
			ArrayList<String> newRes = new ArrayList<>();
			char c= str.charAt(i);//获取要插入的字符
			//(2.1)依次遍历原集合中已有的排列
			for (String s: res) {
				//(2.1.1)向当前排列的"前面位置"加入当前字符
				newRes.add(c+s);
				//(2.1.2)向当前排列的 "后面位置"加入当前字符
				newRes.add(s+c);
				//(2.1.3)向当前排列的 所有可能的"中间位置"加入当前字符
				for(int j=1;j<=s.length()-1;j++) {
					String news = s.substring(0, j)+c+s.substring(j);
					newRes.add(news);
				}
			}
			res = newRes;
		}
		return res;
	}
	
}


方法二:交换法。


import java.util.ArrayList;

public class Main {
	
	static ArrayList<String> list = new ArrayList<String>();//存放结果
	public static void main(String[] args) {
		String str="ABC";
		
		getPermutation2(str.toCharArray(),0);//方法二:对串str进行全排列
		System.out.println(list);
	}

	
	/*
	 * 方法二:递归
	 * */
	private static void getPermutation2(char[] str,int k) {
		if(k==str.length) {//已确定完最后一个位置
			list.add(new String(str));
			return;
		} 
		for(int i=k;i<str.length;i++) {//依次将这些位置的元素与下标为k的位置进行交换
			char temp = str[i];//(1)交换
			str[i] = str[k];
			str[k] = temp;
			
			getPermutation2(str, k+1);//(2)再继续确定下一个位置
			
			temp = str[i];//(3)回溯
			str[i] = str[k];
			str[k] = temp;
		}
	}
	
}


方法三:前缀法。解决全排列中的字典序问题

找出“字典序”的第k个排列,我们需按照字典序(即从小到大)的顺序获取排列

需解决:(1)到第k个排列时,不再继续排列

          (2)按照字典序递增

方法:每次从头开始扫描有序的数组[A B C],如果不在就加入


import java.util.ArrayList;
import java.util.Scanner;

public class Main {
	static int k; //字典序第k个排列
	static int cnt=0;//记录当前已完成排列的数目
	static ArrayList<String> list = new ArrayList<String>();//存放结果
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		k=sc.nextInt();//字典序第k个排列
		
		String str="ABC";
		getPermutation3("",str.toCharArray());//方法二:对串str进行全排列
		System.out.println(list);
	}

	
	/*
	 * 方法三:前缀法
	 * 参数1prefix:当前的前缀
	 * 参数2a:全排列的数组
	 * (1)每次将未被加入前缀的字符加入 到当前的前缀prefix中
	 * (2)如果当前前缀的长度 = 数组a的长度。则排列完一次
	 *    且当前完成的排列是第k个,则结束
	 * */
	private static void getPermutation3(String prefix,char[] a) {
		int n = a.length;
		//(1)判断是否完成一次排列
		if(prefix.length()==n) {//已完成的一次排列
			cnt++;
			if(cnt==k) {//找到第k个排列
				System.out.println(prefix);
				System.exit(0);
			}
		}
		//(2)依次向前缀加入字符
		for(int i=0;i<n;i++) {
			char c = a[i];
			//这个字符可用:前缀中出现的次数 < 在数组a中出现的次数
			if(count(prefix.toCharArray(),c)<count(a,c)) {//判断字符c是否已存在前缀中,并且解决了数组a中出现重复字符的问题(即出现次数的问题)
				getPermutation3(prefix+c, a);//接着对加入字符c之后的前缀 进行操作
			}
		}
	}

	//判断字符c在数组ds中出现的次数
	private static int count(char[] ds, char c) {
		int cnt=0;
		for(int i=0;i<ds.length;i++) {
			if(ds[i]==c) cnt++;
		}
		return cnt;
	}
	
}


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值