算法再回顾-动态规划

f(n) = f(n-2) + f(n-1)

上面是一道编程题的原型,菲波拉契数列;往往,我们需要求解函数f(n)的结果。
一般有以下几种解法:
* 递归算法分;即通过递归调用进行计算,但是这种方法计算了过多的重复值,因而效率低下
* 记忆搜索算法:算法思路还是采用递归思想。不同的是,为了解决重复计算的问题,引入了一个记忆数组array[n],用来记录之前已经计算的结果,防止重复计算。
* 动态规划算法:以上的思想其实就是动态规划算法,即当前结果依赖之前的结果。我们这里指的动态规划是指优化后的规划算法。通过分析之前的重复计算原因,我们发现,罪魁祸首在于递归算法。递归的分录调用导致了各干各事,没有共享数据。因而,我们倒换个思路,如果知道最初的起点,直接从起点出发,向后遍历,这样的复杂的其实就是元素的个数。
如上:如果一次求出f(1)f(2)f(3)…,相当于一个1-n的遍历,复杂度为O(n)

常见的动态规划题:

  • 一维动态规划:上梯子(一次1、2步)-菲波拉契数列
  • 二维动态规划:二维图,最短路径,路径个数,成本等

动态规划的几个典型题:

  • 一维问题:上台阶、费布拉奇数列等函数-最大递增子序列长度(dp[i]=max{dp[j]+1(0<=j<i,arr[j]<arr[i])})
  • 二维问题:
    • 求左上到左下的最短路径,考虑成本、障碍我等等
    • 比较字符串是否相似度(可以替换,删除)
    • 字符串相似度可以进一步应用在序列比较,基因序列,语音序列等

参考:直通BAT算法精讲


牛客网-leetcode编程-动态规划17道

package com.code;


import java.util.*;

public class Solution {
    public static void main(String[] args) {
        //int[][] arr = {
        //        {2},
        //        {3, 4},
        //        {6, 5, 7},
        //        {4, 1, 8, 3}
        //};
        int[][] arr = {
                {1},
                {2, 3}
        };
        ArrayList<ArrayList<Integer>> triangle = new ArrayList<>();
        for (int i = 0; i < arr.length; i++) {
            ArrayList<Integer> columnList = new ArrayList<Integer>();
            for (int j = 0; j < arr[i].length; j++) {

                columnList.add(j, arr[i][j]);

            }
            triangle.add(i, columnList);
        }
        int[] num = {1, 9, 8, 3, -1, 0};
        int[][] grid = {{1, 2}, {3, 4}};
        int[][] grid2 = {{0,0,0},{0,1,0}, {0,0,0}};
        System.out.println(new Solution().uniquePaths(2,2));
    }

    /**场景1:求最短路径问题,成本问题
     * unique-paths-二维矩阵求所有的道路nums[i][j]=nums[i-1][j]+nums[i][j-1];
     * @param m
     * @param n
     * @return
     */
    public int uniquePaths(int m, int n) {
        if(m==0||n==0){
            return 0;
        }
        int[][] nums = new int[m][n];
        nums[0][0]=1;
        for(int i=1;i<m;i++){
            nums[i][0]=nums[i-1][0];
        }
        for(int i=1;i<n;i++){
            nums[0][i]=nums[0][i-1];
        }
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                nums[i][j]=nums[i-1][j]+nums[i][j-1];
            }
        }
        return nums[m-1][n-1];
    }

    /**
     * unique-paths-ii-思路同上:但是加了障碍物判断
     * @param obstacleGrid
     * @return
     */
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        if (obstacleGrid == null) {
            return 0;
        }
        obstacleGrid[0][0] = obstacleGrid[0][0] == 0 ? 1 : 0;
        for (int i = 1; i < obstacleGrid.length; i++) {
            if (obstacleGrid[i - 1][0] > 0 && obstacleGrid[i][0] == 0) {
                obstacleGrid[i][0] = 1;
            } else {
                obstacleGrid[i][0] = 0;
            }
        }
        for (int j = 1; j < obstacleGrid[0].length; j++) {
            if (obstacleGrid[0][j - 1] > 0 && obstacleGrid[0][j] == 0) {
                obstacleGrid[0][j] = 1;
            } else {
                obstacleGrid[0][j] = 0;
            }
        }
        for (int i = 1; i < obstacleGrid.length; i++) {
            for (int j = 1; j < obstacleGrid[0].length; j++) {
                if (obstacleGrid[i][j] == 0) {
                    if (obstacleGrid[i][j - 1] > 0) {
                        obstacleGrid[i][j] += obstacleGrid[i][j - 1];
                    }
                    if (obstacleGrid[i - 1][j] > 0) {
                        obstacleGrid[i][j] += obstacleGrid[i - 1][j];
                    }
                } else {
                    obstacleGrid[i][j]=0;
                }
            }
        }
        return obstacleGrid[obstacleGrid.length - 1][obstacleGrid[0].length - 1];
    }

    /**
     * minimum-path-sum-二维矩阵求所有的道路
     * @param grid
     * @return
     */
    public int minPathSum(int[][] grid) {
        if (grid == null) {
            return 0;
        }
        for (int i = 1; i < grid.length; i++) {
            grid[i][0] += grid[i - 1][0];
        }
        for (int j = 1; j < grid[0].length; j++) {
            grid[0][j] += grid[0][j - 1];
        }
        for (int i = 1; i < grid.length; i++) {
            for (int j = 1; j < grid[0].length; j++) {
                grid[i][j] += Math.min(grid[i - 1][j], grid[i][j - 1]);
            }
        }
        return grid[grid.length - 1][grid[0].length - 1];
    }

    /**场景2:求序列选择的所有情况
     * climbing-stairs-一维数组-爬梯子
     * 也可以当做排列组合来做
     * @param n
     * @return
     */
    public int climbStairs(int n) {
        int[] nums = new int[n + 2];
        nums[0] = 0;
        nums[1] = 1;
        int i = 2;
        while (i <= n + 1) {
            nums[i] = nums[i - 2] + nums[i - 1];
            i++;
        }
        return nums[n + 1];
    }

    /** 场景:3:序列相似度问题
     * edit-distance-二维矩阵-比较字符串的距离-一般用于基因序列比较
     * 这里的意思是可以替换,删除,字符
     * 在基因序列对比中,会加入比较权重,删除、替换、插入代价有所不同,比较序列相似度。也可以比较字符串相似度。
     * 各种序列相似度比较问题
     * @param word1
     * @param word2
     * @return
     */
    public int minDistance(String word1, String word2) {
        if (word1 == null && word2 == null) {
            return 0;
        }
        if (word1 == null) {
            return word2.length();
        }
        if (word2 == null) {
            return word1.length();
        }

        int[][] nums = new int[word1.length() + 1][word2.length() + 1];
        for (int i = 0; i <= word1.length(); i++) {
            nums[i][0] = i;
        }
        for (int i = 0; i <= word2.length(); i++) {
            nums[0][i] = i;
        }
        for (int i = 1; i <= word1.length(); i++) {
            for (int j = 1; j <= word2.length(); j++) {
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                    nums[i][j] = nums[i - 1][j - 1];
                } else {
                    nums[i][j] = Math.min(Math.min(nums[i][j - 1], nums[i - 1][j]), nums[i - 1][j - 1]) + 1;
                }
            }
        }
        return nums[word1.length()][word2.length()];
    }

    /**
     * subsets-求字符串的所有子串,深度递归+字符串复制
     * @param S
     * @return
     */
    public ArrayList<ArrayList<Integer>> subsets(int[] S) {
        ArrayList<ArrayList<Integer>> rst = new ArrayList<>();
        if (S == null) {
            return rst;
        }
        if (S.length == 0) {
            rst.add(new ArrayList<Integer>());
            return rst;
        }
        Arrays.sort(S);
        ArrayList<Integer> one = new ArrayList<>();
        find(S, 0, one, rst);

        // 加入比较主要是为了输出顺序符合要求
        rst.sort(new Comparator<ArrayList<Integer>>() {
            @Override
            public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
                if (o1.size() > o2.size()) {
                    return 1;
                } else if (o1.size() < o2.size()) {
                    return -1;
                } else {
                    for (int i = 0; i < o1.size(); i++) {
                        if (o1.get(i) > o2.get(i)) {
                            return 1;
                        } else if (o1.get(i) < o2.get(i)) {
                            return -1;
                        } else {
                            continue;
                        }
                    }
                }
                return 0;
            }
        });
        return rst;
    }


    /**
     * scramble-string-树结构是否对称旋转-这里用了记忆搜索算法,并不是动态规划优化解
     * 同样的题由子树的查找,对称旋转
     * @param s1
     * @param s2
     * @return
     */
    public boolean isScramble(String s1, String s2) {
        if (s1 == null || s2 == null || s1.length() != s2.length()) {
            return false;
        }
        if (s1.equals(s2)) {
            return true;
        }

        //
        int[] letters = new int[26];
        for (int i = 0; i < s1.length(); i++) {
            letters[s1.charAt(i) - 'a']++;
            letters[s2.charAt(i) - 'a']--;
        }
        for (int i = 0; i < letters.length; i++) {
            if (letters[i] != 0) {
                return false;
            }
        }
        // 前面这些都是为了降低复杂度,可以不用
        for (int i = 1; i < s1.length(); i++) {
            if (isScramble(s1.substring(0, i), s2.substring(0, i))
                    && isScramble(s1.substring(i), s2.substring(i))) {
                return true;
            }
            if (isScramble(s1.substring(0, i), s2.substring(s2.length() - i))
                    && isScramble(s1.substring(i), s2.substring(0, s2.length() - 1))) {
                return true;
            }
        }
        return false;
    }

    /**
     * gray-code-所有的排列
     * @param n
     * @return
     */
    public ArrayList<Integer> grayCode(int n) {
        ArrayList<Integer> list = new ArrayList<>();
        int start = 0;
        if (n == 0) {
            list.add(0);
            return list;
        }
        addList(list, start, 0, n, true);
        addList(list, start, 1, n, true);
        return list;
    }

    private void addList(ArrayList<Integer> list, int start, int i, int n, boolean flag) {
        int newStart = (start << 1) + i;
        if (n == 1) {
            list.add(newStart);
            return;
        }
        // 为了符合题目输出顺序
        if ((i == 0 && flag) || (i == 1 && !flag)) {
            addList(list, newStart, 0, n - 1, true);
            addList(list, newStart, 1, n - 1, true);
        } else {
            addList(list, newStart, 1, n - 1, false);
            addList(list, newStart, 0, n - 1, false);
        }
    }

    /**场景4:加密解密,将数字解密为字符,根据制定规则
     * decode-ways-将数字编码为a-z的字符表示,数字可能是一位或者两位
     * 一维计数
     * @param s
     * @return
     */
    public int numDecodings(String s) {
        if (s == null) {
            return 0;
        }
        if (s.length() == 0) {
            return 0;
        }
        int len = s.length();
        int[] nums = new int[len + 1];
        nums[0] = 0;
        for (int i = 1; i <= len; i++) {
            if (i == 1) {
                if (s.charAt(i - 1) == '0') {
                    return 0;
                } else {
                    nums[i] = 1;
                }
                continue;
            }
            char ch1 = s.charAt(i - 2);
            char ch2 = s.charAt(i - 1);
            if ((ch1 == '0' && ch2 == '0') || (ch2 == '0' && ch1 > '2')) {
                return 0;
            }
            if (inArr(ch1, ch2)) {
                nums[i] = nums[i - 2] == 0 ? 1 : nums[i - 2];
            }
            if (ch2 == '0') {
                continue;
            }
            nums[i] += nums[i - 1];
        }
        return nums[len];
    }

    private boolean inArr(char ch1, char ch2) {
        if (ch1 > '0' && ch1 < '2') {
            if (ch2 >= '0' && ch2 <= '9') {
                return true;
            } else {
                return false;
            }
        } else if (ch1 == '2') {
            if (ch2 >= '0' && ch2 <= '6') {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * subsets-ii-同上i:
     * @param num
     * @return
     */
    public ArrayList<ArrayList<Integer>> subsetsWithDup(int[] num) {
        ArrayList<ArrayList<Integer>> rst = new ArrayList<>();
        if (num == null) {
            return rst;
        }
        if (num.length == 0) {
            rst.add(new ArrayList<Integer>());
            return rst;
        }
        Arrays.sort(num);
        ArrayList<Integer> one = new ArrayList<>();
        find(num, 0, one, rst);
        return rst;
    }

    private void find(int[] num, int index, ArrayList<Integer> one, ArrayList<ArrayList<Integer>> rst) {
        rst.add(new ArrayList<Integer>(one));
        for (int i = index; i < num.length; i++) {
            if (i != index && num[i] == num[i - 1]) {
                continue;
            }
            one.add(num[i]);
            find(num, i + 1, one, rst);
            one.remove(one.size() - 1);
        }
    }


    /**
     * interleaving-string-将两个字符串组合成对应的字符串
     * 原理相当于路径排列,只是这里直接进行判断;数组内部使用boolean型
     * 同样在基因问题中,求解基因序列变异的可能性
     * @param s1
     * @param s2
     * @param s3
     * @return
     */
    public boolean isInterleave(String s1, String s2, String s3) {
        if (s1.length() + s2.length() != s3.length()) {
            return false;
        }
        boolean[][] array = new boolean[s1.length() + 1][s2.length() + 1];
        array[0][0] = true;
        for (int i = 1; i <= s1.length(); i++) {
            if (array[i - 1][0] && s1.charAt(i-1) == s3.charAt(i-1)) {
                array[i][0] = true;
            }
        }
        for (int i = 1; i <= s2.length(); i++) {
            if (array[0][i - 1] && s2.charAt(i-1) == s3.charAt(i-1)) {
                array[0][i] = true;
            }
        }
        for (int i = 1; i <= s1.length(); i++) {
            for (int j = 1; j <= s2.length(); j++) {
                if ((array[i][j - 1] && s2.charAt(j - 1) == s3.charAt(i + j - 1)
                        || (array[i - 1][j] && s1.charAt(i - 1) == s3.charAt(i + j - 1)))) {
                    array[i][j] = true;
                }
            }
        }
        return array[s1.length()][s2.length()];
    }

    /**
     * distinct-subsequences-判断是否是一个字符串的子串
     * 判断基因是否由某个基因变异而来
     * @param S
     * @param T
     * @return
     */
    public int numDistinct(String S, String T) {
        if(S==null || T==null){
            return 0;
        }
        int[][] arr = new int[S.length()+1][T.length()+1];
        for(int i=0;i<S.length();i++){
            arr[i][0]=1;
        }

        for(int i=1;i<=S.length();i++){
            for(int j=1;j<=T.length();j++){
                arr[i][j]=arr[i-1][j];
                if(S.charAt(i-1)==T.charAt(j-1)){
                    arr[i][j]+=arr[i-1][j-1];
                }
            }
        }
        return arr[S.length()][T.length()];
    }


    public ArrayList<ArrayList<Integer>> generate(int numRows) {
        ArrayList<ArrayList<Integer>> rs = new ArrayList<>();
        for (int i = 0; i < numRows; i++) {
            rs.add(getRow(i));
        }
        return rs;
    }

    public ArrayList<Integer> getRow(int rowIndex) {
        ArrayList<Integer> list = new ArrayList<>();
        int[] arr = new int[rowIndex + 1];
        arr[0] = 1;
        for (int i = 1; i <= rowIndex; i++) {
            for (int j = i; j >= 1; j--) {
                arr[j] += arr[j - 1];
            }
        }
        for (int num : arr) {
            list.add(num);
        }
        //int k=rowIndex;
        //for(int i=0;i<=rowIndex;i++){
        //    list.add(find(k,i));
        //}
        return list;
    }

    private int find(int k, int i) {
        if (i == 0 || i == k) {
            return 1;
        }
        return find(k - 1, i - 1) + find(k - 1, i);
    }

    /**
     * triangle-金字塔最小-递归算法,也可从下到上动态规划
     * @param triangle
     * @return
     */
    public int minimumTotal(ArrayList<ArrayList<Integer>> triangle) {
        int n = 0;
        int left = find(triangle, n, 0);
        return left;
    }

    private int find(ArrayList<ArrayList<Integer>> triangle, int row, int column) {
        if (row<triangle.size()) {
            if(row==triangle.size()-1){
                return triangle.get(row).get(column);
            }
            int left = find(triangle, row + 1, column + 0);
            int right = find(triangle, row + 1, column + 1);
            int min = left < right ? left : right;
            return triangle.get(row).get(column) + min;
        } else {
            return 0;
        }
    }

    /**
     * palindrome-partitioning-ii-白字符串切割成回文的字符串-再看
     * @param s
     * @return
     */
    public int minCut(String s) {
        if(s==null || s.length()==0) {
            return 0;
        }
        int len = s.length();
        int[] min = new int[len+1];
        boolean[][] matrix = new boolean[len][len];
        for(int i=0;i<len;i++) {
            min[i]=len-i;
        }
        //dp过程
        for (int i=len-1; i>=0; --i){
            for (int j=i; j<len; ++j){
                if ((s.charAt(i) == s.charAt(j) && (j-i<2))
                        || (s.charAt(i) == s.charAt(j) && matrix[i+1][j-1]))
                {
                    matrix[i][j] = true;
                    min[i] = getMinValue(min[i], min[j+1]+1);
                }
            }
        }
        return min[0]-1;
    }

    public int getMinValue(int a, int b){
        return a > b ? b : a;
    }

    /**
     * candy-分糖果
     * 个子高的分的糖果比旁边的高,每个人至少一个糖
     * @param ratings
     * @return
     */
    public int candy(int[] ratings) {
        int len = ratings.length;
        int[] array = new int[len];
        // 1、初始化一个糖果
        for (int i = 0; i < len; i++) {
            array[i]=1;
        }
        // 2、从左到右,保证一个方向上数大的,糖果多
        for (int i = 0; i < len-1; i++) {
            if(ratings[i]<ratings[i+1]){
                array[i+1] = array[i]+1;
            }
        }
        // 3、从右到左,保证一个方向上数大的,糖果多
        for (int i = len-1; i >0; i--) {
            if(ratings[i]<ratings[i-1] && array[i]>=array[i-1]){
                array[i-1] = array[i]+1;
            }
        }
        int sum =0 ;
        for (int i = 0; i < len; i++) {
            sum+=array[i];
        }
        return sum;
    }


    /**
     * word-break-字符串是否可以由字典中的字符串组合而成
     * @param s
     * @param dict
     * @return
     */
    public boolean wordBreak2(String s, Set<String> dict) {
        if (s == null || s == "" || dict == null) {
            return false;
        }

        int len = s.length();
        // 用于记录i之前的字符串是否可分(不包括i)
        boolean[] array = new boolean[len+1];
        // 初始化第一个点
        array[0] = true;
        for (int i = 1; i <= len; i++) {
            for (int j = 0; j < i; j++) {
                if (array[j] && dict.contains(s.substring(j, i))) {
                    array[i] = true;
                    //如果i存在,直接跳出
                    break;
                }
            }
        }
        return array[len];
    }


    /**
     * word-break-ii-字符串可以由字典中的字符串组合,求出所有的组合形式
     * @param s
     * @param dict
     * @param map
     * @return
     */
    public ArrayList<String> wordBreak(String s, Set<String> dict) {
        if (s == null || s == "" || dict == null) {
            return null;
        }
        // 保存已存在子字符串组成,防止重复操作
        Map<String, ArrayList<String>> map = new HashMap<>();

        // 深度优先搜索
        return dfs(s, dict, map);
    }

    private ArrayList<String> dfs(String s, Set<String> dict, Map<String, ArrayList<String>> map) {
        // 判断map中是否存在当前字符串列表
        if (map.containsKey(s)) {
            return map.get(s);
        }
        int len = s.length();
        ArrayList<String> list = new ArrayList<>();
        if (len == 0) {
            // len变为1
            list.add("");
        } else {
            for (int i = len-1; i >=0; i--) {
                String subStr = s.substring(i);
                // 字典中不存在,返回
                if (!dict.contains(subStr)) {
                    continue;
                }
                // 字典中存在
                // 递归划分右边的子串
                List<String> rightList = dfs(s.substring(0,i), dict, map);

                // 如果右部不能组成,则跳过(注意:当时"",size==1)
                if (rightList.size() == 0) {
                    continue;
                }

                // 合并(注释地方需要更具体以,从后向前输出)
                for (String str : rightList) {
                    StringBuffer stringBuffer = new StringBuffer();
                    stringBuffer=new StringBuffer(subStr).append(stringBuffer);
//                    stringBuffer.append(subStr);
                    if (i != 0 && i != len) {
                        // 如果左右有空值,则不需要空格
                        stringBuffer=new StringBuffer(" ").append(stringBuffer);
//                        stringBuffer.append(" ");
                    }
                    stringBuffer=new StringBuffer(str).append(stringBuffer);
//                    stringBuffer.append(str);
                    list.add(stringBuffer.toString());
                }
            }
        }
        map.put(s,list);
        return list;
    }
}
展开阅读全文

没有更多推荐了,返回首页