数据结构复习笔记(Data Structures & Algorithms in Java, 4th) - recursive 递归

重覆执行除了可以使用回圈(loop)来达到以外,另一个方法就是递归(recursive),面对递归问题时,一般的思路是:
首先思考一下子问题是什么,有多少子问题需要解决以得到 f(n)。也就是说,在一个 recursive binary tree 问题中,所有任一部份都依赖於二组子问题;而在链表的递归问题中,只需要解决一组子问题。总而言之,若要求得 f(n) 的解,则我们先计算 f(0) 或 f(1),通常也是一般固定的值。我们下面直接用一些简单的例子来解说递归

1. 阶乘

比如说 4! = 4 * 3 * 2 * 1,求 n! = ? 

我们可以规纳一下:

base case: 0! = 1

n! = n * (n-1) * (n-2) *.....*3*2*1

我们总结后 => f(n) = n * f(n-1)

    public static int getFactorial(int n) {
        if (n == 0) return 1;
        return n * getFactorial(n - 1);
    }

2. 斐波那契数列:  1, 1, 2, 3, 5, 8, 13, 21, 34....

我们归纳一下:

Base case: f(0) = 0, f(1) = 1, f(2) = f(1) + f(0)

所以我们可以总结得到 => f(n) = f(n-1) + f(n-2)

public static int getNthFibnacci(int n) {
    if(n == 0) {
        return 0;
    } else if(n == 1) {
        return 1;
    } else {
        return getNthFibnacci(n-1) + getNthFibnacci(n-2);
    }
}
3. 将数组逆向排列

    public static <E> void reverseArray(E[] array, final int left, final int right) {
        if (left < right) {
            // swap objects at index i and index j
            E temp = array[right];
            array[right] = array[left];
            array[left] = temp;

            // recursive
            reverseArray(array, left + 1, right - 1);
        }
    }
Linear Recursion:最简单的递归是线性递归,也就是说在每次执行中,最多只执行一次逓归方法。比如说上面的例1

Binary Recursion:而当每次执行二次递归方法时,比如说上面的例2我们称其为 Binary Recursion。递归的方式可以让我们的代码写起来更简洁,而代价就是运行时多占用内存。在内存有限的情况下,我们就使用 while/ for loop 的方式解决问题。


练习

1. 写一段代码可以将返回集合内的所有子集

首先我们得想清楚我们的时间和空间复雜度。我们可以先这么想,每一个子集可以是存在或是不存在,所以每一个子集有2个选择,第一个有2个,第二个有2个…到最后一个有2个,因此,这一共有 2^n 个子集合。

解法一、递归

a. f(n) 集合数组 S = [0,1,...n], 第一个子集为S[0],则较小的集合数组 f(n-1) = S[1,2...n]。

b. 取得较小集合的所有子集合,还有第一项。遍历所有子集合,全加上第一项。

c. 将新集合加入。

    public static <E> ArrayList<ArrayList<E>> getAllSubSet(ArrayList<E> set, int index) {
        ArrayList<ArrayList<E>> allSubSet;
        if (set.size() == index) {
            allSubSet = new ArrayList<>();
            allSubSet.add(new ArrayList<E>()); // 空集合为任何集合的子集合
        } else {
            allSubSet = getAllSubSet(set, index + 1);
            E item = set.get(index);
            ArrayList<ArrayList<E>> moreSubSet = new ArrayList<>(); // try to avoid concurrentMod exception
            for (ArrayList<E> subset : allSubSet) {
                ArrayList<E> newSubset = new ArrayList<E>();
                newSubset.addAll(subset);
                newSubset.add(item);
                moreSubSet.add(newSubset);
            }
            allSubSet.addAll(moreSubSet);
        }
        return allSubSet;
    }
2. 实现一个方法取得一个字符串的所有排列。

字符串长度为0时,便是空字符串;若为1的话,则只有1种;如果是2的话,比如 "ab",就是 ab或是 ba;如果是 abc,就是 a 插入[bc]中的所有字符串组合,  a插入[cb]中的所有字符串组合…依此类推 ...

    public static ArrayList<String> getAllPermutation(String text) {
        if (text == null) {
            return null;
        }

        ArrayList<String> permutations = new ArrayList<>();
        if (text.isEmpty()) {
            permutations.add("");
            return permutations;
        }

        final char first = text.charAt(0);
        String substring = text.substring(1);
        ArrayList<String> subPermStrings = getAllPermutation(substring);
        for (String perm : subPermStrings) {
            final int size = perm.length();
            for (int i = 0; i <= size; i++) {
                permutations.add(addCharToStringAt(perm, i, first));
            }
        }

        return permutations;
    }

    private static String addCharToStringAt(String perm, int i, char first) {
        String start = perm.substring(0, i);
        String end = perm.substring(i);
        return start + first + end;
    }

3. 实现一个方法返回 n对括号的组合

范例:
输入:3 (e.g., 3 pairs of parentheses)
输出: ()()(), ()(()), (())(), ((()))

我们使用递归的方法,产生所有括号组合,然后打印出合格的。

left:只要我们还有左括号,我们可以不断产生左括号。

right:只要次数不多於左括号,我们便能产生右括号。

    public static void getCharArray(int left, int right, char[] str, int strIndex) {
        if (left < 0 || right < left) {
            return;
        }

        if (left == 0 && right == 0) {
            System.out.println(str);
        } else {
            if (left > 0) {
                str[strIndex] = '(';
                getCharArray(left - 1, right, str, strIndex + 1);
            }

            if (right > left) {
                str[strIndex] = ')';
                getCharArray(left, right - 1, str, strIndex + 1);
            }
        }
    }

4. 小画家里,有个功能,就是将特定色块的颜色变成另外一种颜色。因此,我们用二维数组代表屏幕,开始变色的起始点,新旧颜色。根据已给出的条件,请实现该方法。

    public void paintFill(int[][] screen, int x, int y, int originColor, int newColor) {
        if (screen.length < y || screen[0].length < x || x < 0 || y < 0) {
            return;
        }

        if (screen[y][x] == originColor) {
            screen[y][x] = newColor;
            paintFill(screen, x, y - 1, originColor, newColor); // up
            paintFill(screen, x, y + 1, originColor, newColor); // down
            paintFill(screen, x - 1, y, originColor, newColor); // left
            paintFill(screen, x + 1, y, originColor, newColor); // right

        }
    }

所有代码都在Github上

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值