递归和动态规划

1.递归

  • 自下而上的递归
    由子问题或者规模较小问题的答案,构建出规模较大问题的答案
  • 自上而下的递归
    思考如何将问题分解成多个子问题。同时注意子问题是否重叠。

2.分治算法

3.动态规划

中间结果需要缓存起来,以备后续使用。

4.贪心算法

5.编程题

Q1.有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶、3阶。请实现一个方法,计算小孩有多少种上楼的方式。

Ans.思路:

  • 递归的思路非常直接
  • 动态规划的思路需要记住已经计算过的

本质上只用记住最近三个即可:

    public static int countWaysDP(int n) {
        if (n < 0) {
            return 0;
        } else if (n == 1) {
            return 1;
        } else if (n == 2) {
            return 2;
        } else if (n == 3) {
            return 4;
        }

        int fn3 = 1;
        int fn2 = 2;
        int fn1 = 4;

        int i = 4;
        int result = 0;
        while (i <= n) {
            result = fn3 + fn2 + fn1;
            fn3 = fn2;
            fn2 = fn1;
            fn1 = result;
            i++;
        }
        return result;
    }

Q2. 有个机器人坐在X*Y网格的左上角,只能向右、向下移动。机器人从(0,0)到(X,Y)有多少种走法。
进阶:假设有些点为“禁区”,机器人不能踏足。找出一条路径,让机器人从左上角移动到右下角。

Ans.思路:

  • 从数学角度而言,其实非常简单。机器人总共向右走X步,向下走Y步。然后中间向下和向右可以随意穿插。所以总的可能数目为C(X+Y, X)。
  • 可以用算法实现,一种另外的角度。F(a,b) = F(a-1,b) + F(a, b-1),注意边界的时候需要首先初始化为1.(使用动态规划实现的时候)

Q3.在数组A[0 ... n-1]中,有所谓的魔术索引,满足条件A[i] = i。给定一个有序整数数组,元素值各不相同,编写一个方法,在数组A中找出一个魔术索引,若存在的话。
进阶:如果数组元素有重复值,又该如何处理?

Ans.思路:

  • 本质是二分查找
  • 若有重复值,就不能只搜一边,要搜索两边。注意搜索的边界,对于左边取min(midIndex-1, midValue),对于右边取max(midIndex+1, midValue)
    public static int magicSlow(int[] array) {
        for (int i = 0; i < array.length; i++) {
            if (array[i] == i) {
                return i;
            }
        }
        return -1;
    }
    
    public static int magicFast(int[] array, int start, int end) {
        if (end < start || start < 0 || end >= array.length) {
            return -1;
        }
        int midIndex = (start + end) / 2;
        int midValue = array[midIndex];
        if (midValue == midIndex) {
            return midIndex;
        }
        /* Search left */
        int leftIndex = Math.min(midIndex - 1, midValue);
        int left = magicFast(array, start, leftIndex);
        if (left >= 0) {
            return left;
        }
        
        /* Search right */
        int rightIndex = Math.max(midIndex + 1, midValue);
        int right = magicFast(array, rightIndex, end);
        
        return right;
    }
    
    public static int magicFast(int[] array) {
        return magicFast(array, 0, array.length - 1);
    }

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

Ans.思路:

  • 从数学方面看,很容易处理,对于某个元素无非取或不取,所以有2^n个子集。
  • 因此可利用0 ~ (2^n - 1)的二进位转换成集合
    public static ArrayList convertIntToSet(int x, ArrayList set) {
        ArrayList subset = new ArrayList(); 
        int index = 0;
        for (int k = x; k > 0; k >>= 1) {
            if ((k & 1) == 1) {
                subset.add(set.get(index));
            }
            index++;
        }
        return subset;
    }
    
    public static ArrayList> getSubsets2(ArrayList set) {
        ArrayList> allsubsets = new ArrayList>();
        int max = 1 << set.size(); /* Compute 2^n */ 
        for (int k = 0; k < max; k++) {
            ArrayList subset = convertIntToSet(k, set);
            allsubsets.add(subset);
        }
        return allsubsets;
    }

Q5.编写一个方法,确定某字符串的所有排列组合。

Ans.思路:

  • 从简单发现规律,分析n = 1, 2, 3时的情况。可以根据f(2)生成f(3),只要在f(2)的每种情况里面塞a3即可。
  • 直接用递归实现
    public static ArrayList getPerms(String str) {
        if (str == null) {
            return null;
        }
        ArrayList permutations = new ArrayList();
        if (str.length() == 0) { // base case
            permutations.add("");
            return permutations;
        }
                
        char first = str.charAt(0); // get the first character
        String remainder = str.substring(1); // remove the first character
        ArrayList words = getPerms(remainder);
        for (String word : words) {
            for (int j = 0; j <= word.length(); j++) {
                String s = insertCharAt(word, first, j);
                permutations.add(s);
            }
        }
        return permutations;
    }
    
    public static String insertCharAt(String word, char c, int i) {
        String start = word.substring(0, i);
        String end = word.substring(i);
        return start + c + end;
    }

Q6.实现一种算法,打印n对括号的全部有效组合。

Ans.思路:

  • 递归解决思路:在已有的n-1对基础上加入1对,构成n对的解,但是会有重复,需要去除重复,(可以使用HashSet来去重)。有重复解,那么必然会拖累效率
  • 按照内在的规律来进行:每次调用,都可以选择左括号或者右括号,但是要满足如下条件:
    1)左括号没有用完,就可以插入左括号
    2)只要左括号比右括号多,就可以插入右括号

使用HashSet去重:

    public static String insertInside(String str, int leftIndex) {
        String left = str.substring(0, leftIndex + 1);
        String right = str.substring(leftIndex + 1, str.length());
        return left + "()" + right;
    }
    
    public static Set generateParens(int remaining) {
        Set set = new HashSet();
        if (remaining == 0) {
            set.add("");
        } else {
            Set prev = generateParens(remaining - 1); 
            for (String str : prev) {
                for (int i = 0; i < str.length(); i++) {
                    if (str.charAt(i) == '(') {
                        String s = insertInside(str, i);
                        /* Add s to set if it is not already in there. Note:    
                         * HashSet automatically checks for duplicates before
                         * adding, so an explicit check is not necessary. */
                        set.add(s);         
                    }
                }
                set.add("()" + str);
            }
        }
        return set;
    }

根据括号排列的内在规律:

    public static void addParen(ArrayList list, int leftRem, int rightRem, char[] str, int count) {
        if (leftRem < 0 || rightRem < leftRem) return; // invalid state
        
        if (leftRem == 0 && rightRem == 0) { /* all out of left and right parentheses */
            String s = String.copyValueOf(str);
            list.add(s);
        } else {
            if (leftRem > 0) { // try a left paren, if there are some available
                str[count] = '(';
                addParen(list, leftRem - 1, rightRem, str, count + 1);
            }
            if (rightRem > leftRem) { // try a right paren, if there�s a matching left
                str[count] = ')';
                addParen(list, leftRem, rightRem - 1, str, count + 1);
            }
        }
    }
    
    public static ArrayList generateParens(int count) {
        char[] str = new char[count*2];
        ArrayList list = new ArrayList();
        addParen(list, count, count, str, 0);
        return list;
    }

Q7.编写函数,实现许多图片编辑软件都支持的“填充颜色”功能。给定一个屏幕(以二维数组表示,元素为颜色值)、一个点和一个新的颜色值,将新颜色填入这个点的周围区域,直到原来的颜色值全都改变。

Ans.思路:

  • 这就是回溯法,每一个点都向四个方向延伸
    public enum Color {
        Black, White, Red, Yellow, Green
    }
    
    public static String PrintColor(Color c) {
        switch(c) {
        case Black:
            return "B";
        case White:
            return "W";
        case Red:
            return "R";
        case Yellow:
            return "Y";
        case Green:
            return "G";
        }
        return "X";
    }
    
    public static void PrintScreen(Color[][] screen) {
        for (int i = 0; i < screen.length; i++) {
            for (int j = 0; j < screen[0].length; j++) {
                System.out.print(PrintColor(screen[i][j]));
            }
            System.out.println();
        }
    }
    
    public static int randomInt(int n) {
        return (int) (Math.random() * n);
    }
    
    public static boolean PaintFill(Color[][] screen, int x, int y, Color ocolor, Color ncolor) {
        if (x < 0 || x >= screen[0].length || y < 0 || y >= screen.length) {
            return false;
        }
        if (screen[y][x] == ocolor) {
            screen[y][x] = ncolor;
            PaintFill(screen, x - 1, y, ocolor, ncolor); // left
            PaintFill(screen, x + 1, y, ocolor, ncolor); // right
            PaintFill(screen, x, y - 1, ocolor, ncolor); // top
            PaintFill(screen, x, y + 1, ocolor, ncolor); // bottom
        }
        return true;
    }
    
    public static boolean PaintFill(Color[][] screen, int x, int y, Color ncolor) {
        if (screen[y][x] == ncolor) return false;
        return PaintFill(screen, x, y, screen[y][x], ncolor);
    }
    

Q8.给定数量不定的硬币,币值为25分、10分、5分和1分,编写代码计算n分有几种表示法。

Ans.思路:

  • 从25分开始枚举包括0个一直到最多4个;
    针对上面每一种情况,开始枚举10分硬币;
    依次类推
    public static int makeChange(int amount, int[] denoms, int index) {
        if (index >= denoms.length - 1) return 1; // one denom remaining -> one way to do it
        int denomAmount = denoms[index];
        int ways = 0;
        for (int i = 0; i * denomAmount <= amount; i++) {
            int amountRemaining = amount - i * denomAmount;
            ways += makeChange(amountRemaining, denoms, index + 1); // go to next denom
        }
        return ways;
    }
    
    public static int makeChange1(int n) {
        int[] denoms = {25, 10, 5, 1};
        return makeChange(n, denoms, 0);
    }

针对上面的递归解法,可以使用动态规划解决:

    public static int makeChange2(int n) {
        int[] denoms = {25, 10, 5, 1};
        int[][] map = new int[n + 1][denoms.length];
        return makeChange2(n, denoms, 0, map);
    }
    
    public static int makeChange2(int amount, int[] denoms, int index, int[][] map) {
        if (map[amount][index] > 0) { // retrieve value
            return map[amount][index];
        }
        if (index >= denoms.length - 1) return 1; // one denom remaining -> one way to do it
        int denomAmount = denoms[index];
        int ways = 0;
        for (int i = 0; i * denomAmount <= amount; i++) {
            // go to next denom, assuming i coins of denomAmount
            int amountRemaining = amount - i * denomAmount;
            ways += makeChange2(amountRemaining, denoms, index + 1, map);
        }
        map[amount][index] = ways;
        return ways;
    }   

Q9.设计一种算法,打印八皇后在8x8棋盘上的各种摆法,其中每个皇后都不同行、不同列,也不在对角线上。

Ans.思路:

  • 典型的回溯法
    public static int GRID_SIZE = 8;
    
    /* Check if (row1, column1) is a valid spot for a queen by checking if there
     * is a queen in the same column or diagonal. We don't need to check it for queens
     * in the same row because the calling placeQueen only attempts to place one queen at
     * a time. We know this row is empty. 
     */
    public static boolean checkValid(Integer[] columns, int row1, int column1) {
        for (int row2 = 0; row2 < row1; row2++) {
            int column2 = columns[row2];
            /* Check if (row2, column2) invalidates (row1, column1) as a queen spot. */
            
            /* Check if rows have a queen in the same column */
            if (column1 == column2) { 
                return false;
            }
            
            /* Check diagonals: if the distance between the columns equals the distance
             * between the rows, then they're in the same diagonal.
             */
            int columnDistance = Math.abs(column2 - column1); 
            int rowDistance = row1 - row2; // row1 > row2, so no need to use absolute value
            if (columnDistance == rowDistance) {
                return false;
            }
        }
        return true;
    }
    
    public static void placeQueens(int row, Integer[] columns, ArrayList results) {
        if (row == GRID_SIZE) { // Found valid placement
            results.add(columns.clone()); 
        } else {
            for (int col = 0; col < GRID_SIZE; col++) {         
                if (checkValid(columns, row, col)) {
                    columns[row] = col; // Place queen
                    placeQueens(row + 1, columns, results); 
                }       
            }
        }
    }
    
    public static void clear(Integer[] columns) {
        for (int i = 0; i < GRID_SIZE; i++) {
            columns[i] = -1;
        }
    }

Q10.给你一堆n个箱子,箱子宽wi、高hi、深di。箱子不能翻转,将箱子堆起来时,下面箱子的宽度、高度和深度必须大于上面的。实现一个方法,搭出最高的一堆箱子,堆箱的高度为每个箱子高度的综合。

Ans.思路:

  • 典型的回溯法
  • 可以改造成动态规划,符合最优子问题等性质
public class Box {
    public int width;
    public int height;
    public int depth;
    public Box(int w, int h, int d) {
        width = w;
        height = h;
        depth = d;
    }
    
    public boolean canBeUnder(Box b) {
        if (width > b.width && height > b.height && depth > b.depth) {
            return true;
        }
        return false;
    }
    
    public boolean canBeAbove(Box b) {
        if (b == null) {
            return true;
        }
        if (width < b.width && height < b.height && depth < b.depth) {
            return true;
        }
        return false;       
    }
    
    public String toString() {
        return "Box(" + width + "," + height + "," + depth + ")";
    }
}

递归方法:

    public static int stackHeight(ArrayList boxes) {
        if (boxes == null) {
            return 0;
        }
        int h = 0;
        for (Box b : boxes) {
            h += b.height;
        }
        return h;
    }
    
    public static ArrayList createStackR(Box[] boxes, Box bottom) {
        int max_height = 0;
        ArrayList max_stack = null;
        for (int i = 0; i < boxes.length; i++) {
            if (boxes[i].canBeAbove(bottom)) {
                ArrayList new_stack = createStackR(boxes, boxes[i]);
                int new_height = stackHeight(new_stack);
                if (new_height > max_height) {
                    max_stack = new_stack;
                    max_height = new_height;
                }
            }
        }
        
        if (max_stack == null) {
            max_stack = new ArrayList();
        }
        if (bottom != null) {
            max_stack.add(0, bottom);
        }
        
        return max_stack;
    }

动态规划:

    public static ArrayList createStackDP(Box[] boxes, Box bottom, HashMap> stack_map) {
        if (bottom != null && stack_map.containsKey(bottom)) {
            return (ArrayList) stack_map.get(bottom).clone();
        }
        
        int max_height = 0;
        ArrayList max_stack = null;
        for (int i = 0; i < boxes.length; i++) {
            if (boxes[i].canBeAbove(bottom)) {
                ArrayList new_stack = createStackDP(boxes, boxes[i], stack_map);
                int new_height = stackHeight(new_stack);
                if (new_height > max_height) {
                    max_stack = new_stack;
                    max_height = new_height;
                }
            }
        }       
        
        if (max_stack == null) {
            max_stack = new ArrayList();
        }
        if (bottom != null) {
            max_stack.add(0, bottom);
        }
        stack_map.put(bottom, max_stack);
        
        return max_stack;
    }

Q11.给定一个布尔表达式,由0、 1、 &、 | 和 ^ 等符号组成,以及一个想要的布尔结果result,实现一个函数,算出有几种括号的放法可使该表达式得出result值。

Ans.思路:

  • 一个直接的做法是使用回溯法
  • 优化,针对|,可以根据卡塔兰算出总数,然后减去false false情况
    public static int countR(String exp, boolean result, int start, int end) {
        if (start == end) {
            if (exp.charAt(start) == '1' && result) {
                return 1;
            } else if (exp.charAt(start) == '0' && !result) {
                return 1;
            }
            return 0;
        }
        int c = 0;
        if (result) {
            for (int i = start + 1; i <= end; i += 2) {
                char op = exp.charAt(i);
                if (op == '&') {
                    c += countR(exp, true, start, i - 1) * countR(exp, true, i + 1, end); 
                } else if (op == '|') {
                    c += countR(exp, true, start, i - 1) * countR(exp, false, i + 1, end);
                    c += countR(exp, false, start, i - 1) * countR(exp, true, i + 1, end);
                    c += countR(exp, true, start, i - 1) * countR(exp, true, i + 1, end);
                } else if (op == '^') {
                    c += countR(exp, true, start, i - 1) * countR(exp, false, i + 1, end);
                    c += countR(exp, false, start, i - 1) * countR(exp, true, i + 1, end);
                }
            }
        } else {
            for (int i = start + 1; i <= end; i += 2) {
                char op = exp.charAt(i);
                if (op == '&') {
                    c += countR(exp, false, start, i - 1) * countR(exp, true, i + 1, end);
                    c += countR(exp, true, start, i - 1) * countR(exp, false, i + 1, end); 
                    c += countR(exp, false, start, i - 1) * countR(exp, false, i + 1, end);
                } else if (op == '|') {
                    c += countR(exp, false, start, i - 1) * countR(exp, false, i + 1, end);
                } else if (op == '^') {
                    c += countR(exp, true, start, i - 1) * countR(exp, true, i + 1, end);
                    c += countR(exp, false, start, i - 1) * countR(exp, false, i + 1, end);
                }
            }           
        }
        return c;
    }   

动态规划,查表法:

    public static int countDP(String exp, boolean result, int start, int end, HashMap cache) {
        String key = "" + result + start + end;
        if (cache.containsKey(key)) {
            return cache.get(key);
        }
        if (start == end) {
            if (exp.charAt(start) == '1' && result == true) {
                return 1;
            } else if (exp.charAt(start) == '0' && result == false) {
                return 1;
            }
            return 0;
        }
        int count = 0;
        if (result) {
            for (int i = start + 1; i <= end; i += 2) {
                char op = exp.charAt(i);
                if (op == '&') {
                    count += countDP(exp, true, start, i - 1, cache) * countDP(exp, true, i + 1, end, cache); 
                } else if (op == '|') {
                    count += countDP(exp, true, start, i - 1, cache) * countDP(exp, false, i + 1, end, cache);
                    count += countDP(exp, false, start, i - 1, cache) * countDP(exp, true, i + 1, end, cache);
                    count += countDP(exp, true, start, i - 1, cache) * countDP(exp, true, i + 1, end, cache);
                } else if (op == '^') {
                    count += countDP(exp, true, start, i - 1, cache) * countDP(exp, false, i + 1, end, cache);
                    count += countDP(exp, false, start, i - 1, cache) * countDP(exp, true, i + 1, end, cache);
                }
            }
        } else {
            for (int i = start + 1; i <= end; i += 2) {
                char op = exp.charAt(i);
                if (op == '&') {
                    count += countDP(exp, false, start, i - 1, cache) * countDP(exp, true, i + 1, end, cache);
                    count += countDP(exp, true, start, i - 1, cache) * countDP(exp, false, i + 1, end, cache); 
                    count += countDP(exp, false, start, i - 1, cache) * countDP(exp, false, i + 1, end, cache);
                } else if (op == '|') {
                    count += countDP(exp, false, start, i - 1, cache) * countDP(exp, false, i + 1, end, cache);
                } else if (op == '^') {
                    count += countDP(exp, true, start, i - 1, cache) * countDP(exp, true, i + 1, end, cache);
                    count += countDP(exp, false, start, i - 1, cache) * countDP(exp, false, i + 1, end, cache);
                }
            }           
        }
        cache.put(key, count);
        return count;
    }

优化:

    public static int total(int n) {
        // Function to return (2n) ! / ((n+1)! * n!)
        
        // To keep the numbers small, we divide by i when possible to do evenly. If not,
        // we store up the remainder and divide when possible.
        long num = 1;
        long rem = 1;
        for (int i = 2; i <= n; i++) {
            num *= (n + i);
            rem *= i;
            if (num % rem == 0) { 
                num /= rem;
                rem = 1;
            }
        }       
        return (int)num;
    }
    
    public static int countDPEff(String exp, boolean result, int start, int end, HashMap cache) {
        String key = "" + start + end;
        int count = 0;
        if (!cache.containsKey(key)) {
            if (start == end) {
                if (exp.charAt(start) == '1') {
                    count = 1;
                } else {
                    count = 0;
                }
            }   
            
            for (int i = start + 1; i <= end; i += 2) {
                char op = exp.charAt(i);
                if (op == '&') {
                    count += countDPEff(exp, true, start, i - 1, cache) * countDPEff(exp, true, i + 1, end, cache); 
                } else if (op == '|') {
                    int left_ops = (i - 1 - start) / 2; // parens on left
                    int right_ops = (end - i - 1) / 2;  // parens on right
                    int total_ways = total(left_ops) * total(right_ops);
                    int total_false = countDPEff(exp, false, start, i - 1, cache) * countDPEff(exp, false, i + 1, end, cache);
                    count += total_ways - total_false;
                } else if (op == '^') {
                    count += countDPEff(exp, true, start, i - 1, cache) * countDPEff(exp, false, i + 1, end, cache);
                    count += countDPEff(exp, false, start, i - 1, cache) * countDPEff(exp, true, i + 1, end, cache);
                }
            }
            cache.put(key, count);
        } else {
            count = cache.get(key);
        }
        if (result) {
            return count;
        } else {
            int num_ops = (end - start) / 2;
            return total(num_ops) - count;
        }
    }   

Q12.删除了文档中所有空格、标点,大写变小写。给定一个字典(一组单词),设计一个算法,找出拆分一连串单词的最佳方式。这里“最佳”的定义是,解析后无法辨析的字符序列越少越好。

Ans.思路:

  • 递归处理方式:在字符后插入一个空格 或者 不插入(分割 不分割两种方式),会产生大量重复解
  • 为了消除重复解,可以引入表存储解
  • 优化(快速剪枝):以xt开头的单词不存在,所以可以快速剪枝


对于wordEnd超过长度的,可以直接返回长度,因为其兄弟分支已经检查了该字符是否在dictionary中。

    public static int parseSimple(int wordStart, int wordEnd) {
        if (wordEnd >= sentence.length()) {
            return wordEnd - wordStart;
        }
        
        String word = sentence.substring(wordStart, wordEnd + 1);
        
        /* break current word */
        int bestExact = parseSimple(wordEnd + 1, wordEnd + 1);
        if (!dictionary.contains(word, true)) {
            bestExact += word.length();
        }
        
        /* extend current word */
        int bestExtend = parseSimple(wordStart, wordEnd + 1);
        
        /* find best */
        return Math.min(bestExact, bestExtend);
    }   

加入表存储解:

public class Result {
    public int invalid = Integer.MAX_VALUE;
    public String parsed = "";
    public Result(int inv, String p) {
        invalid = inv;
        parsed = p;
    }
    
    public Result clone() {
        return new Result(this.invalid, this.parsed);
    }
    
    public static Result min(Result r1, Result r2) {
        if (r1 == null) {
            return r2;
        } else if (r2 == null) {
            return r1;
        } 
        
        return r2.invalid < r1.invalid ? r2 : r1;
    }   
}
    /* incomplete code */
    public static Result parse(int wordStart, int wordEnd, HashMap cache) {
        if (wordEnd >= sentence.length()) {
            return new Result(wordEnd - wordStart, sentence.substring(wordStart).toUpperCase());
        }
        if (cache.containsKey(wordStart)) {
            return cache.get(wordStart).clone();
        }
        String currentWord = sentence.substring(wordStart, wordEnd + 1);
        boolean validPartial = dictionary.contains(currentWord, false);
        boolean validExact = validPartial && dictionary.contains(currentWord, true);
        
        /* break current word */
        Result bestExact = parse(wordEnd + 1, wordEnd + 1, cache);
        if (validExact) {
            bestExact.parsed = currentWord + " " + bestExact.parsed;
        } else {
            bestExact.invalid += currentWord.length();
            bestExact.parsed = currentWord.toUpperCase() + " " + bestExact.parsed;
        } 
        
        /* extend current word */
        Result bestExtend = null;
        if (validPartial) {
            bestExtend = parse(wordStart, wordEnd + 1, cache);
        }
        
        /* find best */
        Result best = Result.min(bestExact, bestExtend);
        cache.put(wordStart, best.clone());
        return best;
    }   

使用trie快速剪枝:

    public static int parseOptimized(int wordStart, int wordEnd, Hashtable cache) {
        if (wordEnd >= sentence.length()) {
            return wordEnd - wordStart;
        }
        if (cache.containsKey(wordStart)) {
            return cache.get(wordStart);
        }       
        
        String currentWord = sentence.substring(wordStart, wordEnd + 1);
        boolean validPartial = dictionary.contains(currentWord, false);
        
        /* break current word */
        int bestExact = parseOptimized(wordEnd + 1, wordEnd + 1, cache);
        if (!validPartial || !dictionary.contains(currentWord, true)) {
            bestExact += currentWord.length();
        }
        
        /* extend current word */
        int bestExtend = Integer.MAX_VALUE;
        if (validPartial) {
            bestExtend = parseOptimized(wordStart, wordEnd + 1, cache);
        }
        
        /* find best */
        int min = Math.min(bestExact, bestExtend);
        cache.put(wordStart, min);
        return min;
    }

你可能感兴趣的:(递归和动态规划)