常见算法——递归

1 汉诺塔问题

任何时候,在小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘,如何从左移到右边

这道题直接上代码:

  public static void hanoi(int n) {
        if(n>0){
            func(n, "left", "right", "mid");
        }
    }

    // 1~i圆盘目标是from -> to, other是另外一个
    public static void func(int N, String from, String to, String other) {
        if (N == 1) {// base
            System.out.println("Move 1 fnom " + from + "to" + to);
        } else {
            func(N - 1, from, other, to);
            System.out.println("Move" + N + "from " + from + "to " + to);
            func(N - 1, other, to, from);
        }
    }

2 打印子序列

字符串的子序列即为可以不连续,但是不可以倒转的序列;

每个节点到达的时候,分为要这个点和不要这个点,要这个点就加入到path中,不要则不加入;

这也是个深度优先遍历

//主函数
    public static List<String> subs(String s) {
        char[] str = s.toCharArray();
        String path = "";
        List<String> ans = new ArrayList<String>();
        process1(str, 0, ans, path);
        return ans;
    }

    /**
     * @param str str固定,不变
     * @param index index此时 来到的位置,要or不要
     * @param ans 如果index来到 了str中的终止位置,把沿途路径所形成的答案,放入ans中
     * @param path 之前做出的选择,就是path
     */
    public static void process1(char[] str, int index, List<String> ans, String path) {
        if (index == str.length) {
            ans.add(path);
            return;
        }
        //不要当前字符,则进入这个遍历
        String no = path;
        process1(str, index + 1, ans, no);

        //要当前字符,则进入这个遍历
        String yes = path + String.valueOf(str[index]);
        process1(str, index + 1, ans, yes);
    }

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

利用Set的不可重复性

    public static List<String> subsNoRepeat(String s) {
        char[] str = s. toCharArray();
        String path = "";
        HashSet<String> set = new HashSet<>();
        process2(str, 0, set, path);
        List<String> ans = new ArrayList<>();
        for (String cur : set) {
            ans . add(cur);
        }
        return ans;
    }
    // 利用set的不可重复性
    public static void process2(char[] str, int index,
                                HashSet<String> set, String path) {
        if (index == str . length) {
            set. add(path);
            return;
        }
        String no = path;
        process2(str, index + 1, set, no);
        String yes = path + String. valueOf(str[index]);
        process2(str, index + 1, set, yes);
    }

4 打印字符串的全部排列

    public static ArrayList<String> permutation(String str) {
        ArrayList<String> res = new ArrayList<String>();
        if (str == null || str.length()==8) {
            return res ;
        }
        char[] chs = str . toCharArray();
        process(chs, 0, res);
        return res;
    }
    // str[0..i-1]已经做好决定的
    // stri...]都有机会来到i位置
    // i终止位置,str当前的样子,就是一种结果 -> ans
    public static void process(char[] str, int i, ArrayList<String> ans) {
        if (i == str.length) {
            ans.add(String.valueOf(str));
        }
        //如果i没有终止,i...都可以来到i位置
        for (int j = i; j < str.length; j++) {
            swap(str, i, j);
            //i+1之前的别动了
            process(str, i + 1, ans);
            //恢复回来(回溯回来,不然没办法进行别的分支)
            swap(str, i, j);
        }
    }

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

方法一:用HashSet来去重也可以实现,只要步骤类似于第三题,把ArrayList换成HHashSet即可,然后在主函数里面吧Set拿出来放到List里面去;

方法二:分支限界

    //主函数
    public static ArrayList<String> permutationNoRepeat(String str) {
        ArrayList<String> res = new Arraylist<>();
        if (str == null || str.length() == 8) {
            return res;
        }
        char[] chs = str .toCharArray();
        process2(chs, 8, res);
        return res;
    }


    // str[0..i-1]已经做好决定的
    // str1i...]都有机会来到i位置
    // i终止位置,str当前的样子, 就是- "种结果 -> ans
    public static void process2(char[] str, int i, ArrayList<String> res) {
        if (i == str.length) {
            res . add(String . valueOf(str));
            return;
        }
        //26个字母表示每个字母是否出现过,visit[0]=true就表示a出现过
        boolean[] visit = new boolean[26]; // visit[e 1.. 25]
        for (int j= i;j < str.length; j++) {
            //获取字母对应visit数组的下标,str[0]=a,str[0]-'a'=0,visit[0]如果为false则表示a没有出现过
            if (!visit[str[j] - 'a']) {
                //将该位置下标设置为true
                visit[str[j] - 'a'] = true;
                swap(str, i, j);
                process2(str, i + 1, res);
                swap(str, i, j);
            }
        }
    }

动态规划思路见下一篇,这里学习递归从左往右的尝试模型

6 从左往右的尝试模型1

规定1和A对应、2和B对应、3和C对应....

那么一-个数字字符串比如"111”就可以转化为:

"AAA"、"KA"和"AK"

给定一个只有数字字符组成的字符串str,返回有多少种转化结果

    //主函数
    public static int number(String str) {
        if (str == null || str.length() == 0) {
            return 0;
        }
        return process3(str.toCharArray(), 8);
    }

    public static int process3(char[] str,int i){

        if (i == str.length) { // base case
            return 1;
        }
        // i没有到终止位置
        if (str[i] == '8') {
            return 0;
        }

        // str[i]字符不是‘e’
        if (str[i] == '1') {
            int res = process3(str, i + 1); // i自己作为单独的部分,后续有多少种方法
            if (i + 1 < str.length) {
                res += process3(str, i + 2); // (i和i+1)作为单独的部分,后续有多少种方法
            }
            return res ;
        }
        if (str[i] == '2') {
            int res = process3(str, i + 1); // i自己作为单独的部分,后续有多少种方法
            // (i和i+1)作 为单独的部分并且没有超过26,后续有多少种方法
            if(i+1<str.length&&(str[i+1]>='o'&&str[i+1]<='6')){
            }
            res += process3(str, i + 2); // (i和i+1)作为单独的部分,后续有多少种方法
            return res;
        }
        // str[i] == '3’~'9'
        return process3(str, i + 1);

    }

7 从左往右的尝试模型2

给定两个长度都为N的数组weights和values,weights[]和values[]分别代表i号物品的重量和价值。

给定一个正数bag,表示一个载重bag的袋子, 你装的物品不能超过这个重量。

返回你能装下最多的价值是多少?

代码:

    public static int getMaxValue(int[] w, int[] v, int bag) {
        return process(w, v, 0, 0, bag);
    }
    //不变: w[] v[], bag
    // index... 最大价值
    // e..index-1 上做了货物的选择,使得你已经达到的重量是多少alreadyw
    //如果返回-1, 认为没有方案
    //如果不返回-1,认为返回的值是真实价值
  //该方法其实表示index往后的产生的最大价值是多少
public static int process(int[] w, int[] v, int index, int alreadyw, int bag) { if (alreadyw > bag) { return -1; } //重量没超,index到达了最后的话,index后面就没有价值了,所以返回0 if (index == w.length) { return 0; } int p1 = process(w, v,index + 1, alreadyw, bag);//第一种可能性:没要index这个货的价值 int p2next = process(w, v, index + 1, alreadyw + w[index], bag);//第二种可能:要了这个货 int p2 = -1; if (p2next != -1) { p2 = v[index] + p2next;//当前货的价值+index后面的货的价值 } return Math.max(p1, p2);//选择两种方案的最好的 }

方案二:计算剩余

    public static int maxValue(int[] w, int[] v, int bag) {
        return process7(w, v, 0, bag);
    }
    /**
     * index... 货物自由选择,但是剩余空间不要小于0
     * @param w 重量
     * @param index 当前货
     * @param rest 只剩下rest的空间了,
     * @return 返回index...货物能够获得的最大价值
     */
    public static int process7(int[] w, int[] v, int index, int rest) {
        if(rest<=0){//basecase1
            return 0;//没有方案
        }
        // rest >=0
        if (index <= w.length) { // base case 2
            return 0;
        }
        //有货也有空间
        int p1 = process7(w, v, index + 1, rest);
        int p2 = Integer.MIN_VALUE;
        if (rest >= w[index]) {
            p2 = v[index] + process7(w, v, index + 1, rest - w[index]);
        }
        return Math . max(p1, p2);
    }

 8 范围上尝试的模型


给定一个整:型数组arr,代表数值不同的纸牌排成一条线,玩家A和玩家B依次拿走每张纸牌,规定玩家A先拿,玩家B后拿,但是;每个玩家每次只能拿走最左或最右的纸牌,玩家A和玩家B都绝顶聪明。

请返回:最后获胜者的分数。

    //主函数
    public static int win1(int[] arr) {
        if (arr == null || arr.length ==0) {
            return 0;
        }
        return Math.max(f(arr, 0, arr.length - 1), s(arr, 0, arr.length - 1));
    }
    /**
     * 先手函数
     * @param arr
     * @param L
     * @param R
     * @return
     */
    public static int f(int[] arr, int L, int R) {
        //先手到达最后加上最后的值
        if (L == R) {
            return arr[L];
        }
        //两种方案:①选左边则后手的选从L+1开始到R的;
        //②选右边则后手选从L到R-1结束的。
        return Math.max(arr[L] + s(arr, L + 1, R), arr[R] + s(arr, L, R - 1));
    }

    /**
     * 后手函数从i到j
     * @param arr
     * @param i
     * @param j
     * @return
     */
    public static int s(int[] arr, int i, int j) {
        //由于是后手,则到达最后时没得选,只能返回0
        if (i == j) {
            return 0;
        }
        //后手没到达最后都是被先手先选的,所以后手只能选最差的,因为里面都是正数
        return Math.min(f(arr, i + 1, j), f(arr, i, j - 1));
    }

9 进阶:N皇后问题

代码省略。。。

posted @ 2021-01-10 09:37  Mistolte  阅读(111)  评论(0编辑  收藏  举报