zoukankan      html  css  js  c++  java
  • 动态规划

    简介

    动态规划遵循一套固定的流程:递归的暴力解法( O(2^n) ) -> 带备忘录的递归解法( O(n) ) -> 非递归的动态规划解法( O(n) )。
    
    
    「自顶向下」:
    	是从上向下延伸,
    	都是从一个规模较大的原问题比如说 f(20),向下逐渐分解规模,直到 f(1) 和 f(2) 触底,然后逐层返回答案。
    
    
    「自底向上」:
    	直接从最底下,最简单,问题规模最小的 f(1) 和 f(2) 开始往上推,直到推到我们想要的答案 f(20),
    

    动态规划解法

    1. 将原问题分解为子问题
        f(0),f(1),f(2)...f(n)
    
    2. 确定状态
        f(n)
    
    3. 确定一些初始状态(边界条件)的值
        f(n)=0
    
    4. 确定状态转移方程(当前子问题值与前一个子问题值的关系)
    	f(n) 是一个状态 n,这个状态 n 是由状态 n - 1 和状态 n - 2 相加转移而来,这就是状态转移
    	动态规划问题最困难的就是写出状态转移方程。
    

    适合使用动规求解的问题

    1. 问题具有最优子结构(问题的最优解所包含的子问题的解也是最优的)
    
    2. 求最优解问题
    

    动态规划1:爬楼梯,求共多少爬法(n为正整数)

    /**
     * 方法1:递归(超时):太多冗余
     */
    class Solution1 {
        public static int climbStairs(int n) {
            if(n==1 || n==2)return n;
            return climbStairs(n-1)+climbStairs(n-2);
        }
    }
    
    
    /**
     * 方法2:记忆化递归递归
     *      把每一步的结果存储在 memo 数组之中,每当函数再次被调用,我们就直接从 memo 数组返回结果。
     *
     * 时间复杂度:O(n),树形递归的大小可以达到 n。
     * 空间复杂度:O(n),递归树的深度可以达到 n。
     */
    class Solution2 {
        public static int climbStairs(int n) {
            int memo[] = new int[n + 1];
            return climb_Stairs(0, n, memo);
        }
        public static int climb_Stairs(int i, int n, int memo[]) {
            if (i > n) {
                return 0;
            }
            if (i == n) {
                return 1;
            }
            if (memo[i] > 0) {
                return memo[i];
            }
            memo[i] = climb_Stairs(i + 1, n, memo) + climb_Stairs(i + 2, n, memo);
            return memo[i];
        }
    }
    
    class Solution2 {
        public static int climbStairs(int n) {
            int memo[] = new int[n + 1];
            return climb_Stairs(n, memo);
        }
    
        public static int climb_Stairs(int n, int memo[]) {
            if (n == 1 || n == 2) return memo[n] = n;
            if (memo[n] > 0) {
                return memo[n];
            }
            return memo[n] = climb_Stairs(n - 1, memo) + climb_Stairs(n - 2, memo);
        }
    }
    
    
    /**
     * 方法3:动态规划
     *
     * 思路(状态转移方程):
     *      第n阶爬法的数量=第n-1阶爬法的数量+第n-2阶爬法的数量
     * 
     * 时间复杂度:O(n)
     * 空间复杂度:O(1)
     */
    class Solution3 {
        public static int climbStairs(int n) {
            if(n==1 || n==2)return n;
            int xxx =1;
            int xx =2;
            int x = 0;
            int i=3;
    
            while(i++<=n){
                x=xxx+xx;
                xxx=xx;
                xx=x;
            }
            return x;
        }
    }
    
    
    class aaa {
        public static void main(String[] args) {
            long l = System.currentTimeMillis();
            System.out.println(Solution1.climbStairs(45));
            System.out.println(System.currentTimeMillis()-l);
        }
    }
    

    动态规划2:打家劫舍:求可以盗取的最大数(不能盗取相邻元素)

    /**
     * 思路(状态转移方程):
     *      n个元素最大可以盗取数值=
     *          n-2个元素最大可以盗取数值+第n个元素数值
     *          与
     *          n-1个元素最大可以盗取数值
     *          取最大值
     *
     * 时间复杂度:O(n)
     * 空间复杂度:O(1)
     */
    class Solution {
        public int rob(int[] nums) {
            if (nums.length == 0) return 0;
            if (nums.length == 1) return nums[0];
            if (nums.length == 2) return nums[0] > nums[1] ? nums[0] : nums[1];
    
            int xxx = nums[0];  //前前一个最大
            int xx = nums[0] > nums[1] ? nums[0] : nums[1]; //前一个最大
            int x = nums[2];    //当前数
            int sum = 0;
    
            for (int i = 2; i < nums.length; i++) {
                sum = xx > xxx + x ? xx : xxx + x;
                if (i < nums.length - 1) {
                    xxx = xx;
                    xx = sum;
                    x = nums[i + 1];
                }
            }
            return sum;
        }
    }
    

    动态规划3:给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和

    /**
     * 思路:
     *  前一个子数组和是正数,则说明有增益,与当前元素相加(前面的子序列对自己有力,保留前面,团结互助)
     *  前一个数组是负数,则说明无增益,子数组从当前数组开始(前面的子序列对自己不利,丢弃前面,自立门户)
     *
     * 状态:dp[i]:表示以 nums[i] 结尾的连续子数组的最大和(如果数组长度为n,则有n种情况)
     * 
     * 状态转移方程:dp[i]=max{nums[i],dp[i−1]+nums[i]}
     * 
     * 时间复杂度:O(N)。
     * 空间复杂度:O(1)。
     */
    class Solution {
        public int maxSubArray(int[] nums) {
            int max= nums[0];
            int sum= 0;
            for (int i = 0; i <nums.length ; i++) {
                if(sum<0)sum=nums[i];
                else sum+=nums[i];
                max=Math.max(max,sum);
            }
            return max;
        }
    }
    

    动态规划4:最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1

    /**
     * 思路:动态规划
     * 
     * 动态转移方程:金额n的最少硬币数=(金额n-各种硬币面值)+1 中所需硬币数最小的元素
     * 
     * 时间复杂度:O(Sn)    S是金额,n是硬币面值数
     * 空间复杂度:O(S)
     */
    class Solution {
        public int coinChange(int[] coins, int amount) {
            int[] f = new int[amount + 1];
            f[0] = 0;
    
            for (int i = 1; i <= amount; i++) {
                int cast = Integer.MAX_VALUE;
                for (int j = 0; j < coins.length; j++) {
                    if (i - coins[j] >= 0) {
                        if (f[i - coins[j]] != Integer.MAX_VALUE) cast = Math.min(cast, f[i - coins[j]] + 1);
                    }
                }
                f[i] = cast;
            }
            return f[amount] == Integer.MAX_VALUE ? -1 : f[amount];
        }
    }
    

    动态规划5:三角形最小路径和(给定一个三角形,找出最小路径和。每一步只能移动到下一行中相邻的结点上)

    /**
     * 方法1:记忆化递归法
     * 
     * 思路:
     *      当前元素的最小值=下一层元素两个元素的最小值+当前元素
     */
    class Solution2 {
        private Integer[][] arr = null;
    
        public int minimumTotal(List<List<Integer>> triangle) {
            arr = new Integer[triangle.size()][triangle.size()];
            return helper(0, 0, triangle);
        }
    
        private int helper(int row, int col, List<List<Integer>> triangle) {
            if (row == triangle.size() - 1) return arr[row][col] = triangle.get(row).get(col);
            if (arr[row][col] != null) return arr[row][col];
            int left = helper(row + 1, col, triangle);
            int right = helper(row + 1, col + 1, triangle);
            return arr[row][col] = Math.min(left, right) + triangle.get(row).get(col);
        }
    }
    
    
    /**
     * 思路(状态转移方程):自底向上
     *      当前元素的最小值=下一层元素两个元素的最小值+当前元素
     */
    class Solution1 {
        public int minimumTotal(List<List<Integer>> triangle) {
            int[] ints = new int[triangle.size() + 1];
            for (int i = triangle.size() - 1; i <= 0; i--) {
                for (int j = 0; j < triangle.get(i).size(); j++) {
                    ints[j] = Math.min(ints[j], ints[j + 1]) + triangle.get(i).get(j);
                }
            }
            return ints[0];
        }
    }
    

    动态规划6:给定一个无序的整数数组,找到其中最长上升子序列的长度

    /**
     * 示例:
     *      输入: [10,9,2,5,3,7,101,18]
     *      输出: 4
     *
     * 思路(状态转移方程):
     *      dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度。
     *  
     *      设计动态规划算法,需要一个 dp 数组。
     *      我们可以假设 dp[0...i-1]dp[0...i−1] 都已经被算出来了,然后通过这些结果算出 dp[i]的最大值。
     *
     * 时间复杂度 O(N^2)
     * 空间复杂度 O(N)
     */
    class Solution {
        public int lengthOfLIS(int[] nums) {
            if(nums.length<2)return nums.length;
            int max=0;
            int[] ints = new int[nums.length];
            ints[0]=1;
            for (int i = 1; i < nums.length; i++) {
                ints[i]=1;
                for (int j = 0; j <i ; j++) {
                    if(nums[j]<nums[i]){
                        ints[i]=Math.max(ints[i],ints[j]+1);
                    }
                }
                max=Math.max(max,ints[i]);
            }
            return max;
        }
    }
    

    动态规划7:给定一个包含非负整数的网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小(只能向下或者向右移动一步)

    /**
     * 思路(状态转移方程):
     *      dp[i][j]=min(dp[i-1][j],dp[i][j-1])+dp[i][j]
     * 
     * 时间复杂度 :O(mn)。遍历整个矩阵恰好一次。
     * 空间复杂度 :O(mn)。额外的一个同大小矩阵。
     * 
     * 注:在原数组上存储,这样就不需要额外的存储空间
     */
    class Solution {
        public static int minPathSum(int[][] grid) {
            int row = grid.length;
            int col = grid[0].length;
    
            int[][] ints = new int[row][col];
            ints[0][0] = grid[0][0];
    
            for (int i = 1; i < row; i++) {
                ints[i][0] = ints[i - 1][0] + grid[i][0];
            }
            for (int i = 1; i < col; i++) {
                ints[0][i] = ints[0][i - 1] + grid[0][i];
            }
    
            for (int i = 1; i < row; i++) {
                for (int j = 1; j < col; j++) {
                    ints[i][j] = Math.min(ints[i - 1][j], ints[i][j - 1]) + grid[i][j];
                }
            }
            return ints[row - 1][col - 1];
        }
    }
    
  • 相关阅读:
    ACdream 1224 Robbers (贪心)
    HDU 4320 Arcane Numbers 1 (质因子分解)
    在脚本中重定向输入
    呈现数据
    shell中的for、while、until(二)
    shell中的for、while、until
    C 指针疑虑
    结构化命令
    fdisk -c 0 350 1000 300命令
    PC机上的COM1口和COM2口
  • 原文地址:https://www.cnblogs.com/loveer/p/11786100.html
Copyright © 2011-2022 走看看