一些经典的递归过程

1.汉诺塔问题

相传在古印度圣庙中,有一种被称为汉诺塔(Hanoi)的游戏。该游戏是在一块铜板装置上,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置64个金盘。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上,

我们先看一个比较好理解的版本

我们假如说有三个金盘自上而下分别为 1  2  3,第一大步我们需要把 1 2 移动到B杆,然后把3移动到C杆,第二大步,我们需要把 3 移动到 C 杆  第三大步,我们再把 1 2移动到C杆这样我们就能把所有的金盘移动到C杆了。下面是最简单的写法

public static void hanoi1(int n){
        leftToRight(n);
    }

    // 1 - n  从左到右
    private static void leftToRight(int n) {
        // base case  如果只有一个,就直接移动
        if (n == 1){
            System.out.println("Move 1 from left to right");
            return;
        }else{
            //不为一个的时候,先把 n - 1 个从左移到中
            leftToMid(n - 1);
            //把剩下那一个直接移动
            System.out.println("move "+n+" from left to right ");
            //把刚刚的n-1从中移动到右
            midToRight(n - 1);
        }
    }


    private static void midToRight(int n) {
        if (n == 1){
            System.out.println("Move 1 from mid to right");
            return;
        }else {
            midToLeft(n - 1);
            System.out.println("Move"+ n +"from mid to right");
            leftToRight(n - 1);
        }
    }

    private static  void midToLeft(int n) {
        if (n == 1){
            System.out.println("mid 1 from mid to left");
            return;
        }else {
            midToRight( n - 1);
            System.out.println("move"+n+"from mid to left");
            leftToRight(n-1);
        }
    }

    // 1 - n 从左到中
    private static void leftToMid(int n) {
        if (n == 1){  //如果是一个直接挪
            System.out.println("Move 1 from left To Mid");
            return;
        }else {
            //如果不是一个,先把n-1从左到右
            leftToRight( n - 1);
            //然后把那一个从左移到中
            System.out.println("Move "+ n + "from left to mid");
            //然后把n-1从右移动到中
            rightToMid(n - 1);
        }
    }

    private static void rightToMid(int n) {
        if (n == 1){
            System.out.println("Move 1 from right to mid");
        }else {
            rightToLeft(n - 1);
            System.out.println("move"+n+"from right to mid");
            leftToRight( n - 1);
        }
    }

    private static void rightToLeft(int n) {
        if (n == 1){
            System.out.println("Move 1 from right to left");
        }else {
            rightToMid(n - 1);
            System.out.println("Move"+n+"from right to left");
            midToLeft( n - 1);
        }
    }

看起来很简单,很好理解对吧,但是我们看这六个过程是有些麻烦是吧,我们仔细看,只有一个参数却要六个过程,我们来进行一个抽象,我们假设没有左中右,1-N都在from上,我们的目标是to,1-N,我们要从from 挪到to中去,我们引进一个中间的other,第一大步就是 1 到 n - 1 从 from 到  other  中,第二步 将 n 从 from 移动到 to  第三步在将 1 到 n-1 从other移动到to中去,这样是不是就全都到to中了。

public static void hanoi2(int n,String from,String to,String other){
        if (n == 1){
            System.out.println("Move 1 form"+from+"to"+to);
        }else {
            hanoi2(n - 1,from,other,to);
            System.out.println("Move" + n +"from"+"to"+to);
            hanoi2(n-1,other,to,from);
        }
    }

经过抽象之后,发现代码是不是少了很多,我们可以通过增加参数来增加递归更多的可能性。

2.打印一个字符串全部的子序列

什么叫子序列?可以不是连续的,例如 1 2 3 4 5 6 ,  1  2 , 1 3 ,2 5 ,等等,只要能找得到,都算子序列

public static List<String> subs(String s){
        char[] chars = s.toCharArray();
        String path = "";
        List<String> ans = new ArrayList<>();
        process(chars,0,ans,path);
        return ans;
    }

    // chars 固定参数
    //来到str[index]字符 index 是位置
    //str[0...index-1]已经走过了,之前的决定,都在path上
    //str[index...] 还能决定,之前已经确定了,而后面还能自由选择的话
    //把所有生成的子序列,放入到ans中
    private static void process(char[] chars, int index, List<String> ans, String path) {
        if (index == chars.length){
            ans.add(path);
            return;
        }else {
            //没要index 位置的字符
            process(chars,index+1,ans,path);
            //要了index 位置字符
            process(chars,index+1,ans,path+chars[index]);
        }
    }

3.打印一个字符串全部的子序列,要求不要出现重复字面值的子序列

有了上面的方法,这题就很好做了,我们把添加的结果放到set中,这样就没有重复值,最后再转成list返回即可。

public static List<String> subs1(String s){
        char[] chars = s.toCharArray();
        String path = "";
        Set<String> ans = new HashSet<>();
        process1(chars,0,ans,path);
        List<String> list = new ArrayList<>();
        for (String set : ans) {
            list.add(set);
        }
        return list;
    }

    // chars 固定参数
    //来到str[index]字符 index 是位置
    //str[0...index-1]已经走过了,之前的决定,都在path上
    //str[index...] 还能决定,之前已经确定了,而后面还能自由选择的话
    //把所有生成的子序列,放入到ans中
    private static void process1(char[] chars, int index, Set<String> ans, String path) {
        if (index == chars.length){
            ans.add(path);
            return;
        }else {
            //没要index 位置的字符
            process1(chars,index+1,ans,path);
            //要了index 位置字符
            process1(chars,index+1,ans,path+chars[index]);
        }
    }

4.打印一个字符串的全部排列

假如说传入一个字符串 abc  ,我们就应该返回[abc, acb, bac, bca, cab, cba]

这里面用到递归中非常常用的一个技巧,叫做恢复现场。

public static List<String> permutation1(String str){
        List<String> result = new ArrayList<>();
        if (str == null || str.length() == 0){
            return result;
        }
        char[] chars = str.toCharArray();
        ArrayList<Character> characters = new ArrayList<>();
        for (int i = 0; i < chars.length; i++) {
            characters.add(chars[i]);
        }
        String path = "";
        fun(characters,path,result);

        return result;
    }

    public static void fun(ArrayList<Character> rest,String path,List<String> ans){
        if (rest.isEmpty()){
            ans.add(path);
        }else {
            int N = rest.size();
            for (int i = 0; i < N; i++) {
                char cur = rest.get(i);
                rest.remove(i);
                fun(rest,path+cur,ans);
                rest.add(i,cur);
            }
        }
    }

这个递归看起来不是很好,为什么呢,因为递归最讲究的是可变参数的设计,只有可变参数怎么评价优劣,我们再以后再讲,下面介绍一个好一点的递归。

public static List<String> permutation2(String str){
        List<String> result = new ArrayList<>();
        if (str == null || str.length() == 0){
            return result;
        }
        char[] chars = str.toCharArray();
        func(chars,0,result);
        return result;
    }

    public static void func(char[] str,int index,List<String> ans){
        if (index == str.length){
            ans.add(String.valueOf(str));
        }else {
            for (int i = index; i < str.length; i++) {
                swap(str,i,index);
                func(str,index+1,ans);
                swap(str,i,index);
            }
        }
    }

    public static void swap(char[] strs,int i,int j){
        char temp = strs[i];
        strs[i] = strs[j];
        strs[j] = temp;
    }

5.打印一个字符串的全部排列,要求不出现重复的排列

public static List<String> permutation3(String str){
        List<String> result = new ArrayList<>();
        if (str == null || str.length() == 0){
            return result;
        }
        char[] chars = str.toCharArray();
        func3(chars,0,result);
        return result;
    }

    public static void func3(char[] str,int index,List<String> ans){
        if (index == str.length){
            ans.add(String.valueOf(str));
        }else {
            boolean[] visited = new boolean[256];
            for (int i = index; i < str.length; i++) {
                if (!visited[str[i]]){
                    visited[str[i]] = true;
                    swap(str,i,index);
                    func(str,index+1,ans);
                    swap(str,i,index);
                }
            }
        }
    }

    public static void swap(char[] strs,int i,int j){
        char temp = strs[i];
        strs[i] = strs[j];
        strs[j] = temp;
    }

6.给你一个栈,请你逆序这个栈,不能申请额外数据结构,只能使用递归函数,如何实现?

public static void reverse(Stack<Integer> stack){
        if (stack.isEmpty()){
            return;
        }
        int i = f(stack);
        reverse(stack);
        stack.push(i);

    }

    //栈底元素移除
    //上面元素盖下来
    //返回移除掉的栈底元素
    public static int f(Stack<Integer> stack){
        int result = stack.pop();
        if (stack.isEmpty()){
            return result;
        }else {
            int last = f(stack);
            stack.push(result);
            return last;
        }
    }

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值