蓝桥杯算法基础(33)逐步生成结果类问题 (递归,dp)形式

”逐步生成结果“类问题之数值型

自下而上的递归(递归,数学归纳,动态规划)
-解决简单情况下的问题
-推广到稍复杂情况下的问题
...
-如果递推次数很明显,用迭代
-如果有封闭形式,可以直接求解

f(n)=f(n-1)+f(n-2)

迭代也叫递归,但不是编程语言的递归


上楼梯

有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶、3阶
请实现一个方法,计算小孩有多少种上楼梯得的方式
为了防止溢出,请将结果Mod 1000000007

给定一个正整数int n,返回一个数,代表上楼的方式数。
保证n小于等于100000

static final int mod = 1000000007;

public static long recursion(int n){

        if(n<0)return 0;//小于0走不了
        if(n==0||n==1)retrun 1;//从0直接一步到第三阶,从1直接一步到三第阶
        if(n==2)return 2;//从0到2有两种方式,0->1->2,0->2,然后从2阶一步到三阶,但是总共有两种方式
        return recursion(n-1)%mod+recursion(n-2)%mod+recursion(n-3)%mod;%mod;

}


//循环迭代,代码长,但性能高
public static int recursion2(int n){
        if(n<0)return 0;
        if(n==0||n==1)return 1;
        if(n==2)return 2;
        if(n==3)return 4;
        int x1=1;
        int x2=2;
        int x3=3;
       for(int i=4;i<=n;i++){
       int x_1=x1;
       x1=x2%mod;
       x2=x3%mod;
       x3=((x1+x2)%mod+x1)%modl//注意此处

       }
       return x3;

}

机器人走方格

有一个X*Y的网络,一个机器人只能走格点,且只能向右或向下走,要从左上角走到右下角
请设计一个算法,计算机器人有多少种走法
给定两个正整数int x,int y,请返回机器人的走法数目,保证x+y小于等于12

与走楼梯类似
【1】【1】【1】
【1】【1+1=2】【2+1=3】
【1】【1+2=3】【3+3=6】

递推公式
DP公式 f(x,y)=f(x,y-1)+f(x-1,y);

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

public static int solve(int x,int y){
  if(x==1||y==1)return 1;

  return solve(x-1,y)+solve(x,y-1);

}


public static int solve1(int m,int n){
        int[][] state=new int[m-1][n-1];
        for(int i=1;i<=n;i++){
         int[1][i]=1;
        }

        for(int i=1;i<=m;i++){
        int[i][1]=1;
        }

        for(int i-2;i<=m;i++){

            for(int j=2;<=n;j++){
                state[i][j]=state[i][j]+state[i-1][j];
            }
        }

        return state[][];

}

边界初始化为1,然后依此递推

题解:硬币问题

假设我们有8种不同面值的硬币,{1,2,5,10,20,50,100,200},用这些硬币组合构成一个给定的数值n
例如n=200,那么一种可能的组合方式为200=3*1+1*2+1*5+2*20+1*50+1*100
问总共有多少种可能的组合方式?(这道题目来自著名编程网站ProjectEuler)类似的题目还有:
    [华为面试题]1分2分5分的硬币三种,组合成1角,共有多少种组合
    1*x+2*y+5*z=10
    [创新工厂笔试题]有1分,2分,5分,10分四种硬币,每种硬币数量无限,给定n分钱,有多少组合可以组成n分钱

    1 5 10 25分 n,可以有多少种组合方法


 1 5 10 25->n

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  ......25
 1 1 1 1 2 2 2 2 2 4  4  4  4  4  6   ......

15->0*10->0*5
        ->1*5
        ->2*5
        ->3*5

  ->1*10->0*5
        ->1*5

f(25)=f(要1个25,剩0,1 5 10)+f(要0个25,剩25,1 5 10)

f(50)=f(要2个25,剩0,1 5 10)+f(要1个25,剩25,1 5 10)+f(要0个25,剩50,1 5 10)

参数
出口

最大面值用多少个


//递归形式
public int countWays(int n){
        if(n<=0)return 0;
        return countWays(n,new int[]{1,5,10,15},3);

}

private int countWaysCore(int n,int[] coins,int cur){


       if(cur==0)return 1;//到最小的硬币就只有一种选法了
       int res=0;
    //不选coins[cur]
    //要一个
    //要两个
        //coin[cur]最大面值
    for(int i=0;i*coins[cur]<=n;i++){
           int shengyu=n-i*coins[cur];
           res+=countWaysCore(shengyu,coins,cur-1);//cur-1下一个最大值

    }

    return res;
}

//若是最小的硬币不是1,则上述代码,似乎没有判断,选完最小的,但是不足以达到n总数,当这种情况下,则应该返回0


//dp形式
int[][] state;
private int countWays1(int n){

    int[] coins={1,5,10,25};

    int[][] dp=new int[4][n+1];//前i种面值,组合出面值j

    for(int i=0;i<4;i++){
        dp[i][0]=1;//存储面值0,只有一种,第一列初始化为1
    }
    for(int j=0;j<n+1;j++){

       dp[0][j]=1;//用1来凑任何面值都只有一种凑法,第一行初始化为1
    }

    for(int i=1;i<4;i++){
        for(int j=0;j<n+1;j++){
            for(int k=0;k<=j/coins[i];++k){
             dp[i][j]+=dp[i-1][j-k*coins[i]];//从0个开始选,数组更新结果的总和

            }
        }
    }


}



为什么写出递归
-因为递归有更强的表达力



递归=递推={递归,迭代}
本质为数学归纳
记忆型递归,迭代都叫做动态规划

“逐步生成结果”类问题之非数值型

题解:合法括号

实现一种算法,打印n对括号的全部有效组合(即左右括号正确配对)

n               组合
1                ()
2      ()(),                  (())
3   ()()(),()()(),(()())  ()(()),(())(),((()))

左,右,包一生三

s(n)={对s(n-1)中每个元素 添左,添右,添外
//逐步生成之递归算法
public Set<String>parenthesis(int n){
        Set<String>s_n=new HashSet();//HashSet可以去重

        Set<String> s_n_i=parenthesis(n-1);
        for(String eOfN_1:s_n_1){
        s_n.add("()"+eOfN_1);//填左
        s_n.add(eOfN_1+"()");//添右
        s_n.add("("+eOfN-1+")");//添外
        }
        return s_n
}//也是从底层开始的



//迭代形式
private See<String>parenthesis(int n){
    Set<String>res=new HashSet();//保存上次迭代的状态
    res.add("()");
    if(n==1){
    return res;//若是n=1直接就退出了
    }


    for(int i=2;i<=n;i++){
    for(String e:res){
        Set<String>res_new=new HashSet<>();

        for(String e:res){
        res_new.add(e+"()");
        res_new.add("()"+e);
        res_new.add("("+e+")");

        }

        res=res_new;//每一层结束后,更新
        }
 return res;   //返回最后一层res
    }

}

题解:非空子集(子集生成)

给定一个int数组A和数组大小int n,请返回A的所有非空子集
保证A的元素个数小于等于20,且元素互异
各子集内部从大到小排序,子集之间字典逆序排序

{A,B,C}-->{A},{B},{C},{A,B},{A,C},{B,C},{A,B,C}
原有集合的子集
C1+C2+C3
 3  3  3

                                        {A}<-{}->{}
                   {A}<-{A}->{A,B}                           {B}<-{}->{}
       {A}<-{A}->{A,C}  {A,B}<-{A,B}->{A,B,C}      {B}<-{B}->{B,C}  {}<-{}->{C}

选与不选


public Set<Set<Integer>>getSubSets3(int[] A,int n){
    return getSubsets3Core(A,n,n-1);
}


private Set<Set<Integer>>getSubSet3Core(int[] A,int n,int cur){
        if(cur==0){//处理第一个元素
        Set<Set<Integer>>nil=new HashSet<>();//空集
        Set<Set<Integer>>first =new HashSet<>();//包含第一个元素的集合
        first.add(A[0]);

        newSet.add(nil);
        newSet.add(first);


        return newSet;
        }

        Set<Set<Integer>> oldSet=getSubset3Core(A,n,cur-1);
        Set<Set<Integer>> newSet=new HashSet<>();//消重

        for(Set<Integer> set:oldSet){
            //对于每个子集,cur这个元素可以加进去,也可以不加进去
            newSet.add(set);//不加进去,保留原样

            Set<Integer>clone =(Set<Integer>)((HashSet)set).clone();//克隆
            clone.add(A[cur]);
            newSet.add(clone);//加进去

        }
        return newSet;
}
//最后会有一个空集,需要处理一下,将第一个元素去掉就可以了
//用迭代器删除
Iterator<Set<Integer>> iterator=set.iterator();
    if(iterator.hasNext()){//判断下一个是否含有元素,下标从-1开始
        iterator.next();//元素到下一个
        ieterator.remove();//删除当前元素
    }




//逐步生成迭代大法
public Set<Set<Integer>> getSubsets2(int[] A,int n){

            Set<Set<Integer>> res=new HashSet<>();
            res.add(new HashSet<>());//初始化为空集//匿名对象,也可以加入集合中,显示空集
            //从第一个元素开始处理
            for(int i=0;i<n;i++){
                Set<Set<Integer>> res_new=new HashSet<>();//新建一个大集合

                res_new addAll(res);//把原来集合中的每个子集都加入到新集合中
                //遍历之前的集合全部克隆一遍
                for(Set e:res){
                Set clone=(Set)((HashSet)e).clone();
                clone.add(A[i]);//把当前元素加进去
                res_new.add(clone);//把克隆的子集加到大集合中
              }//将整体不选的加入大集合,拷贝整体不选的,整体加上选的元素,再将整体选的加入大集合,
              res=res_new;

            }

        return res;
}


子集生成之二进制

{A,B,C}
111
110
101
100
011
010
001
000
去掉空集就是2^3-1
1~2^3-1
S=new Set
number中二进制为1
S.add(对应的元素)

public ArrayList<ArrayList<integer>> getSubsets(int[] A,int n){

    Arrays.Sort(A);//正序排序
    ArrayList<ArrayList<Integer>> res=new ArrayList<>();//大集合

        for(int i=Case11_NExpoment.ex(2,n)-1;i>0;i--){//大数字-1
         ArrayList<Integer>S=new ArrayList<>();//对每个i建立一个集合
            for(int j=n-1;j>0;j--){//检查哪个位上的二进制为1,从高位开始检查,高位对应着数组靠后的元素
                if(((i>>j)&1)==1){
                s.add(A[j]);
                }
            }
            res.add(s);
        }
        return res;

}

题解:全排列I

{A,B,C}

每一个都要选,但是顺序不同
ABC ACB
BAC BCA
CAB CBA

[3]*[2]*[1]=6

N!当N>7的时候就全面超越2^n-1


每个|都代表可以插入的位置
1             |A|
2        |B|A|  |A|B|
3  CBA BCA BAC CAB ACB ABC


//逐步生成打法 迭代法
 public ArrayList<String>getPermutation0(String A){
        int n=A.length();
        ArrayList<String> res=new ArrayList<>();
        res.add(A.charAt(0)+"");//初始化,包含一个字符
        for(int i=1;i<n;i++){//从第二个字符开始
        ArrayList<String>res_new=new ArrayList<>();
        char c=A.charAt(i);//新字符
         for(String str:res){//访问上一趟集合中的每个字符串
              //插入到每个位置,形成一个新串
              String newStr=c+str;//加在前面
              res_new.add(newStr);
              newStr=Str+c;//加在后面
              res_new.add(new);
              //加在中间,中间有很多位置,因此需要一个循环来依此加入
              for(int j=1;j<str.length();j++){//数组是.length,字符串String 和BuilderString BufferString都是.length()
              //将字符串拆开,再插入
              newStr=str.substring(0,j)+c+str.substring(j);//substring本身是一个单词,中间的S不大写
              res_new.add(newStr);

              }

         }
         res=res_new;
        }
        retrun res;


 }

 全排列II

 回溯[A,B,C]

 打乱顺序
 每个元素都尝试着放到头部
                [ABC]
          A[BC]  B[]  C[]
     B[C]  C[B]
     C      B
[A][B][C]  [A][C][B]

多路递归(回溯)
for(i=k...N-1){
    swap(k,i);//交换
    f(arr,k+1);//递归结束
    swap(k,i);//换回来
}

ArrayList<String> res=new ArrayList<>();
public ArrayList<String> getPermutation(String A){
        char[] arr=A.toCharArray();
        Arrays.sort(arr);//abcd
        getPermutationCore(arr,0);
        return res;
}

private void getpermutationCore(char[] arr,int k){
        if(k==arr.length){
            res.ade(new String(arr));
        }
        //从k位开始的每个字符,都尝试放在新排列的k这个位置上

        for(int i=k;i<arr.length;i++){
            swap(arr,k,i);//把后缀每个字符换到k位
            getpermutationCore(arr,k+1);
            swap(arr,k,i);//回溯
        }
}

        //交换位置
        static void swap(char[] arr,int i,int j){

        char temp=char[i];
        char[i]=char[j];
        char[j]=temp;
        }

全排列III

前缀法
A A B C-->先找到A,再找A,再找B,再找C AABC
一个排列结束,变回前缀AAB此时下标从C开始,继续往下找,遍历一遍没找到
则前缀回到了AA,从B开始继续找,找到C,前缀变为AAC,再找到B,AAC,又一个排列结束



final static int k=3;
static int count=0;
public static void permutation(String prefix,char[] arr){
//prefix前缀,相当于一个筐,当筐填满了,就是一个排列完成了,然后退回上一个前缀,继续找,一遍找完位置,若没找到,则在回到上一个前缀。继续下一个找
    if(prefix.length()==arr.length){//前缀的长度==字符集的长度,一个排列就完成了
    count++;
        if(count==k){
           System.out.println("--------:"+prefix);
           System.exit(0);
        }

    }
    //每次都从头扫描,只要该字符可用,我们就附加到前缀后面,前缀变长了

    for(int i=0;i<arr.length;i++){
        char ch=arr[i];
        //这个字符串可用,在pre中出现次数<在字符集中的出现次数
        if(count(prefix,ch)<count(arr,ch)){
        permutation(prefix+ch,arr);
        }
    }
}

    private static int count(char[] arr,char ch){
    int cnt=0;
    for(char c:arr){
    if(c==ch)cnt++;

    }

    return cnt;

    }


交换法,--简介,重复,无法维持字典序
前缀法,--复杂,可维持字典序

 封闭式举例

-汉诺塔移动次数
-斐波那契数列第n项
-上楼梯

运算的次数

f(n)=2f(n-1)+1
f(n-1)=2^(n-1)+1
f(n)=2(2^(n-1))+1

1   f(3)
2   f(4)
3   f(5)
.    .
.    .
n-2 f(n)

f(n)=f(n-1)+f(n-2)+f(n-3)
[f1,f2,f3] * [0 0 1]-->[f2,f3,f4]
             [1 0 1]
             [0 1 1]

1     4
2     5
3     6
..
n-3   n
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值