竞赛算法--深入递归(上)(DFS、回溯、剪枝等)

竞赛算法–深入递归(中)(DFS、回溯、剪枝等)

竞赛算法–深入递归(下)(DFS、回溯、剪枝等)

1、双管齐下解递归

1、自上而下的递归(分治(分解、递归、合并))直观

  • //快速排序。

2、自下而上的递归(递推,数学归纳,动态规划

  • 解决简单情况下的问题
  • 推广到稍复杂情况下的问题
  • …………
  • 如果递推次数很明确,用迭代
  • 如果有封闭形式,可以直接求解
1.1 “逐步生成结果”类问题之数值型
例题1.1.1:上楼梯

题目描述:

  • 有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶,2阶,3阶。
  • 请实现一个方法,计算小孩有多少种上楼方式。
  • 为了防止溢出,请将结果 mod 1000000007
  • 给定一个正整数 int n ,请返回一个数,代表上楼的方式数

题目分析+题解代码:

  • 先把n从小往大想,逐步分析
    在这里插入图片描述
public class Main{
    static final int MOD = 1000000007;
    public static void main(String [] args){
        System.out.println(recusion1(7));
        System.out.println(recusion2(7));
        //System.out.println(recusion3(7));
    }
    // n较小时就会超时,效率低 O(3^n)
    // 递归方法    顺着想,倒着写(逆向思维)
    public static long recursion1(int n){
        if(n < 0) return 0;
        if(n==0 || n==1) return 1;
        if(n==2) return 2;
        return recursion1(n-1)%MOD + recursion1(n-2)%MOD + recursion1(n-3)%MOD;
    }
    /**
    * 迭代方法,   O(n)
    * 顺着想,顺着写    */
    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 , x2=2 , x3=4 ;
        for(int i =4;i<=n;i++){
            int temp = x1;
            x1 = x2 % MOD;
            x2 = x3 % MOD;
            X3 = ((x1+x2) % MOD + temp ) % MOD ;
        }
        return x3;
    }
}
例题1.1.2:机器人走方格

题目描述:

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

题目分析+题解代码:

在这里插入图片描述

public class Main{
    public static void main(String [] args){
        System.out.printlm(solve1(2,3));
        System.out.printlm(solve2(2,3));
    }
        // 递归解法
    public static int solve1(int x,int y){
        if(x ==1 || y==1) return 1;
            return solve(x-1,y)+solve(x,y-1);
    }
        // ※ 迭代解法(动态规划)
    public static int solve2(int m,int n){
        int [][] state = new int[m+1][n+1];
        for(int i=1;i<n;i++)
            state[1][i] = 1;
        for(int i=1;i<m;i++)
            state[i][1] = 1;
        for(int i=2;i<=m;i++){
            for(int j =2;j<=n;j++){
                state[i][j] = state[i][j-1]+state[i-1][j];
            }
        }
        return state[m][n];
    }
}
例题1.1.3:硬币表示
1.1 “逐步生成结果”类问题之非数值型

此时就要用容器去装了

  • 生成一点,装一点,所谓迭代就是慢慢改变

例题:

  • 1.2.1 合法括号
  • 1.2.2 非空子集
  • 1.2.3 字符串(集合)全排列
例题1.2.1:合法括号

题目描述:

  • 实现一种算法,打印n 对括号的全部有效组合(即,左右括号正确配对)。
  • 示例 :
  • 输入 : 3
  • 输出 : ( ( ( ) ) ) , ( ( ) ( ) ) , ( ) ( ( ) ) , ( ) ( ) ( ) ,

题目分析+题解代码:
在这里插入图片描述

import java.util.*;
public class Mian{
    public static void main(String [] args){
        //Main obj = new Main();
        Set<String> bracketSort = parenthesis(4);
        System.out.println(bracketSort);
    }
    
    public boolean bracketsSort(int n){
        
    }
     // 逐步生成 之 递归解法
    public static Set<String> parenthesis(int n){
        Set<String> s_n = new HashSet<>();
        if(n==1){
            s_n.add("()");
            return s_n;
        }
        Set<String> s_n_1 = 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 ;
    }
    
    // ※ 迭代方法
    public static Set<String> parenthesis_1(int n){
        Set<String> res = new HashSet<>();//保存上次迭代的状态
        res.add("()");
        if(n==1){
            return res;
        }
        for(int i=2;i<=n;i++){
            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;
    }
}
例题1.2.2:非空子集

题目描述:

  • 编写一个方法,返回某个集合的所有的子集。

题目分析+题解代码:

import java.util.*;
public class Main{
    public static void main(String[]args){
        Main obj = new Main();
        int [] A = {123,456,789};
        System.out.println(obj.getSubsets3Core(A,A.length,A.length-1));
        System.out.println(obj.getSubsets2(A,A.length));
    }
    
    //递归增量构造法
    private Set<Set<Integer>> getSubsets3Core(int[] A,int n , int cur){
            Set<Set<Integer>> newSet = new HashSet<>();
	    if(cur == 0){  // 处理第一个元素
	        Set<Integer> nil = new HashSet<>(); // 空集
	        Set<Integer> first = new HashSet<>(); // 包含第一个元素
	        first.add(A[0]);
	        newSet.add(nil);
	        newSet.add(first);
	        return newSet;
	    }
	    Set<Set<Integer>> oldSet = getSubsets3Core(A,n,cur-1)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;
    }
	// 逐步生成迭代大法
    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 a = (Set)((HashSet) e ).clone();
                a.add(A[i]); // 把当前元素加进去
                res_new.add(a); //把克隆的子集加到大集合中
            }
            res = res_new;
        }
        return res;
    }

}

二进制解法

在这里插入图片描述

public ArrayList<ArrayList<Integer>> getSubsets(int []A,int n){
    Arrays.sort(A); // 按字典序正序排列
    ArrayList<ArrayList<Integer>> res = new ArrayList<>(); // 大集合
    for(int i=(int)Math.pow(2,n)-1;i>0;i--){
        // 对每一个i 建立一个集合
        ArrayList<Integer> ints = new ArrayList<>();
        for(int j=n-1;j>=0;j--){//检查哪个位上的二进制为1
            if(((i >> j) & 1) == 1 )
                ints.add(A[j]);
        }
        res.add(ints);
    }
    return res;
}
例题1.2.3:全排列(上)

题目描述:

  • 将一个字符串的每个字符全排列。
  • 例 输入:ABC
  • 输出 :ABC 、 ACB 、BAC 、 BCA 、 CAB 、CBA.
import java.util.*;
public class Main{
    public static void main(String args[]){
        Main obj = new Main();
        ArrayList<String> res = obj.getPermutation0("ABCD");
        System.out.println(res.size());
        System.out.println(res);
    }
    // 逐步生成法--迭代法
    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(newStr);
                // 加在中间
                for(int j = 1;j<str.length();j++){
                    newStr = str.substring(0,j)+c+str.substring(j);
                    res_new.add(newStr);
                }
            }
            res = res_new; // 更新
        }
        return res;
    }
}
例题1.2.3:全排列(中)

交换法—简洁、方便应对重复如AABC

题目描述:

  • 将一个字符串的每个字符全排列
  • 例:输入 : ABC
  • 输出 :ABC 、 ACB 、BAC 、 BCA 、 CAB 、CBA.
import java.util.*;
public class Main{
    ArrayList<String> res = new ArrayList<>();
    public static void main(String [] args){
        Main obj = new Main();
        ArrayList<String> res = obj.getPermutation("ABCD");
        System.out.println(res.size());
        System.out.println(res);
  
    }
    public ArrayList<String> getPermutation(String A){
        char[] arr = A.toCharArray();
        Arrays.sort(arr); // abcd
        getPermutationCore(arr,0);
        return res;
    }
    public void getPermutationCore(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位
            getPermutationCore(arr,k+1);
            swap(arr,k,i);  // 回溯
        }
    }
    static void swap(char [] arr,int i,int j){
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}
例题1.2.4:全排列(下)

前缀法–复杂-能维持良好的字典序,但在处理重复字符时较为繁琐

题目描述:

  • 将一个字符串的每个字符全排列,按字典序输出
  • 例:输入 : ABC
  • 输出 :ABC 、 ACB 、BAC 、 BCA 、 CAB 、CBA.
public class Main{
    public static void main(String [] args){
        String s = "123";
        permutation("",s.toCharArray());
    }
    final static int k = 3;
    static int count = 0;
    private static void permutation(String prefix , char [] arr){
        if(prefix.length() == arr.length){ // 前缀的长度 == 字符集的长度,一个排列就完成了
            count++;
            if(count == k){
                System.out.println(prefix);
                System.out.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);
            }
        }
    }
    public static int count(char[] arr ,char ch){
        int cnt = 0;
        for(char c : arr){
            if(c == ch)  cnt++;
        }
        return cnt;
    }
}
发布了52 篇原创文章 · 获赞 7 · 访问量 1805

猜你喜欢

转载自blog.csdn.net/weixin_44107920/article/details/104035180