前言

如果你对这篇文章可感兴趣,可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」,查看完整博客分类与对应链接。

本文是我在「力扣」上写的第二篇文章,现将其发布于此处。

动态规划问题一直是大厂面试时最频繁出现的算法题,主要原因在于此类问题灵活度高,思维难度大,没有很明显的套路做法。

也正是因为这个原因,我们打算出一个「动态规划」系列文章来尝试破解面试中所涉及的动态规划问题。而本文就是这个系列的第一篇文章,主要目的是说明动态规划是什么,动态规划问题应该如何思考?

本文一共分成三个部分,具体内容框架如下所示:

宝石挑选

问题引入

小 Q 是一个宝石爱好者。

这一天,小 Q 来到了宝石古董店,店家觉得小 Q 是个宝石行家,于是决定和小 Q 玩一个游戏。

游戏是这样的,一共有 nnn 块宝石,每块宝石在小 Q 心中都有其对应的价值。注意,由于某些宝石质量过于差劲,因此存在只有店家倒贴钱,小 Q 才愿意带走的宝石,即价值可以为负数。

小 Q 可以免费带走一个连续区间中的宝石,比如区间 [1,3][1, 3][1,3] 或区间 [2,4][2, 4][2,4] 中的宝石。

请问小 Q 能带走的最大价值是多少?

问题分析

首先思考最暴力的解法。

枚举所有区间,暴力累加区间中宝石的价值,最后选一个价值最大的区间。时间复杂度 O(n3)O(n^3)O(n3)。

O(n3)O(n^3)O(n3) 显然有些无法接受,因此想想有没有办法优化,比如优化掉暴力累加的部分。

优化 1.0

仔细思考不难发现,我们可以枚举区间右端点,然后固定右端点,左端点不断向左移动,边移动边累加,就可以将时间复杂度优化到 O(n2)O(n^2)O(n2)。

例如我们固定右端点是 3,那么左端点就从 3 移动到 1,边移动边累加答案,就可以在移动过程中计算出区间 [3,3][3, 3][3,3]、[2,3][2, 3][2,3]、[1,3][1, 3][1,3] 的答案了。因此枚举所有区间右端点,即可在 O(n2)O(n^2)O(n2) 时间复杂度内找到答案。

但是 O(n2)O(n^2)O(n2) 时间还是有些过高了,因此思考有没有办法继续优化呢?

优化 2.0

观察 O(n2)O(n^2)O(n2) 的解法,不难发现我们用了 O(n)O(n)O(n) 的时间复杂度才求出了固定某个点为区间右端点时,区间最大价值和。

例如固定了 nnn 为区间右端点后,我们通过从 nnn 到 111 枚举左端点,才求出了以 nnn 为区间右端点时的区间最大价值和,即 O(n)O(n)O(n) 的时间复杂度。

那么继续思考,「以 nnn 为区间右端点的区间最大和」,与「以 n−1n-1n−1 为区间右端点的区间最大和」,这两者是否有关联呢?

为了描述方便,接下来我们用 f[i]f[i]f[i] 来代替「以 iii 为区间右端点的区间最大和」,用 a[i]a[i]a[i] 来代替第 iii 块宝石的价值。

不难发现,如果 f[n−1]f[n-1]f[n−1] 为正数,则 f[n]f[n]f[n] 一定等于 f[n−1]+a[n]f[n-1]+a[n]f[n−1]+a[n];如果 f[n−1]f[n-1]f[n−1] 为负数,则 f[n]f[n]f[n] 一定等于 a[n]a[n]a[n]。因此我们可以推导出如下转移方程:
f[i]=max(f[i−1]+a[i],a[i])f[i]=max(f[i-1]+a[i],a[i]) f[i]=max(f[i−1]+a[i],a[i])

根据上述转移方程,我们可以在 O(n)O(n)O(n) 时间复杂度内求出最大的 f[i]f[i]f[i],即将此题时间复杂度优化到 O(n)O(n)O(n),而这个优化的过程就是「动态规划」的过程。

在上述推导过程中,一共分为两步:

  1. 将整个问题划分为一个个子问题,并令 f[i]f[i]f[i] 为第 iii 个子问题的答案
  2. 思考大规模的子问题如何从小规模的子问题推导而来,即如何由 f[i−1]f[i-1]f[i−1] 推出 f[i]f[i]f[i]

这两个步骤便是「动态规划」解题思路的核心所在,即确定动态规划时的「状态」与「转移方程」。

动态规划概述

动态规划(Dynamic Programming),因此常用 DP 指代动态规划。本块内容我们主要讲解「动态规划解题思路」与「动态规划问题类别」。

动态规划解题思路

动态规划主要分为两个核心部分,一是确定「DP 状态」,二是确定「DP 转移方程」。

DP 状态

「DP 状态」的确定主要有两大原则:

  1. 最优子结构
  2. 无后效性
最优子结构

我们仍以「宝石挑选」例题来讲解这两大原则,首先是「最优子结构」。

什么是「最优子结构」?将原有问题化分为一个个子问题,即为子结构。而对于每一个子问题,其最优值均由「更小规模的子问题的最优值」推导而来,即为最优子结构。

因此「DP 状态」设置之前,需要将原有问题划分为一个个子问题,且需要确保子问题的最优值由「更小规模子问题的最优值」推出,此时子问题的最优值即为「DP 状态」的定义。

例如在「宝石挑选」例题中,原有问题是「最大连续区间和」,子问题是「以 iii 为右端点的连续区间和」。并且「以 iii 为右端点的最大连续区间和」由「以 i−1i-1i−1 为右端点的最大连续区间和」推出,此时后者即为更小规模的子问题,因此满足「最优子结构」原则。

由此我们才定义 DP 状态 f[i]f[i]f[i] 表示子问题的最优值,即「以 iii 为右端点的最大连续区间和」。

无后效性

而对于「无后效性」,顾名思义,就是我们只关心子问题的最优值,不关心子问题的最优值是怎么得到的。

仍以「宝石挑选」例题为例,我们令 DP 状态 f[i]f[i]f[i] 表示「以 iii 为右端点的最大连续区间和」,我们只关心「以 iii 为右端点的区间」这个子问题的最优值,并不关心这个子问题的最优值是从哪个其它子问题转移而来。

即无论 f[i]f[i]f[i] 所表示区间的左端点是什么,都不会影响后续 f[i+1]f[i+1]f[i+1] 的取值。影响 f[i+1]f[i+1]f[i+1] 取值的只有 f[i]f[i]f[i] 的数值大小。

那怎样的状态定义算「有后效性」呢?

我们对「宝石挑选」例题增加一个限制,即小 Q 只能挑选长度 ≤k\leq k≤k 的连续区间。此时若我们定义 f[i]f[i]f[i] 表示「以 iii 为右端点的长度 ≤k\leq k≤k 的最大连续区间和」,则 f[i+1]f[i+1]f[i+1] 的取值不仅取决于 f[i]f[i]f[i] 的数值,还取决于 f[i]f[i]f[i] 是如何得到的。

因为如果 f[i]f[i]f[i] 取得最优值时区间长度 =k=k=k,则 f[i+1]f[i+1]f[i+1] 不能从 f[i]f[i]f[i] 转移得到,即 f[i]f[i]f[i] 的状态定义有后效性。

最后概括一下,「最优子结构」就是「DP 状态最优值由更小规模的 DP 状态最优值推出」,此处 DP 状态即为子问题。而「无后效性」就是「无论 DP 状态是如何得到的,都不会影响后续 DP 状态的取值」。

DP 转移方程

有了「DP 状态」之后,我们只需要用「分类讨论」的思想来枚举所有小状态向大状态转移的可能性即可推出「DP 转移方程」。

我们继续以「宝石挑选」问题为例。

在我们定义「DP 状态」f[i]f[i]f[i] 之后,我们考虑状态 f[i]f[i]f[i] 如何从 f[1]∼f[i−1]f[1]\sim f[i-1]f[1]∼f[i−1] 这些更小规模的状态转移而来。

仔细思考可以发现,由于 f[i]f[i]f[i] 表示的是连续区间的和,因此其取值只与 f[i−1]f[i-1]f[i−1] 有关,与 f[1]∼f[i−2]f[1]\sim f[i-2]f[1]∼f[i−2] 均无关。

我们再进一步思考,f[i]f[i]f[i] 取值只有两种情况,一是向左延伸,包含 f[i−1]f[i-1]f[i−1],二是不向左延伸,仅包含 a[i]a[i]a[i],由此我们可以得到下述「DP 转移方程」:
f[i]=max(f[i−1]+a[i])f[i]=max(f[i-1]+a[i]) f[i]=max(f[i−1]+a[i])
注意,i∈[1,n]i\in[1,n]i∈[1,n] 且 f[0]=0f[0]=0f[0]=0。

动态规划问题类别

讲述完 DP 问题的解题思路后,我们来大致列举一下 DP 问题的类别。

DP 问题主要分为两大类,第一大类是 DP 类型,第二大类是 DP 优化方法。

其中在 DP 类型部分,面试中最常考察的就是「线性 DP」,而在优化方法部分,最常见的是「RMQ 优化」,即使用线段树或其它数据结构查询区间最小值,来优化 DP 的转移过程。

习题练习

接下来我们以三道习题为例,来强化一下确定「DP 状态」和「DP 转移方程」的 DP 问题求解思路。

面试题 08.01. 三步问题

题目描述

三步问题。有个小孩正在上楼梯,楼梯有 n 阶台阶,小孩一次可以上 1 阶、2 阶或 3 阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模 1000000007。

示例 1:

输入:n = 3
输出:4
说明: 有四种走法

示例 2:

输入:n = 5
输出:13

数据范围

n 范围在 [1, 1000000] 之间

解题思路

DP 问题思路主要就是确定「DP 状态」与「DP 转移方程」,因此我们首先考虑「DP 状态」。

「DP 状态」的确定有两大原则,一是「最优子结构」,二是「无后效性」,简要概括就是将原问题划分为多个子问题,且「大规模子问题最优值」仅与「小规模子问题最优值」有关,与「小规模子问题最优值」是如何得到的无关。

此题需要求出爬 nnn 阶楼梯的总方案数,因此很容易想到子问题是爬 iii 阶楼梯的总方案数。接下来再进一步验证该状态是否符合「最优子结构」与「无后效性」两大原则。

令 f[i]f[i]f[i] 表示爬 iii 阶楼梯的总方案数,原问题被划分为了多个求最优值的子问题,

继续思考,不难发现小孩爬楼梯只有三种选项,一次上 1、2、3 阶,因此 f[i]f[i]f[i] 的值仅由 f[i−1]f[i-1]f[i−1]、f[i−2]f[i-2]f[i−2]、f[i−3]f[i-3]f[i−3] 的值决定,因此符合「最优子结构」原则。

再进一步思考,f[i]f[i]f[i] 的取值与 f[i−1]f[i-1]f[i−1]、f[i−2]f[i-2]f[i−2]、f[i−3]f[i-3]f[i−3] 的数值是如何得到的无关,因此符合「无后效性」原则。

确定完「DP 状态」后,我们再来确定「DP 转移方程」。

由于小孩只有三种爬楼选项,因此 f[i]f[i]f[i] 的值仅由 f[i−3]∼f[i−1]f[i-3]\sim f[i-1]f[i−3]∼f[i−1] 决定。且由于爬楼的最后一步不同,因此 f[i]f[i]f[i] 的值由 f[i−3]∼f[i−1]f[i-3]\sim f[i-1]f[i−3]∼f[i−1] 累加得到,即如下所示:
f[i]=(f[i−1]+f[i−2]+f[i−3])%modf[i]=(f[i-1]+f[i-2]+f[i-3])\ \%\ mod f[i]=(f[i−1]+f[i−2]+f[i−3]) % mod

注意,f[1]=1f[1]=1f[1]=1,且转移时需要注意 f[i−1]f[i-1]f[i−1]、f[i−2]f[i-2]f[i−2]、f[i−3]f[i-3]f[i−3] 不要越界。

C++ 代码实现

class Solution {public:vector<int> f;int mod = 1000000007;int waysToStep(int n) {f.resize(n+1);f[0] = 1;for(int i = 1; i <= n; i++) {f[i] = f[i-1];if(i >= 2) f[i] = (f[i] + f[i-2]) % mod;if(i >= 3) f[i] = (f[i] + f[i-3]) % mod;}return f[n];}
};

64. 最小路径和

题目描述

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

**说明:**每次只能向下或者向右移动一步。

示例 1:

输入:
[[1,3,1],[1,5,1],[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。

解题思路

仍然是相同的解题思路,即依次确定「DP 状态」与「DP 转移方程」,且「DP 状态」的确定需要满足「最优子结构」与「无后效性」。

此题需要求出从左上角出发,到达坐标 (m,n)(m,n)(m,n) 的路径数字和最小值。因此不难想到,子问题就是从左上角出发,到达坐标 (i,j)(i,j)(i,j) 的路径数字和最小值。

令 f[i][j]f[i][j]f[i][j] 表示从左上角到坐标 (i,j)(i,j)(i,j) 的路径数字和最小值,原问题即可被划分为多个求最优值的子问题,且由于每次只能向下或向右移动一步,因此 f[i][j]f[i][j]f[i][j] 的取值由 f[i−1][j]f[i-1][j]f[i−1][j] 和 f[i][j−1]f[i][j-1]f[i][j−1] 的值决定,即符合「最优子结构原则」。

进一步验证,可以发现,f[i][j]f[i][j]f[i][j] 的取值与 f[i−1][j]f[i-1][j]f[i−1][j] 和 f[i][j−1]f[i][j-1]f[i][j−1] 所对应的具体路径无关,因此符合「无后效性」。

此处啰嗦一下。如果题目改为每次可以向上、下、左、右移动一步,且不能走重复的格子,则 f[i][j]f[i][j]f[i][j] 的值虽然与 f[i][j−1]f[i][j-1]f[i][j−1]、f[i][j+1]f[i][j+1]f[i][j+1]、f[i−1][j]f[i-1][j]f[i−1][j]、f[i+1][j]f[i+1][j]f[i+1][j] 的值有关,但由于「不能走重复的格子」这一限制,f[i][j−1]∼f[i+1][j]f[i][j-1]\sim f[i+1][j]f[i][j−1]∼f[i+1][j] 所对应的具体路径会影响到 f[i][j]f[i][j]f[i][j] 的取值,即不符合「无后效性」。

确定完「DP 状态」后,继续确定「DP 转移方程」。

由于只能向下或向右移动一步,且由于其最后一步不同,因此 f[i][j]f[i][j]f[i][j] 由 f[i−1][j]f[i-1][j]f[i−1][j] 和 f[i][j−1]f[i][j-1]f[i][j−1] 中的最小值转移得到,即如下所示:
f[i][j]=min(f[i−1][j],f[i][j−1])+grid[i][j]f[i][j]=min(f[i-1][j],f[i][j-1])+grid[i][j] f[i][j]=min(f[i−1][j],f[i][j−1])+grid[i][j]

注意,grid[i][j]grid[i][j]grid[i][j] 表示坐标 (i,j)(i,j)(i,j) 处的数字大小,f[1][1]=grid[1][1]f[1][1]=grid[1][1]f[1][1]=grid[1][1],转移时需要注意不要越界。

C++ 代码实现

class Solution {public:int minPathSum(vector<vector<int>>& grid) {for(int i = 0; i < grid.size(); i++)for(int j = 0; j < grid[0].size(); j++) {if(i == 0 && j == 0) continue;int tp = 1e9;if(i > 0) tp = min(tp, grid[i-1][j]);if(j > 0) tp = min(tp, grid[i][j-1]);grid[i][j] += tp;}return grid[grid.size()-1][grid[0].size()-1];}
};

152. 乘积最大子数组

题目描述

给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

示例 1:

输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

示例 2:

输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

解题思路

继续采用相同的解题思路,即依次确定「DP 状态」与「DP 转移方程」,且「DP 状态」的确定需要满足「最优子结构」与「无后效性」。

此题其实是「宝石挑选」问题的进阶版,即连续区间最大乘积。因此与「宝石挑选」问题的思路一致,令 f[i]f[i]f[i] 表示以 iii 为右端点的连续区间最大乘积,即可将原问题划分为多个求最优值的子问题,但这个状态定义是否符合「最优子结构」原则呢?

我们可以举一个例子来进一步思考。

例如给出 nums=[2,−1,−2]nums=[2,-1,-2]nums=[2,−1,−2],根据上述 f[i]f[i]f[i] 的定义,我们可以得到 f=[2,−1,4]f=[2,-1,4]f=[2,−1,4]。不难发现 f[3]=4≠nums[3]≠f[2]∗nums[3]f[3]=4\not=nums[3]\not=f[2]*nums[3]f[3]=4​=nums[3]​=f[2]∗nums[3],f[i]f[i]f[i] 的值与 f[i−1]f[i-1]f[i−1] 的值无关,即 DP 状态最优值无法由更小规模的 DP 状态最优值推出,因此不符合「最优子结构」原则。

于是问题来了,怎样的状态定义才符合「最优子结构」呢?

继续思考可以发现,上述状态定义出错的原因主要在于如果 nums[i]nums[i]nums[i] 为负数,则 $ f[i-1]*nums[i]$ 只会越乘越小。因此我们需要根据 nums[i]nums[i]nums[i] 的正负值进行分类讨论:

  • nums[i]>0nums[i]>0nums[i]>0

    • f[i]=max(nums[i],f[i−1]∗nums[i])f[i]= max(nums[i],f[i-1]*nums[i])f[i]=max(nums[i],f[i−1]∗nums[i])
  • nums[i]≤0nums[i] \leq 0nums[i]≤0
    • f[i]=max(nums[i],f[i]= max(nums[i],f[i]=max(nums[i],「以 i−1i-1i−1 为右端点的连续区间最小乘积」∗*∗ nums[i])nums[i])nums[i])

由此可以发现,我们需要引入新的「DP 状态」。令 maxn[i]maxn[i]maxn[i] 表示「以 iii 为右端点的连续区间最大乘积」,minn[i]minn[i]minn[i] 表示「以 iii 为右端点的连续区间最小乘积」。

不难发现 maxn[i]maxn[i]maxn[i]、minn[i]minn[i]minn[i] 的取值由 maxn[i−1]maxn[i-1]maxn[i−1]、minn[i−1]minn[i-1]minn[i−1] 的值推导而来,且与其具体的区间大小无关,因此同时满足「最优子结构」与「无后效性」原则。

最后我们再通过「分类讨论」即可确定如下「DP 转移方程」:

if(nums[i] > 0) {maxn[i] = max(nums[i], maxn[i - 1] * nums[i]);minn[i] = min(nums[i], minn[i - 1] * nums[i]);
}
else {maxn[i] = max(nums[i], minn[i - 1] * nums[i]);minn[i] = min(nums[i], maxn[i - 1] * nums[i]);
}

总结一下,此题根据「最优子结构」原则否定了第一种状态定义。否定之后进一步观察题目性质,得到了新的「DP 状态」,并通过「分类讨论」的方式推出「DP 转移方程」,使得本题得以圆满解决。

C++ 代码实现

class Solution {public:vector<int> maxn, minn;int maxProduct(vector<int>& nums) {int n = nums.size(), ans = nums[0];maxn.resize(n);minn.resize(n);maxn[0] = minn[0] = nums[0];for (int i = 1; i < nums.size(); ++i) {if(nums[i] > 0) {maxn[i] = max(nums[i], maxn[i - 1] * nums[i]);minn[i] = min(nums[i], minn[i - 1] * nums[i]);}else {maxn[i] = max(nums[i], minn[i - 1] * nums[i]);minn[i] = min(nums[i], maxn[i - 1] * nums[i]);}ans = max(ans, maxn[i]);}return ans;}
};

总结

最后我们来总结一下 DP 问题的解题思路:

  • 确定「DP 状态」

    • 符合「最优子结构」原则:DP 状态最优值由更小规模的 DP 状态最优值推出
    • 符合「无后效性」原则:状态的得到方式,不会影响后续其它 DP 状态取值
  • 确定「DP 转移方程」
    • 分类讨论,细心枚举

最后的最后,希望大家在求解 DP 问题时可以参考上述解题思路,祝大家刷题愉快!

算法萌新如何学好动态规划(一)相关推荐

  1. random_state的值如何选_算法萌新如何学好动态规划(3)

    本文是「动态规划」系列文章的第三篇,作为 算法萌新如何学好动态规划(2) 的一个延伸.本篇文章将主要聚焦于动态规划经典模型 -- 背包问题的讲解. 背包问题属于线性 DP 模型,之所以单独拎出来讲,主 ...

  2. 机器学习萌新必学的 Top10 算法

    点击上方"Datawhale",选择"星标"公众号 价值内容第一时间获取 来源: 量子位 在机器学习领域里,不存在一种万能的算法可以完美解决所有问题,尤其是像预 ...

  3. 腾讯广告算法大赛 | 萌新粉丝投稿讲述数据竞赛小白观赛心得

    腾讯广告算法大赛高手云集,在围观比赛的过程中你一定也有了不少心得体验!想要分享这份感受却无处诉说?本期我们接受了来自大赛粉丝的投稿,让我们来一起看看萌新们都有什么收获吧! 作为一个数据竞赛经验很少的小 ...

  4. 萌新卷妹带你逃出算法无名岛第五站

    萌新卷妹带你逃出算法无名岛第五站

  5. 萌新卷妹带你逃出算法无名岛第六站

    萌新卷妹带你逃出算法无名岛第六站

  6. 从算法的角度分析王者荣耀的低级分享系列(1)设置坑死萌新

    2019年7月25日更正, 王者又把技能轮盘设置调回去了, 和之前一毛一样, 我要把轮盘调到0才是最顺畅的. 我们在玩王者, 策划在玩我们, 另外元歌的34技能释放不出来的bug也回来了, 并且更严重 ...

  7. 萌新卷妹带你逃出算法无名岛第二站

    萌新卷妹带你逃出算法无名岛第二站

  8. 记萌新赛的命题过程与踩气球过程

    从命题环节开始记起吧,踩气球部分放在后半段压轴. 大约$20$天以前,那天早上我刚下火车,得知今年比赛命题组组长是$xiang578$,老师发的命题人员名单中并没有我.出于对命题工作的好奇与热爱,我向 ...

  9. 刚毕业的职场萌新应具备这9种技能

    一年一度的毕业季又到了 不知道各位有没有找到心仪的工作呢 初入职场的萌新 一定是既激动又紧张 今日分享一份职场新人书单 介绍9种职场必备的技能 这既是图灵君多年工作的经验总结 也是送给所有毕业生的一份 ...

  10. 超强NLP思维导图,知识点全面覆盖:从基础概念到最佳模型,萌新成长必备资源...

    栗子 发自 凹非寺 量子位 报道 | 公众号 QbitAI 一只萌新,想把自己修炼成一个成熟的NLP研究人员,要经过一条怎样的路? 有个名叫Tae-Hwan Jung的韩国小伙伴,做了一份完整的思维导 ...

最新文章

  1. Servlet(一)
  2. 贝壳一站式大数据开发平台实践
  3. 计算机PS英语词汇,操作计算机必读(必会)的53个英文单词共享
  4. Hibernate4 注解方法说明
  5. 删除网络信息服务器端,网络安全:手动清除gh0st远控服务端
  6. Linux umask and chmod
  7. SpringBoot系列(8):SpringBoot中的MVC支持【组件型注解、请求和参数型注解】详解
  8. xp 安装IPv6后,无法上网
  9. PHP与其他语言的比较
  10. stm32L152 使用内部内部flash做EEPROM 出FLASH_ERROR_WRP问题及解决办法
  11. (2020)Simple Copy-Paste is a Strong Data Augmentation Method for Instance Segmentation
  12. 掌控板+Mixly+MixIO 初试物联网-摇杆篇
  13. 奇异值分解及几何意义
  14. 安卓7.1 新特性Shortcut
  15. 【JS动画基础】 闪动,匀速,缓动。
  16. 2019年10月8日股市走势预测——06
  17. 数据中台体系化建设核心方法论
  18. NumPy入门讲座(5):实战演练
  19. Java动态生成pdf文件(用于实时生成电子证书)
  20. (23)【漏洞利用】【原理、利用过程】中间件解析漏洞、CMS漏洞、编辑器漏洞、CVE漏洞

热门文章

  1. 摩托罗拉里程碑2刷android 2.3.4
  2. RHEL AS 5 安装MYSQL
  3. 轻量容器和注射依赖 的自实现
  4. 给wordpress添加留言者操作系统、浏览器和运营商信息
  5. 6. Keras-RNN应用
  6. java的三种流程控制语录_Java 流程控制语句
  7. eplan窗口宏与符号宏是什么_如何使用EPLAN里的宏值集?
  8. python的if和else、for、while语法_Python中的if、while、for 语法及实例
  9. fraction类的创建java_设计一个表示分数的类Fraction。这个类用两个int类型的变量分别表示分子和分母。(Java语言)...
  10. 计算机二级与c语言有什么关系,计算机二级c和c++区别?