【leetcode】动态规划案例汇总

背景, 基本思路,案例模板

背景

1 动态规划在算法中,可以说是占半壁江山,其中重要的原因是,通过该算法,将直来直去的暴力搜索法,其计算量直接降低一个数量级,其能力是十分显著的;
2 其利用的数学技术就是最优化原理,这一先进的效能工具;
3 本文梳理各种优秀博客及书籍中,对动态规划模板的总结,相互映照,取长补短,形成模式化思维;然后再结合具体的案例,进行应用实践,以期能够养成解决这一类问题的思维;

一、基本思路

1.1 触发条件

   是否可用动态规划方法进行处理,一般需要遵循如下几种条件。

1.1.1 算法的乐趣定义

1 最优化原理
	1)问题的最优子结构的性质
	2)不管之前是否是最优决策,都必须保证从现在开始的决策是在之前决策基础上的最优决策,那么这样的最优子结构就符合最优化原理;
2 无后向性
	1)之前的决策只影响当前阶段的决策,当时对之后各阶段的决策不产生影响;

1.2 实现步骤

1.2.1 五分钟学算法

 动态规划问题,一般分为四个步骤,分别是:
 1 问题分解
 2 状态定义
 3 转移方程推导
 4 算法实现

1.2.2 算法的乐趣

1 定义最优子问题
	1)确定问题的优化目标及最优解判定;
	2)对决策过程分阶段;可按照时间空间顺序或者问题演化状态;
2 定义状态
	1)既是决策对象,也是决策的结果;
	2)对起始状态施加决策,状态发生变化,得到决策的结果状态;
	3)状态的定义是建立在子问题定义的基础上,因此状态必须满足“无后向性”。必要时,可以增加状态的维度,引入更多的约束条件,使得状态定义满足“无后向性”条件;(什么案例?装配线问题,背包问题中的剩余容量约束)
3 定义决策与状态转移方程
	1)从n-1阶段,到n阶段的演化规律;
	2)如果状态选择不合适,导致子问题之间没有重叠,也就不存在状态转移关系,会退化为类似分治法那样的朴素递归搜索算法;
4 确定边界条件
	1)递归终结条件;
	2)对于递推关系直接实现的动态规划方法,需要确定状态转移方程递归式的初始条件或者边界条件;

1.2.3 mu

muyids
动态规划包含三个重要概念:最优子结构,边界,状态转移公式
1 最优子结构
2 边界:边界是最简单的最优子结构,无需再简化便可得到结果;如果一个问题没有边界,将无法得到有限的结果;
3 状态转移方程;
针对本段话,我认为边界这块强调的好,此外,我认为边界有两个维度的含义,一是广义上的最优子结构是一类边界,二是狭义上的每个元素的取值范围,也可以看做是一种边界;
而将两者综合在一起看,后者就是前者细分后的值;

1.3 案例模板

 基于上述1.1和1.2 进行描述,上面有,下面再写;

1.4 其他博文一针见血的话

1 动态规划说起来很高大上,说白了是以空间换时间,将计算结果封存起来,避免重复计算;

二、案例汇总

2.1 二维空间接雨水

题目描述

2.1.1 触发条件

1 最优化原理
1)每一列的最大盛水量之和相加,即为总的盛水量;
2 无后向性
1)当某一列盛水量确定之后,不会发生改变,并且对之后决策不会产生影响;

2.1.2 实现步骤

1 问题分解
	1)子问题是哪一个?
	a 每一列的盛水量,是一个子问题;
	b 当前列左(右)侧的最大高度;然后借用木桶原理计算,即会演化为子问题a
	2)最优解
	所有子问题的和,就是全局最优;

2 状态定义
 1)当前列左(右)侧最大高度--maxLeft, maxRight;
 2)当前列的高度--height[i]
 3)当前列的储水量; 
 
3 转移方程推导
 1)maxLeft[i] = max(maxLeft[i-1], height[i-1])
 
4 边界条件
 1)maxLeft/maxRight的i边界是[0, length - 1];
 2) 初始条件:maxLeft[0] = 0, maxRight[length - 1] = 0; 因为左右两侧不存在储水的情况;

2.1.3 代码

见题目代码

#define fmax(a, b) (((a) > (b)) ? (a) : (b))
#define fmin(a, b) (((a) > (b)) ? (b) : (a))
int trap(int* height, int heightSize){
    // 基于列进行查找处理;
    // 1 求解每列的左右两侧的最大值;
    // 2 针对每一个位置的值,基于木桶原理,进行求解;
    int i, totalNum, min;
    int *maxLeft = malloc(heightSize * sizeof(int));
    int *maxRight = malloc(heightSize * sizeof(int));
    totalNum = 0;
    for (i = 1; i < heightSize - 1; i++) {
        maxLeft[i] = fmax(maxLeft[i - 1], height[i - 1]); 
    }
    for (i = heightSize - 2; i > 0; i--) {
        maxRight[i] = fmax(maxRight[i + 1], height[i + 1]);
    }
    for (i = 1; i < heightSize - 1; i++) {
        min = fmin(maxLeft[i], maxRight[i]);
        if (min > height[i]) {
            totalNum = totalNum + min - height[i];
        }
    }
    free(maxLeft);
    maxLeft = NULL;
    free(maxRight);
    maxRight = NULL;
    return totalNum;
}

2.2 最长回文字符串

添加链接描述

2.2.1 触发条件

1 最优化原理
1)结合中心扩展法,每一个字母为中心的最长回文字符中,选择其中最大的;
2 无后向性
1)当前字符为中心的求解,对其他字符没有影响

2.2.2 实现步骤

1 问题分解
	1)求解每个字符的最长回文字符;
	2)每个字符的求解方式:当前字符为中心,向外扩展;
2 状态定义
	1)中心点的左边坐标;--left
	2)右边坐标;--right
	3)当前字符位置的最长回文长度--length
	4)中心点--i;
	5)当前求出的最长回文字符,初始值是1--maxLen;
3 状态转移方程
	1)dp[left, right] = dp[left + 1, right - 1] 与 s[left] ?= s[right];
	2)注意中心点起始位置,存在与中心点相同的情况
	if (s.charAt(l) == s.charAt(r) && (r - l <= 2 || dp[l + 1][r - 1]))
	3)状态转移方程为当前阶段,然后for循环驱动变化;

2.2.3 代码

public String longestPalindrome(String s) {
        if (s == null || s.length() < 2) {
            return s;
        }
        int strLen = s.length();
        int maxStart = 0;  //最长回文串的起点
        int maxEnd = 0;    //最长回文串的终点
        int maxLen = 1;  //最长回文串的长度

        boolean[][] dp = new boolean[strLen][strLen];

        for (int r = 1; r < strLen; r++) {
            for (int l = 0; l < r; l++) {
                if (s.charAt(l) == s.charAt(r) && (r - l <= 2 || dp[l + 1][r - 1])) {
                    dp[l][r] = true;
                    if (r - l + 1 > maxLen) {
                        maxLen = r - l + 1;
                        maxStart = l;
                        maxEnd = r;

                    }
                }

            }

        }
        return s.substring(maxStart, maxEnd + 1);

    }

作者:reedfan
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zhong-xin-kuo-san-fa-he-dong-tai-gui-hua-by-reedfa/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2.3 跳石板

跳石板

2.3.1 触发条件

1 最优化原理
1)从前往后推导,每一步的最优,达到终点的最优;
2 无后向性
1)达到当前步骤是最优后,那么从当前步开始的之后,都取决于之后的决定,而与之前的步骤无关;

2.3.2 实现步骤

1 问题分解
	1)求解到达每一步的最小步数;
	2)然后以此类推,找出到达最后终点的步数;
2 状态定义
	1)当前的石板位置--i;
	2)到达当前石板的最小步数--dp[i];
	3)下一步能够走的步数--k;
	4)存储所能到达的下一个节点的最小步数--dp[i + k];
3 状态转移方程推导
	1)dp[i + k] = min(dp[i + k], dp[i] + 1);
4 边界条件
	0)step = m - n + 1;
	1)i属于[0,  step - 1]

2.4 把数字翻译成字符串

题目

2.4.1 触发条件

2.4.2 实现步骤

1 问题分解
1)子问题为求出【1-n】中的k位翻译数字的最优,然后不断拼接直到获取n位字符串的最优;
2 状态定义
1)当前字符长度--i--[0, count - 1]
2)当前位置的最大长度--dp[i]
3 状态转移方程
分为两种情况,在‘a’-'z'之间,则说明可以使用字母表示两位数,否则,要么不能跳转,或者高位数是‘0’,本质上仍旧为一位数;
1)dp[i] = dp[i-1];
2)dp[i] = dp[i-1] + dp[i-2];
4 边界条件及实现
1)dp[0] = 1;
2)dp[1]的求解,需要单独拎出来,因为它不适用于dp[i - 2];
3)其他取值范围,在状态定义时已经写过;

2.4.3 其他

1 我在本题花费时间比较长的非智力因素是:起始位置为零或者为一,没有规划好;
2 关于数字转化为字符数组的两种方法,位于csdn的另一篇文章的[2.2小节](https://blog.youkuaiyun.com/qq_31382031/article/details/105344489)进行记录;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值