zoukankan      html  css  js  c++  java
  • leetcode动态规划笔记一---一维DP

    动态规划

    刷题方法

    告别动态规划,连刷 40 道题,我总结了这些套路,看不懂你打我 - 知乎
    北美算法面试的题目分类,按类型和规律刷题
    九章的DP分类
    九章DP分类
    重点250

    题目分类

    一维dp

    矩阵型DP

    House Robber : 一维dp

    方法一:自定向下 + memo

    • 这种方法是从问题整体出发,向子问题方向分解求值,同时将子问题结果缓存下来。
    • 一般来说这种方法在分析出,子问题与父问题关系后,能直接想到。
    • 注:如果去掉memo,那就是递归解问题的方法了,可以用在子问题解不重叠的场景。
    class Solution {
        Map<Integer,Integer> m = new HashMap<>();
        int helper(int[] nums, int right){
            if(m.containsKey(right)){
                return m.get(right);
            }
            
            if(right == 0){
                m.put(right, nums[0]);
                return nums[0];
            }
            else if(right == 1){
                int i = Math.max(nums[0], nums[1]);
                m.put(right, i);
                return i;
            }
            
            int i = Math.max(helper(nums, right - 2) + nums[right], helper(nums, right-1));
            m.put(right, i);
            return i;
        }
        public int rob(int[] nums) {
            if(nums.length == 0) return 0;
            return helper(nums, nums.length - 1);
        }
    }
    

    方法二:自底向上 + memo

    • 由小问题向大问题推导,这个思路就必须有memo缓存了,不过可以考虑进一步优化缓存.
    • 这个效率已经要比方法一快了.
    class Solution {
        public int rob(int[] nums) {
            if(nums.length == 0) return 0;
            
            int[] memo = new int[nums.length];
            for(int i=0; i<nums.length; i++){
                if(i == 0 ) memo[i] = nums[i];
                else if(i == 1){
                    memo[i] = Math.max(nums[0], nums[1]);
                }
                else{
                    memo[i] = Math.max(memo[i-1], memo[i-2] + nums[i]);
                }
            }
            
            return memo[nums.length - 1];
        }
    }
    

    方法三:自底向上 + memo optimised

    class Solution {
        public int rob(int[] nums) {
            if(nums.length == 0) return 0;
            if(nums.length == 1) return nums[0];
            
            int n_1 = nums[0], n_2 = 0;
            for(int i=1; i<nums.length; i++){
                int t = Math.max(n_1, n_2 + nums[i]);
                n_2 = n_1; n_1 = t;
            }
            
            return n_1;
        }
    }
    

    House Robber II : 一维dp

    方法:同上

    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 Math.max(nums[0], nums[1]);
            int x = 0;
            // 1. drop the last num
             int n2 = nums[0], n1 = Math.max(nums[0], nums[1]);
            for(int i = 2; i < nums.length -1; i ++){
                int t = Math.max(n2 + nums[i], n1);
                n2 = n1;
                n1 = t;
            }
            x = n1;
            
            n2 = 0;
            n1 = nums[1];
            for(int i = 2; i < nums.length; i ++){
                int t = Math.max(n2 + nums[i], n1);
                n2 = n1;
                n1 = t;
            }
            
            return Math.max(x, n1);
        }
    }
    

    House Robber III:一维DP

    方法一: 自顶向下 + memo
    这种方式写起来容易出错,效率也比较低

    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode(int x) { val = x; }
     * }
     */
    class Solution {
        Map<TreeNode, Integer> m = new HashMap<TreeNode, Integer>();
        Map<TreeNode, Integer> m2 = new HashMap<TreeNode, Integer>();
        
        int helper(TreeNode t, boolean parentUsed){
            if(t == null) return 0;      
            
            if(parentUsed){
                if(m.containsKey(t)){
                    return m.get(t);
                }            
                int x = helper(t.left, false) + helper(t.right, false);
                m.put(t, x);
                return x;
            }
            else{
                if(m2.containsKey(t)){
                    return m2.get(t);
                }  
                
                int notUse = helper(t.left, false) + helper(t.right, false);
                
                int use = t.val + helper(t.left, true) + helper(t.right, true);
                
                int x = Math.max(use, notUse);
                
                m2.put(t, x);
                return x;
            }
        }
        public int rob(TreeNode root) {
            if(root == null) return 0;
            
            return Math.max(root.val + 
                            helper(root.left, true) + helper(root.right,true), 
                            helper(root.left, false) + helper(root.right, false));
        }
    }
    

    方法二:自顶向下 + memo
    虽然也是自顶向下,下面的方法就简洁很多;特别注意标important那行容易出错

    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode(int x) { val = x; }
     * }
     */
    class Solution {
        
        int[] helper(TreeNode t){
            int[] re = new int[2];
            if(null == t){
                re[0] = 0;
                re[1] = 0;
                return re;
            }
            
            int[] left = helper(t.left);
            int[] right = helper(t.right);
            
            re[0] = t.val + left[1] + right[1];
            re[1] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]); // important
            return re;
        }
        public int rob(TreeNode root) {
            if(root == null) return 0;
            
            int[] re = helper(root);
            
            return Math.max(re[0], re[1]);
        }
    }
    

    Decode Ways : 一维DP,求方法总数

    解法一: 自顶向下,递归 + no memo, time limit exceed
    这道题真有点不太好想

    class Solution {
        int helper(char[] str, int right){
            int sum1 = 0, sum2 = 0;
    
            if(right < 0) return 1;
            if(right == 0){
                return (str[right] != '0') ? 1 : 0;
            }
            
            if(str[right] != '0'){
                sum1 = helper(str, right-1);
            }
            
            if(str[right-1] != '0'){
                Integer x = Integer.valueOf(new String(str, right - 1, 2));
                if(x >= 1 && x <= 26){
                    sum2 = helper(str, right-2);
                }
            }
            
            return sum1 + sum2;
        }
        
        public int numDecodings(String s) {
            return helper(s.toCharArray(), s.length() - 1);
        }
    }
    

    解法二: 自顶向下,递归 + memo

    class Solution {
        Map<Integer, Integer> m = new HashMap<>();
        
        int helper(char[] str, int right){
            int sum1 = 0, sum2 = 0;
            
            if(m.containsKey(right)){
                return m.get(right);
            }
            
            if(right < 0) return 1;
            if(right == 0){
                return (str[right] != '0') ? 1 : 0;
            }
            
            if(str[right] != '0'){
                sum1 = helper(str, right-1);
            }
            
            if(str[right-1] != '0'){
                Integer x = Integer.valueOf(new String(str, right - 1, 2));
                if(x >= 1 && x <= 26){
                    sum2 = helper(str, right-2);
                }
            }
            
            m.put(right, sum1+sum2);
            return sum1 + sum2;
        }
        
        public int numDecodings(String s) {
            return helper(s.toCharArray(), s.length() - 1);
        }
    }
    

    方法三:自底向上 + 加 O(n) memo

    class Solution {    
        public int numDecodings(String s) {
            if(s.length() == 0 || s.charAt(0) == '0') return 0;
            
            int[] memo = new int[s.length() + 1];
            memo[0] = 1; memo[1] = 1;
            
            char[] str = s.toCharArray();
            
            for(int i=1; i<s.length(); i++){
                
                int t1 = 0, t2 = 0;
                if(str[i] != '0'){
                    t1 = memo[i];
                }
                
                if(str[i-1] != '0'){
                    Integer x = Integer.valueOf(new String(str, i-1, 2));
                    if(x >=  10 && x <= 26){
                        t2 = memo[i-1];
                    }
                }
                
                memo[i+1] = t1 + t2;
            }
            
            return memo[s.length()];
        }
    }
    

    Word Break : 一维DP,求是否可行

    方法一 : 自顶向下
    递归 + memo

    class Solution {
        Map<Integer, Boolean> m = new HashMap<>();
        
        boolean helper(char[] str, int right, Set<String> dict){
            if(right >= str.length){
                return true;
            }
            
            if(m.containsKey(right)){
                return m.get(right);
            }
            
            for(int j = 1; j < str.length - right + 1; j++){
                if(dict.contains(new String(str, right, j))){
                    if(helper(str, right + j, dict)){
                        return true;
                    }
                }
            }
            
            m.put(right, false);
            return false;
        }
        
        public boolean wordBreak(String s, List<String> wordDict) {
            Set<String> dict = new HashSet<>();
            
            for(String r : wordDict){
                dict.add(r);
            }
            
            return helper(s.toCharArray(), 0, dict);
        }
    }
    

    方法二:自底向上
    递推 + memo
    (ps:这道题并不像经典dp,它当前状态不仅依赖于上阶段的状态,而是依赖于过去所有阶段的状态,看看grandyang的解说

    class Solution {
          public boolean wordBreak(String s, List<String> wordDict) {
            Set<String> dict = new HashSet<>();
            int maxi = 0;
            for(String r : wordDict){
                dict.add(r);
                maxi = Math.max(maxi, r.length()); 
            }
            
            char[] arr = s.toCharArray();
            boolean[] can = new boolean[s.length() + 1];
            can[0] = true;  
            
            for(int i = 0; i < s.length(); i++){
                
                boolean c = false;
                for(int j = i; j >= 0 && j >= (i - maxi); j--){ // j >= (i - maxi)是优化部分
                    if(can[j] && dict.contains(new String(arr, j, i+1 - j))){
                        c = true;break;
                    }
                }
                can[i+1] = c;
            }
            
            return can[s.length()];
        }
    }
    

    Maximum Product Subarray : 求最大最小值

    方法一 : 自底向上
    递推 + memo
    这道题稍微加了一个弯,因为有正负数,我们必须记录上一阶段求得的最大最小值,否则,就不能直接根据上一阶段的结果,直接推导本阶段值。

    class Solution {
        class Maxi{
            int maxi;
            int mini;
            
            Maxi(){
                maxi = 1;
                mini = 1;
            }
        }
        
        public int maxProduct(int[] nums) {
    
            Maxi[] memo = new Maxi[nums.length + 1];
            memo[0] = new Maxi();
            
            for(int i=0; i<nums.length; i++){
                memo[i+1] = new Maxi();
                
                if(nums[i] > 0){
                    int x = Math.max(memo[i].maxi, memo[i].mini);
                    memo[i+1].maxi = (x > 0) ? x * nums[i] : nums[i];
                    
                    int y = Math.min(memo[i].maxi, memo[i].mini);
                    memo[i+1].mini = (y <= 0) ? y * nums[i] : nums[i];
                }
                else if(nums[i] < 0){
                    int x = Math.min(memo[i].maxi, memo[i].mini);
                    memo[i+1].maxi = (x <= 0) ? x * nums[i] : nums[i];
                    
                    int y = Math.max(memo[i].maxi, memo[i].mini);
                    memo[i+1].mini = (y > 0) ? y * nums[i] : nums[i];
                }
                else {
                    memo[i+1].maxi = memo[i+1].mini = 0;
                }
            }
            
            int maxi = Integer.MIN_VALUE;
            for(int i = 1; i < nums.length + 1; i++){
                maxi = Math.max(maxi, memo[i].maxi);
            }
            return maxi;
        }
    }
    

    Ugly Number II

    方法一: 递推
    这个解法的归并排序很经典

    class Solution {       
        public int nthUglyNumber(int n) {
            if(n <= 5) return n;
            
            List<Integer> li = new ArrayList<>();
            li.add(1);
            
            int idx2 = 0;
            int idx3 = 0;
            int idx5 = 0;
            while(li.size() < n){
                int v2 = 2 * li.get(idx2);
                int v3 = 3 * li.get(idx3);
                int v5 = 5 * li.get(idx5);
                
                int mini = Math.min(v2, Math.min(v3, v5));
                li.add(mini);
                
                if(mini == v2) idx2++;
                if(mini == v3) idx3++;
                if(mini == v5) idx5++;
                
            }
            
            return li.get(n-1);
        }
    }
    

    方法二 : 最小堆
    这种写法效率低点,但是是个思路;另外,需搞懂1. Comparator这种写法是什么语法; 2. Priority, Iterator的用法

    class Solution {       
        public int nthUglyNumber(int n) {
            if(n <= 5) return n;
            
            PriorityQueue<Long> q = new PriorityQueue<>(
                new Comparator<Long>(){
                    public int compare(Long a, Long b){
                        if(a < b){
                            return -1;
                        }
                        else if(a > b){
                            return 1;
                        }
                        else {
                            return 0;
                        }
                    }
                });
            
            q.add((long)1);
            
            for(int i = 1; i < n; i++){
                long x = q.remove();
                
                Iterator<Long> it = q.iterator();
                while(it.hasNext()){
                    if(x == it.next()) it.remove();
                }
                
                q.add( (long)(x * 2) );
                q.add( (long)(x * 3) );
                q.add( (long)(x * 5) );
            }
            
            return q.peek().intValue();
        }
    }
    

    Is Subsquence

    方法一: 自底向上, 递推 + memo
    可以将O(N)空间优化为常数

    class Solution {
        public boolean isSubsequence(String s, String t) {
            int lenS = s.length();
            int lenT = t.length();
            if(lenS > lenT) return false;
            
            int memo = new int[lenS + 1];
            memo[0] = -1;
            
            char[] arrS = s.toCharArray();
            char[] arrT = t.toCharArray();
            
            for(int i = 0; i < lenS; i ++){
                memo[i + 1] = lenT;
                for(int j = memo[i] + 1; j < lenT; j ++){
                    if(arrT[j] == arrS[i]){
                        memo[i + 1] = j;
                        break;
                    }
                }
            }
            
            return memo[lenS] != lenT;
        }
    }
    

    413. Arithmetic Slices

    方法一 :参考grandyang
    方法二 : memo + 递推
    dp[i]表示以位置i上的数字结尾的Arithmetic Slice的数目
    这道题主要要搞清楚,为啥dp[i] = dp[i-1] + 1

    class Solution {
        public int numberOfArithmeticSlices(int[] A) {
            
            if( A.length < 3 ) {
                return 0;
            }
            
            int cnt = 0;
            int[] dp = new int[A.length];
            
            for( int i=2; i < A.length; i++ ) {
                if(A[i] - A[i-1] == A[i-1] - A[i-2]) {
                    dp[i] = dp[i-1] + 1;
                }
                
                cnt += dp[i];
            }
            
            return cnt;
        }
    }
    
  • 相关阅读:
    QT 读写sqllite数据库
    SQLLite 简介
    arcengine 开发经典帖 【强烈推荐仔细研读】
    IHookHelper的用法
    ArcSDE中Compress与Compact的区别
    以Network Dataset(网络数据集)方式实现的最短路径分析
    ArcGIS网络概述
    ClassLoader.getResourceAsStream(name);获取配置文件的方法
    Xml中SelectSingleNode方法,xpath查找某节点用法
    Spring整合JUnit4测试使用注解引入多个配置文件
  • 原文地址:https://www.cnblogs.com/holidays/p/leetcode_note.html
Copyright © 2011-2022 走看看