zoukankan      html  css  js  c++  java
  • 贪心,分治,回溯,动态规划 4大核心算法思想

    4大经典算法问题

    如果我们将这四种算法思想分一下类,那贪心、回溯、动态规划可以归为一类,而分治单独可以作为一类,因为它跟其他三个都不大一样。为什么这么说呢?前三个算法解决问题的模型,都可以抽象成我们今天讲的那个多阶段决策最优解模型,而分治算法解决的问题尽管大部分也是最优解问题,但是,大部分都不能抽象成多阶段决策模型

    贪心

    就是每次取最优,最大解。局部最优,得出全局最优。它是动态规划的一种特例。

    实际上,用贪心算法解决问题的思路,并不总能给出最优解,目前使用的比较少,需要针对非常特殊的数据场景。

    分治算法(divide and conquer)

    分治算法用四个字概括就是“分而治之”,将原问题划分成 n 个规模较小而结构与原问题相似的子问题,递归地解决这些子问题,然后再合并其结果,就得到原问题的解

    分治算法是一种处理问题的思想,递归是一种编程技巧

    要解决这种数据量大到内存装不下的问题,我们就可以利用分治的思想。我们可以将海量的数据集合根据某种方法,划分为几个小的数据集合,每个小的数据集合单独加载到内存来解决,然后再将小数据集合合并成大数据集合。实际上,利用这种分治的处理思路,不仅仅能克服内存的限制,还能利用多线程或者多机处理,加快处理的速度。

    for example

    在统计方面比较多,比如统计我国人口,要知道我国人口就要先知道每个省人口,要知道省人口就要知道每个市人口,要知道市人口就要知道每个区县人口,直到村社区,然后汇总求的总人数。

    回溯

    回溯的处理思想,有点类似枚举搜索。我们枚举所有的解,找到满足期望的解。为了有规律地枚举所有可能的解,避免遗漏和重复,我们把问题求解的过程分为多个阶段。每个阶段,我们都会面对一个岔路口,我们先随意选一条路走,当发现这条路走不通的时候(不符合期望的解),就回退到上一个岔路口,另选一种走法继续走。

    常见问题,八皇后和背包的问题。

    回溯的问题就是,时间复杂度为O(n^2), 指数级别,复杂度很高。一般使用递归来实现,我们会使用备忘录去记录已经计算过的情况,避免重复计算,有时候我们也可以根据实际情况进行剪枝,加快查询。

    动态规划 dynamic programing 又叫 DP

    我们把问题分解为多个阶段,每个阶段对应一个决策。我们记录每一个阶段可达的状态集合(去掉重复的),然后通过当前阶段的状态集合,来推导下一个阶段的状态集合,动态地往前推进。

    大部分动态规划能解决的问题,都可以通过回溯算法来解决,只不过回溯算法解决起来效率比较低,时间复杂度是指数级的。动态规划算法,在执行效率方面,要高很多。尽管执行效率提高了,但是动态规划的空间复杂度也提高了,所以,很多时候,我们会说,动态规划是一种空间换时间的算法思想。

    什么样的问题适合用动态规划来解决呢?换句话说,动态规划能解决的问题有什么规律可循呢?

    一般是用动态规划来解决最优问题,而解决问题的过程,需要经历多个决策阶段。每个决策阶段都对应着一组状态。然后我们寻找一组决策序列,经过这组决策序列,能够产生最终期望求解的最优值。

    三个特征

    1. 最优子结构

    最优子结构指的是,问题的最优解包含子问题的最优解。反过来说就是,我们可以通过子问题的最优解,推导出问题的最优解。

    我们也可以理解为,后面阶段的状态可以通过前面阶段的状态推导出来。

    2. 无后效性

    后效性有两层含义,含义是,在推导后面阶段的状态的时候,我们只关心前面阶段的状态值,不关心这个状态是怎么一步一步推导出来的,且当前阶段的状态不受后面阶段的状态影响。

    3. 重复子问题

    不同的决策序列,到达某个相同的阶段时,可能会产生重复的状态。所以一般可以缓存一起来,避免重复计算。

    动态规划DP解法思路

    先通过回溯分析出是否存在重复子问题,以及是怎么发生的,然后尝试在纸上画一下,递归树,看是否能用回溯+备忘录的方式解决重复子问题。这样得到的结果与DP最后得到的结果效率差不多。

    1. 状态转移表法/ 状态转移方程法

    我们先画出一个状态表。状态表一般都是二维的(可能是三维/四维复杂场景),所以你可以把它想象成二维数组。其中,每个状态包含三个变量,行、列、数组值。我们根据决策的先后过程,从前往后,根据递推关系,分阶段填充状态表中的每个状态。最后,我们将这个递推填表的过程,翻译成代码,就是动态规划代码了。

    比如:  matrix[i][j] + Math.min(states[i][j-1], states[i-1][j]);

    第一个,第二个默认值先找出来。

    状态转移方程法有点类似递归的解题思路。我们需要分析,某个问题如何通过子问题来递归求解,也就是所谓的最优子结构。根据最优子结构,写出递归公式,也就是所谓的状态转移方程。有了状态转移方程,代码实现就非常简单了。一般情况下,我们有两种代码实现方法,一种是递归加“备忘录”,另一种是迭代递推。

    min_dist(i, j) = w[i][j] + min(min_dist(i, j-1), min_dist(i-1, j))

    状态转移方程是解决动态规划的关键

    LeetCode 

    1. 最小路径和

    https://leetcode-cn.com/problems/minimum-path-sum/  采用dp方程

    class Solution {
        public int minPathSum(int[][] grid) {
            if (grid == null || grid.length == 0 || grid[0].length == 0) {
                return 0;
            }
            int rows = grid.length, columns = grid[0].length;
            int[][] dp = new int[rows][columns];
            dp[0][0] = grid[0][0];
            for (int i = 1; i < rows; i++) {
                dp[i][0] = dp[i - 1][0] + grid[i][0];
            }
            for (int j = 1; j < columns; j++) {
                dp[0][j] = dp[0][j - 1] + grid[0][j];
            }
            for (int i = 1; i < rows; i++) {
                for (int j = 1; j < columns; j++) {
                    dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
                }
            }
            return dp[rows - 1][columns - 1];
        }
    }

     2. 硬币兑换

    https://leetcode-cn.com/problems/coin-change/solution/322-ling-qian-dui-huan-by-leetcode-solution/

    coins = [1, 2, 5], amount = 11

    F(11)=min(F(111),F(112),F(115))+1

    3. 乘积最大子数组

    https://leetcode-cn.com/problems/maximum-product-subarray/solution/hua-jie-suan-fa-152-cheng-ji-zui-da-zi-xu-lie-by-g/ 

    4. 三角形最小路径和

    https://leetcode-cn.com/problems/triangle/

    [ [2], [3,4], [6,5,7], [4,1,8,3] ]

    定义二维 dp 数组「自底向上的递推」。

    a. 状态定义:
    dp[i][j]表示从点 (i, j) 到底边的最小路径和。

    b. 状态转移:
    dp[i][j] = min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle[i][j]dp[i][j]=min(dp[i+1][j],dp[i+1][j+1])+triangle[i][j]

    class Solution {
        public int minimumTotal(List<List<Integer>> triangle) {
            int n = triangle.size();
            // dp[i][j] 表示从点 (i, j) 到底边的最小路径和。
            int[][] dp = new int[n + 1][n + 1];
            // 从三角形的最后一行开始递推。
            for (int i = n - 1; i >= 0; i--) {
                for (int j = 0; j <= i; j++) {
                    dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle.get(i).get(j);
                }
            }
            return dp[0][0];
        }
    }
    
    作者:sweetiee
    链接:https://leetcode-cn.com/problems/triangle/solution/di-gui-ji-yi-hua-dp-bi-xu-miao-dong-by-sweetiee/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    范仁义js课程---54、匿名函数在框架中的应用
    js中的匿名函数
    【转】使用VisualSVN Server搭建SVN服务器
    浅析Java中CountDownLatch用法
    【转】Android平台下利用zxing实现二维码开发
    【转】Android应用开发性能优化完全分析
    Android自由行之走进zxing,轻松实现二维码扫描
    【转】Java Thread.join()详解
    【转】Spring websocket 使用
    spring4使用websocket
  • 原文地址:https://www.cnblogs.com/roy1/p/13570680.html
Copyright © 2011-2022 走看看