三.典型问题的递归框架

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/paranior/article/details/79733104

主要内容:

①排列问题

②组合计数问题

③组合枚举问题

④递归设计

递归的难点在于:相似性的设计,如何设计参数才能相似

最常见的问题:

排列问题=排列计数+排列枚举

组合问题=组合计数+组合枚举

重点是:排列枚举问题

关键点:不重不漏

扫描二维码关注公众号,回复: 5656271 查看本文章

递归也是暴力解法的一种,相比循环而言,循环是需要确定循环次数的

1. 搭积木

【问题描述】
小明最近喜欢搭数字积木。一共有10块积木,每个积木上有一个数字,0~9。
搭积木规则:
每个积木放到其它两个积木的上面,并且一定比下面的两个积木数字小。
最后搭成4层的金字塔形,必须用完所有的积木。
下面是两种合格的搭法:
0
1 2
3 4 5
6 7 8 9

0
3 1
7 5 2
9 8 6 4

请你计算这样的搭法一共有多少种?

①4层塔型是固定的,变化的是数字,可以用一维数组来表示每个位置

②筛选条件类似于:a[0]<a[1]&&a[0]<a[2].....

【源代码】

【JAVA:于航】

/*
存储位置备忘
   0
  1 2
 3 4 5
6 7 8 9


*/
public class A
{
    static int N;
   
    static void show(int[] a)
    {
        System.out.println("   " + a[0]);
        System.out.println("  " + a[1] + " " + a[2]);
        System.out.println(" " + a[3] + " " + a[4] + " " + a[5]);
        System.out.println("" + a[6] + " " + a[7] + " " + a[8] + " " + a[9]);
        System.out.println();
    }
   
    static boolean near(int a, int b)
    {
        if(a+1==b || a==b+1) return true;
        return false;
    }
   
    static void test(int[] a)
    {
        if(a[1]<a[0]) return;
        if(a[2]<a[0]) return;
        if(a[3]<a[1]) return;
        if(a[4]<a[1]) return;
        if(a[4]<a[2]) return;
        if(a[5]<a[2]) return;
        if(a[6]<a[3]) return;
        if(a[7]<a[3]) return;
        if(a[7]<a[4]) return;
        if(a[8]<a[4]) return;
        if(a[8]<a[5]) return;
        if(a[9]<a[5]) return;
       
        show(a);
        N++;
    }
   
    // a: 待排元素
    // k: 当前考虑的位置
    static void f(int[] a, int k)
    {
        if(k==a.length-1){
            test(a);
            return;
        }
       
        for(int i=k; i<a.length; i++){
            {int t=a[i]; a[i]=a[k]; a[k]=t;}
            f(a,k+1);
            {int t=a[i]; a[i]=a[k]; a[k]=t;}
        }
    }

   
    public static void main(String[] args)
    {
        int[] a = {0,1,2,3,4,5,6,7,8,9};
       
        f(a,0);
       
        System.out.println("N= " + N);
    }
}

2. 代表团出访//此题原题为代码填空题

【问题描述】
X星球要派出一个5人组成的观察团前往W星。
其中:
A国最多可以派出4人。
B国最多可以派出2人。
C国最多可以派出2人。
D国最多可以派出1人。
E国最多可以派出1人。
F国最多可以派出3人。

那么最终派往W星的观察团会有多少种国别的不同组合呢?

有重复的组合枚举问题:

    ①每次交换后面的元素,怎么能够保证不换来重复的?

    ②与自己不相等才换?

【源代码】

【JAVA:于航】

public class A
{
    //a:可取最大个数的限定
    //k: 当前考虑位置
    //n: 目标名额
    //s: 已经决定的代表团成员
    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++){
            f(a,k+1,n-i,s2);
            s2 += (char)(k+'A');
        }
    }
   
    public static void main(String[] args)
    {
        int[] a = {4,2,2,1,1,3};
       
        f(a,0,5,"");
    }

}

3. 排列枚举

【问题描述】
已知不同字母构成的串,求它的全排列
【源代码】

【JAVA:于航】

import java.util.*;
public class A
{  
    static List f(String s){
        List lst = new Vector();
       //出口条件
        if(s.length()==1){
            lst.add(s);
            return lst;
        }
       
        for(int i=0; i<s.length(); i++){
            char x = s.charAt(i);
            List t = f(s.substring(0,i)+s.substring(i+1));
            for(int k=0; k<t.size(); k++){
                lst.add("" + x + t.get(k));
            }
        }
       
        return lst;
    }


    public static void main(String[] args){
        List lst = f("ABC");
        for(int i=0; i<lst.size(); i++){
            System.out.println(lst.get(i));
        }
    }
}


// ABCDE 所有排列
public class B
{  
    // aa: 待排数据

    // k: 考虑的当前位置(数组下标)

    //递归:通常是从某一点出发考虑问题,再把问题交给相似的问题

此即    典型问题的递归框架

    static void f(char[] aa, int k){
        if(k==aa.length-1){//达到倒数第一个元素,不用交换
            System.out.println(String.valueOf(aa));
            return;//已达到上限
        }
       
        for(int i=k; i<aa.length; i++){//i=k:即自己与自己交换,也是一种,不能忽略
            {char t=aa[k]; aa[k]=aa[i]; aa[i]=t;} // 试探(将每一个元素都与第k个元素进行交换)
            f(aa,k+1);//相似性的设置
            {char t=aa[k]; aa[k]=aa[i]; aa[i]=t;} // 回溯
        }
    }

   
    public static void main(String[] args){
        f("ABC".toCharArray(), 0);
    }

}

4. 组合

【问题描述】
有重复的字母中求取出m个所有组合
例如: “AAABBCCCCCCDD” 中取3个字母的所有组合
【源代码】

【JAVA:于航】

A:组合计数问题

public class A
{
    // m个不同的球中,取n个
    static int f(int m, int n){
        if(n==m) return 1;
        if(n==0) return 1;//两个球中一个都不取,也是一种取法

        return f(m-1,n) + f(m-1,n-1);

        //假设第m个球是特殊的,有两种情况:①第m个球一定取出来了②不取出m球
    }

    }
   
    public static void main(String[] args){
        System.out.println(f(5,3));
        System.out.println(f(5,2));
    }
}

B:组合枚举问题(循环暴力解法,有局限)
// 固定数目的组合问题
// ABCDE 中取3个
public class B
{
    public static void main(String[] args){

        for(char i='A'; i<='E'; i++){

            //只能从上层循环的后面一个开始取,保证不重复

            for(char j= (char)(i+1); j<='E'; j++){
                for(char k= (char)(j+1); k<='E'; k++){
                    System.out.println(""+i+j+k);
                }
            }
        }
    }
}

import java.util.*;

// 递归思路:第1次取什么?

C:组合枚举问题(递归解法)

f(串=“ABCDE”,取数=3){

        创建空列表LL  “A”拿出来

        f("BCDE",2) =>列表L1    “A”组合L1入L2

        “B”拿出来

          f("ACDE",2) =>列表L2    “B”组合L2入LL

        。。。。。。

}

public class C
{
    static List f(String s, int n){
        List lst = new Vector();
        if(n==0){
            lst.add("");
            return lst;
        }
       
        for(int i=0; i<s.length(); i++){
            char x = s.charAt(i);
            List t = f(s.substring(i+1),n-1);
            for(int k=0; k<t.size(); k++){
                lst.add("" + x + t.get(k));
            }
        }
       
        return lst;
    }
   
    public static void main(String[] args){
        List lst = f("ABCDE", 3);
        for(int i=0; i<lst.size(); i++){
            System.out.println(lst.get(i));
        }
    }
}

还有另外一种取法:可以适应更一般的情况(具有重复元素)
// "AABBBC" 取3个, 哪些取法?
public class D
{  
    static void work(int[] x){
        for(int i=0; i<x.length; i++){
            for(int k=0; k<x[i]; k++){
                System.out.print((char)('A'+i));
            }
        }
        System.out.println();  
    }
    // data: 不动, 限制条件
    // x: 取法
    // k: 当前考虑的位置(当前考虑某一点,其余的交给下一层去递归)
    // goal: 距离目标的剩余名额
    static void f(int[] data, int[] x, int k, int goal){
        if(k==x.length){
            if(goal==0) work(x);
            return;
        }
       
        for(int i=0; i<=Math.min(data[k],goal); i++){
            x[k] = i;
            f(data, x, k+1, goal-i);
        }
        x[k] = 0; // 回溯
    }
   
    public static void main(String[] args)
    {  
        int[] data = {2,3,1};  // 每个元素的最大个数
        int[] x = new int[data.length];  // 每个元素取几个
       
        f(data, x, 0, 3);
    }

}

5. 扑克序列

【问题描述】
A A 2 2 3 3 4 4, 一共4对扑克牌。请你把它们排成一行。
要求:两个A中间有1张牌,两个2之间有2张牌,两个3之间有3张牌,两个4之间有4张牌。

请填写出所有符合要求的排列中,字典序最小的那个。
例如:22AA3344 比 A2A23344 字典序小。当然,它们都不是满足要求的答案。
【源代码】

【JAVA:于航】

import java.util.*;
public class TA
{
    static Set set = new TreeSet();
   
    static void test(char[] da)
    {
        String s = new String(da);
       
        if(s.lastIndexOf('A') - s.indexOf('A') != 2) return;
        if(s.lastIndexOf('2') - s.indexOf('2') != 3) return;
        if(s.lastIndexOf('3') - s.indexOf('3') != 4) return;
        if(s.lastIndexOf('4') - s.indexOf('4') != 5) return;
       
        set.add(s);
    }
   
    static void f(char[] da, int k)
    {
        if(k==da.length){//方法一
            test(da);
            return;
        }

       //方法二

        String his=" ";//存放历史记录

        for(int i=k; i<da.length; i++){

            if(his.indexOf(data[i])>=0)  continue;

            his+=da[i];

            {char t=da[k]; da[k]=da[i]; da[i]=t;}
            f(da,k+1);
            {char t=da[k]; da[k]=da[i]; da[i]=t;}
        }
    }
   
    public static void main(String[] args)
    {
        char[] da = "AA223344".toCharArray();
        f(da, 0);
       
        for(Object s: set) System.out.println(s);
    }

}


猜你喜欢

转载自blog.csdn.net/paranior/article/details/79733104