百度知道里面的答主中心答主招幕怎么进不去了在加载中?

300.最长上升子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度。
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。

  1. 上升的定义,nums[i]<nums[j],子序列的定义:不一定要求你连续
  2. 最值:联想到最优子结构,dp[i]可以通过dp[i-1]来求
  3. 动态规划联想到,状态和选择,选择就是选择nums[i]吗?,至于状态dp[i]的定义是:在num[i]时,最长上升子序列的个数;
  4. 想在的问题是状态转移方程如何写:
dp[0]=1, 即当nums[0]时,你本身也可以作为一个子序列
  • 首先分析滑动窗口法不适合
  • 最值问题,比较怪,想到动态规划,联想到最优子结构,dp[i]通过dp[i-1]来求
  • 状态和选择,选择当然是是否选择nums[i], 状态呢?dp[i]的状态如何定义, 若定义为:
这样的话状态转移方程不好写 所以dp[i]定义为: 这种定义之下,想得到整个 nums 数组的「最大子数组和」,不能直接返回 res = max(res, dp[i]); // 也就是说,不一定加上nums[i]后子数组是最大的,有可能返回找前面几个连续数组合
依然使用数学归纳法来找状态转移关系:假设我们已经算出了 dp[i-1],如何推导出 dp[i] 呢?
可以做到,dp[i] 有两种「选择」,要么与前面的相邻子数组连接,形成一个和更大的子数组;
要么不与前面的子数组连接,自成一派,自己作为一个子数组。
如何选择?既然要求「最大子数组和」,当然选择结果更大的那个啦:

综上,我们已经写出来状态方程,就可以直接写出解法:

# 即根据状态的定义:dp[i]定义为:以nums[i]为结尾的[最大子数组和] # 那么在这种定义之下,我们的求解问题整个nums数组的最大子数组和, # 不一定是以nums[i]结尾的,因为若要回答我们的问题, # 需要遍历整个dp数组
  • 这个0-1背包的含义是想说明题目中的物品不能分割,要么装进包里,要么不装。(图解算法已经讲的很明白了)
  • 先说状态,如何才能描述一个问题局面?只要给定几个可选物品和一个背包的容量限制,就形成了一个背包问题,对不对?所以状态有两个,就是「背包的容量」和「可选择的物品」。
    再说选择,也很容易想到啊,对于每件物品,你能选择什么?选择就是「装进背包」或者「不装进背包」嘛。
    明白了状态和选择,动态规划问题基本上就解决了,只要往这个框架套就完事儿了:
  • 第二步要明确dp数组的定义
    dp数组是什么?其实就是描述问题局面的一个数组。换句话说,我们刚才明确问题有什么「状态」,现在需要用dp数组把状态表示出来。
    首先看看刚才找到的「状态」,有两个,也就是说我们需要一个二维dp数组,一维表示可选择的物品,一维表示背包的容量。

dp[i][w]的定义如下:对于前i个物品,当前背包的容量为w,这种情况下可以装的最大价值是dp[i][w]。

比如说,如果 dp[3][5] = 6,其含义为:对于给定的一系列物品中,若只对前 3 个物品进行选择,当背包容量为 5 时,最多可以装下的价值为 6。

PS:为什么要这么定义?便于状态转移,或者说这就是套路,记下来就行了。建议看一下我们的动态规划系列文章,几种动规套路都被扒得清清楚楚了。

因为没有物品或者背包没有空间的时候,能装的最大价值就是 0。
  • 第三步,根据「选择」,思考状态转移的逻辑。
    简单说就是,上面伪码中「把物品i装进背包」和「不把物品i装进背包」怎么用代码体现出来呢?
这一步要结合对dp数组的定义和我们的算法逻辑来分析:
先重申一下刚才我们的dp数组的定义:
dp[i][w]表示:对于前i个物品,当前背包的容量为w时,这种情况
下可以装下的最大价值是dp[i][w]。
如果你没有把这第i个物品装入背包,那么很显然,最大价值dp[i][w]
应该等于dp[i-1][w]。你不装嘛,那就继承之前的结果。
首先,由于i是从 1 开始的,所以对val和wt的取值是i-1。
==显然,你应该寻求剩余重量w-wt[i-1]限制下能装的最大价值,加上第i个
物品的价值val[i-1],这就是装第i个物品的前提下,背包可以装的最大价
综上就是两种选择,我们都已经分析完毕,也就是写出来了状态转移方程,

最后一步,把伪码翻译成代码,处理一些边界情况。

我用 C++ 写的代码,把上面的思路完全翻译了一遍,并且处理
了w - wt[i-1]可能小于 0 导致数组索引越界的问题:

对于这个问题,看起来和背包没有任何关系,为什么说它是背包问题呢?
首先回忆一下背包问题大致的描述是什么:
给你一个可装载重量为 W 的背包和 N 个物品,每个物品有重量和价值两个属性。其中第 i 个物品的重量为 wt[i],价值为 val[i],现在让你用这个背包装物品,最多能装的价值是多少?
那么对于这个问题,我们可以先对集合求和,得出 sum,把问题转化为背包问题:
给一个可装载重量为 sum / 2 的背包和 N 个物品,每个物品
的重量为 nums[i]。现在让你装物品,是否存在一种装法,
  • 第一步 明确两点状态和选择,状态和选择
    状态:背包的容量和可选择的物品
    选择:装进背包或不装进背包

  • 第二步 明确dp数组的含义
    按背包问题的套路,可给出如下定义:
    dp[i][j]=x表示,对于前i个物品,当前背包的容量为j时,若x为true,则可说明可以恰好把背包装满,若x为false,则说明不能恰好把背包装满

  • 第三步 根据选择,思考状态转移的逻辑
    回想到dp数组的含义,可以根据选择对dp[i][j]得到以下状态转移:
    如果不把 nums[i] 算入子集,或者说你不把这第 i 个物品装入背包,那么是否能够恰好装满背包,取决于上一个状态 dp[i-1][j],继承之前的结果。

再进一步,是否可以优化这个代码呢?注意到 dp[i][j] 都是通过上
一行 dp[i-1][..] 转移过来的,之前的数据都不会再使用了。
所以,我们可以进行状态压缩,将二维 dp 数组压缩为一维,节约空间复杂度:
能被先被覆盖要倒过来赋值它。简单来说,就是答主所解释的, 因为每个物品(或者说数字)只能用一次,以免之前的结果影响


我们可以把这个问题转化为背包问题的描述形式:

有一个背包,最大容量为 amount,有一系列物品 coins,每个物品的重量为 coins[i],每个物品的数量无限。请问有多少种方法,能够把背包恰好装满?

这个问题和我们前面讲过的两个背包问题,有一个最大的区别就是,每个物品的数量是无限的,这也就是传说中的「完全背包问题」,没啥高大上的,无非就是状态转移方程有一点变化而已。

下面就以背包问题的描述形式,继续按照流程来分析。

  1. 第一步要明确两点,「状态」和「选择」。
    状态有两个,就是「背包的容量」和「可选择的物品」,选择就是「装进背包」或者「不装进背包」嘛,背包问题的套路都是这样。
  2. 第二步要明确 dp 数组的定义。
    首先看看刚才找到的「状态」,有两个,也就是说我们需要一个二维 dp 数组。
    若使用前i个物品,当背包容量为 j 时,有 dp[i][j] 种方法可以装满背包。
    换句话说,翻译回我们的题目就是:
    若只使用coins 中的前 i 个硬币的面值,若想凑出金额 j,有 dp[i][j] 种凑法。
    经过以上定义,可以得到:
    base case 为 dp[0][..] = 0, dp[..][0] = 1。因为如果不使用任何硬币面值,就无法凑出任何金额;如果凑出的目标金额为 0,那么“无为而治”就是唯一的一种凑法。
  1. 根据「选择」,思考状态转移的逻辑。
    我们这个问题的特殊点在于物品的数量是无限的,所以这里和之前写的背包问题文章有所不同。

如果不把这第 i 个物品装入背包,也就是说你不使用 coins[i]这个面值的硬币,那么凑出面额 j 的方法数 dp[i][j]应该等于 dp[i-1][j],继承之前的结果。

首先由于i是从1开始的,所以coins的索引是i-1时表示第i个硬币的面值。
dp[i][j-coins[i-1]]也不难理解,如果你决定使用这个面值的硬币,那么就应该关注如何凑出金额j-coins[i-1]

比如说,你想用面值为2的硬币凑出金额5,那么如果你知道了凑出金额3的方法,再加上一枚面额为2的硬币,不就可以凑出5了嘛

综上就是两种选择,而我们想求的dp[i][j]是共有多少种凑法,所以dp[i][j]的值应该是以上两种选择的结果之和:

最后一步,把伪码翻译成代码,处理一些边界情况

# 比较和子集背包问题的区别
  1. 我们通过观察可以发现,dp 数组的转移只和dp[i][..]dp[i-1][..]有关,所以可以压缩状态,进一步降低算法的空间复杂度:
    状态压缩的原理还不是很懂

编辑距离问题就是给我们两个字符串 s1s2,只能用三种操作,让我们把 s1变成s2,求最少的操作数。需要明确的是,不管是把 s1变成 s2还是反过来,结果都是一样的,所以后文就以 s1变成

前文「」说过,解决两个字符串的动态规划问题,一般都是用两个指针 i,j分别指向两个字符串的最后,然后一步步往前走,缩小问题的规模

设两个字符串分别为"rad"和"apple",为了把s1变成s2,算法会这样进行:

还有一个很容易处理的情况: 就是 j 走完 s2 时,如果 i 还没走完 s1,那么只能用删除操作把 s1 缩 类似的,如果 i 走完 s1 时 j 还没走完了 s2,那就只能用插入操作把 s2 剩下的字符全部插入 s1。等会会看到,这两种情况就是算法的 base case(类似于归并排序)

对于每对儿字符s1[i]和s2[j], 可以有四种操作:

有这个框架,问题就已经解决了。读者也许会问,这个「三选一」到底该怎么选择呢?很简单,全试一遍,哪个操作最后得到的编辑距离最小,就选谁。这里需要递归技巧,理解需要点技巧,先看下代码:

怎么能一眼看出存在重叠子问题呢?这里再简单提一下,需要抽象出本文算法的递归框架:

对于重叠子问题呢,优化方法无非是备忘录或者 DP table
备忘录很好加,原来的代码稍加修改即可:

有了之前递归解法的铺垫,应该很容易理解。dp[..][0]dp[0][..]对应 base case,dp[i][j](数组,动态规划自底向上)的含义和之前的 dp 函数(自顶向上,递归函数)类似:

既然 dp 数组和递归 dp 函数含义一样,也就可以直接套用之前的思路写代码,唯一不同的是,DP table 是自底向上求解,递归解法是自顶向下求解:

# 字符串第i个字符相等

最近做的这几道字符串的好像都是这个状态转移方程:

题目是这样:你面前有一栋从 1 到 N 共 N 层的楼,然后给你 K 个鸡蛋(K 至少为 1)。现在确定这栋楼存在楼层 0 <= F <= N,在这层楼将鸡蛋扔下去,鸡蛋恰好没摔碎(高于 F 的楼层都会碎,低于 F 的楼层都不会碎)。现在问你,最坏情况下,你至少要扔几次鸡蛋,才能确定这个楼层 F 呢?

也就是让你找摔不碎鸡蛋的最高楼层 F,但什么叫「最坏情况」下「至少」要扔几次呢?我们分别举个例子就明白了。

最坏情况:最原始的方式就是线性扫描:我先在 1 楼扔一下,没碎,我再去 2 楼扔一下,没碎,我再去 3 楼…

至少:现在再来理解一下什么叫「至少」要扔几次。依然不考虑鸡蛋个数限制,同样是 7 层楼,我们可以优化策略。

二分思路显然可以得到最少尝试的次数,但问题是,现在给你了鸡蛋个数的限制 K,直接使用二分思路就不行了。(后面的思路直接看帖子)

二、思路分析(dp函数表示状态转移)
对动态规划问题,直接套我们以前多次强调的框架即可:这个问题有什么「状态」,有什么「选择」,然后穷举。
「状态」很明显,就是当前拥有的鸡蛋数K和需要测试的楼层数N。随着测试的进行,鸡蛋个数可能减少,楼层的搜索范围会减小,这就是状态的变化。
「选择」其实就是去选择哪层楼扔鸡蛋。回顾刚才的线性扫描和二分思路,二分查找每次选择到楼层区间的中间去扔鸡蛋,而线性扫描选择一层层向上测试。不同的选择会造成状态的转移。

现在明确了「状态」和「选择」,动态规划的基本思路就形成了:
肯定是个二维的dp数组(自底向上迭代)或者带有两个状态参数的dp函数(自上而下递归)来表示状态转移;外加一个 for 循环来遍历所有选择,择最优的选择更新结果 :


这段伪码还没有展示递归和状态转移,不过大致的算法框架已经完成了。
我们在第i层楼扔了鸡蛋之后,可能出现两种情况:鸡蛋碎了,鸡蛋没碎。注意,这时候状态转移就来了:

如果鸡蛋碎了,那么鸡蛋的个数K应该减一,搜索的楼层区间应该从[1…N]变为[1…i-1]共i-1层楼;

如果鸡蛋没碎,那么鸡蛋的个数K不变,搜索的楼层区间应该从 [1…N]变为[i+1…N]共N-i层楼。
ps: 细心的读者可能会问,在第i层楼扔鸡蛋如果没碎,楼层的搜索区间缩小至上面的楼层,是不是应该包含第i层楼呀?不必,因为已经包含了。开头说了 F 是可以等于 0 的,向上递归后,第i层楼其实就相当于第 0 层,可以被取到,所以说并没有错误。

因为我们要求的是最坏情况下扔鸡蛋的次数,所以鸡蛋在第i层楼碎没碎,取决于那种情况的结果更大:

递归的 base case 很容易理解:当楼层数N等于 0 时,显然不需要扔鸡蛋;当鸡蛋数K为 1 时,显然只能线性扫描所有楼层:

至此,其实这道题就解决了!只要添加一个备忘录消除重叠子问题即可:


「动态规划」的两个思考方向:
1. 自顶向下求解,称之为「记忆化递归」:初学的时候,建议先写「记忆化递归」的代码,然后把代
码改成「自底向上」的「递推」求解;(就是labuladong的解法,递归+备忘录)
2. 自底向上求解,称之为「递推」或者就叫「动态规划」:在基础的「动态规划」问题里,绝大多数都可以从这个角度入手,做多了以后建议先从这个角度先思考,实在难以解决再考虑「记忆化递归」。

第一步,选择和状态,略

一般而言,需要 0 这个状态的值,这里 0 层楼和 0 个鸡蛋是需要考虑进去的,它们的值会被后来的值所参考,并且也比较容易得到。因此表格需要 N + 1 行,K + 1 列。

  • 第 0 行:楼层为 0 的时候,不管鸡蛋个数多少,都测试不出鸡蛋的 F 值,故全为 0
  • 第 1 行:楼层为 1 的时候,0个鸡蛋的时候,扔 0次,1个以及 1个鸡蛋以上只需要扔 1次
  • 第 0 列:鸡蛋个数为 0的时候,不管楼层为多少,也测试不出鸡蛋的 F 值,故全为 0,虽然不符合题意,但是这个值有效,它在后面的计算中会被用到;
  • 第 1 列:鸡蛋个数为 1的时候,这是一种极端情况,要试出 F 值,最少次数就等于楼层高度(从第1层开始往上试)

输出就是表格的最后一个单元格的值 dp[N][K]

看状态转移方程,当前单元格的值只依赖之前的行,当前列和它左边一列的值。可以状态压缩,让「列」滚动起来。但是「状态压缩」的代码增加了理解的难度,我们这里不做。

先来顺一下解决这种问题的套路:

我们前文多次强调过,很显然只要涉及求最值,没有任何奇技淫
巧,一定是穷举所有可能的结果,然后对比得出最值。

所以说,只要遇到求最值的算法问题,首先要思考的就是:如何穷举出所有可能的结果?

穷举主要有两种算法,就是回溯算法和动态规划,前者就是暴力穷举,而后者是根据状态转移方程推导「状态」。

如何将我们的扎气球问题转化成回溯算法呢?这个应该不难想到的,我们其实就是想穷举戳气球的顺序,不同的戳气球顺序可能得到不同的分数,我们需要把所有可能的分数中最高的那个找出来,对吧。

那么,这不就是一个「全排列」问题嘛,我们前文 回溯算法框架套路详解 中有全排列算法的详解和代码,其实只要稍微改一下逻辑即可,伪码思路如下:

// 输入一组气球,返回戳破它们获得的最大分数 // 回溯算法的伪码解法

回溯算法就是这么简单粗暴,但是相应的,算法的效率非常低。这个解法等同于全排列,所以时间复杂度是阶乘级别,非常高,题目说了nums的大小n最多为 500,所以回溯算法肯定是不能通过所有测试用例的。

这个动态规划问题和我们之前的动态规划系列文章相比有什么特别之处?为什么它比较难呢?
原因在于,这个问题中我们每戳破一个气球 nums[i],得到的分

我们前文动态规划套路框架详解 说过运用动态规划算法的一个重要条件:==子问题必须独立。==所以对于这个戳气球问题,如果想用动态规划,必须巧妙地定义 dp 数组的含义,避免子问题产生相关性,才能推出合理的状态转移方程。

如何定义 dp 数组呢,这里需要对问题进行一个简单地转化。题目说可以认为 nums[-1] = nums[n] = 1,那么我们先直接把这两个边界加进去,形成一个新的数组 points:

那么我们可以改变问题:在一排气球 points 中,请你戳破气球 0 
和气球 n+1 之间的所有气球(不包括 0 和 n+1),使得最终只
剩下气球 0 和气球 n+1 两个气球,最多能够得到多少分?

现在我们要根据这个 dp 数组来推导状态转移方程了,根据我们前文的套路,所谓的推导「状态转移方程」,实际上就是在思考怎么「做选择」,也就是这道题目最有技巧的部分:

不就是想求戳破气球 i 和气球 j 之间的最高分数吗,如果「正向思考」,就只能写出前文的回溯算法;
我们需要「反向思考」,想一想气球 i 和气球 j 之间最后一个被戳破的气球可能是哪一个?

其实气球 i 和气球 j 之间的所有气球都可能是最后被戳破的那一个,不防假设为 k。回顾动态规划的套路,这里其实已经找到了「状态」和「选择」:i 和 j 就是两个「状态」,最后戳破的那个气球 k 就是「选择」。

根据刚才对 dp 数组的定义,如果最后一个戳破气球 k,dp[i][j] 的值应该为:

你不是要最后戳破气球 k 吗?那得先把开区间 (i, k) 的气球都戳破,再把开区间 (k, j) 的气球都戳破;最后剩下的气球 k,相邻的就是气球 i 和气球 j,这时候戳破 k 的话得到的分数就是 points[i]*points[k]*points[j]。

那么戳破开区间 (i, k) 和开区间 (k, j) 的气球最多能得到的分数是多少呢?嘿嘿,就是 dp[i][k] 和 dp[k][j],这恰好就是我们对 dp 数组的定义嘛!


结合这个图,就能体会dp数组定义的巧妙了,由于是开区间,dp[i][k]和dp[k][j]不会影响气球k;而戳破气球k时,旁边相邻的就是气球i和气球j了,最后还会剩下i, (i,i)和气球j (j,j),这也恰好满足了dp数组开区间的定义。

那么,对于一组给定的ij, 我们只要穷举i<k<j的所有气球k, 选择得分最高的作为dp[i][j]的值即可, 这也就是状态转移方程:

// 最后戳破的气球是哪个?

写出状态转移方程就完成这道题的一大半了,但是还有问题:对于k的穷举仅仅是在做选择,到那时如何穷举状态i和j呢:

关于「状态」的穷举,最重要的一点就是:状态转移所依赖的状态必须被提前计算出来。
那么应该如何安排 i 和 j 的遍历顺序,来提供上述的保证呢?我们前文 动态规划答疑篇 写过处理这种问题的一个鸡贼技巧:根据 base case 和最终状态进行推导。

PS:最终状态就是指题目要求的结果,对于这道题目也就是 dp[0][n+1]。


对于任一dp[i][j],我们希望所有dp[i][k]和dp[k][j]已经被计算,画在图上就是这种情况:
那么,为了达到这个要求,可以有两种遍历方法,要么斜着遍历,要么从下到上从左到右遍历:
斜着遍历有一点难写,所以一般我们就从下往上遍历,下面看完整代码:

1143.最长公共子序列/编辑距离(快手百度)


给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

道非常经典的面试题目,因为它的解法是典型的二维动态规划,大部分 比较困难的字符串问题都和这个问题一个套路,比如说 ==编辑距离==

肯定有读者会问,为啥这个问题就是动态规划来解决呢?因为子序列类型的问题,穷举出所有可能的结果都不容易,而动态规划算法做的就是穷举 + 剪枝,它俩天生一对儿。所以可以说只要涉及子序列问题,十有八九都需要动态规划来解决,往这方面考虑就对了。

  • 第一步,一定要明确 dp 数组的含义。对于两个字符串的动态规划问题,套路是通用的。

比如说对于字符串 s1 和 s2,一般来说都要构造一个这样的 DP table:


为了方便理解此表,我们暂时认为索引是从1开始的,待会的代码中只要稍作调整即可,其中dp[i][j]的含义是:对于s1[1..i]s2[1..j],他们的LCS长度是dp[i][j]

比如上图的例子,d[2][4]的含义就是:对于“ac”和“babc”,他们的LCS长度是2。我们最终想得到的答案应该是dp[3][6]

  • 比如说,按照刚才 dp 数组的定义,dp[0][3]=0 的含义是:对于字符串 “” 和 “bab”,其 LCS 的长度为 0。因为有一个字符串是空串,它们的最长公共子序列的长度显然应该是 0。

  • 第三步,找状态转移方程
    状态转移说简单些就是做选择,比如说这个问题,是求 s1 和 s2 的最长公共子序列,不妨称这个子序列为 lcs。那么对于 s1 和 s2 中的每个字符,有什么选择?很简单,两种选择,要么在 lcs 中,要么不在。

这个「在」和「不在」就是选择,关键是,应该如何选择呢?这个需要动点脑筋:如果某个字符应该在 lcs 中,那么这个字符肯定同时存在于 s1 和 s2 中,因为 lcs 是最长公共子序列嘛。所以本题的思路是这样:

用两个指针i和j从后往前遍历s1和s2,如果s1[i]==s2[j], 那么这个字符一定在lcs中;否则的话,s1[i]和s2[j]这两个字符至少有一个不在lcs中,需要丢弃一个。先看一下递归解法,比较容易理解:
 # 这边找到一个 lcs 的元素,继续往前找
 # 谁能让 lcs 最长,就听谁的
 # i 和 j 初始化为最后一个索引

其实这段代码就是暴力解法,我们可以通过备忘录或者 DP table 来优化时间复杂度,比如通过前文描述的 DP table 来解决:

对于 s1[i] 和 s2[j] 不相等的情况,至少有一个字符不在 lcs 中,会不会两个字符都不在呢?比如下面这种情况:
所以代码是不是应该考虑这种情况,改成这样:

我一开始也有这种怀疑,其实可以这样改,也能得到正确答案,但是多此一举,因为 dp[i-1][j-1] 永远是三者中最小的,max 根本不可能取到它。

对于两个字符串的动态规划问题,一般来说都是像本文一样定义 DP table,因为这样定义有一个好处,就是容易写出状态转移方程,dp[i][j] 的状态可以通过之前的状态推导出来:

子序列问题通用思路|516.最长回文子序列

子序列问题是常见的算法问题,而且并不好解决。
首先,子序列问题本身就相对子串、子数组更困难一些,因为前者是不连续的序列,而后两者是连续的,就算穷举你都不一定会,更别说求解相关的算法问题了。

而且,子序列问题很可能涉及到两个字符串,比如前文「最长公共子序列」,如果没有一定的处理经验,真的不容易想出来。所以本文就来扒一扒子序列问题的套路,其实就有两种模板,相关问题只要往这两种思路上想,十拿九稳。

一般来说,这类问题都是让你求一个最长子序列,因为最短子序列就是一个字符嘛,没啥可问的。一旦涉及到子序列和最值,那几乎可以肯定,考察的是动态规划技巧,时间复杂度一般都是 O(n^2)。

原因很简单,你想想一个字符串,它的子序列有多少种可能?起码是指数级的
吧,这种情况下,不用动态规划技巧,还想怎么着?

既然要用动态规划,那就要定义 dp 数组,找状态转移关系。我们说的两种思路模板,就是 dp 数组的定义思路。不同的问题可能需要不同的 dp 数组定义来解决。

1、第一种思路模板是一个一维的dp数组:

举个我们写过的例子[最长递增子序列],在这个思路中dp数组的定义是:
在子数组array[0…i]中,我们要求的子序列(最长递增子序列)的长度是dp[i]。
为啥最长递增子序列需要这种思路呢?前文说得很清楚了,因为这样符合归纳法,可以找到状态转移的关系,这里就不具体展开了。

2、第二种思路模板是一个二维的dp数组:

这种思路运用相对更多一些,尤其是涉及两个字符串/数组的子序列,比如前文讲的「最长公共子序列」。本思路中 dp 数组含义又分为「只涉及一个字符串」和「涉及两个字符串」两种情况。

2.1 涉及两个字符串/数组时(比如最长公共子序列), dp数组的含义如下编辑距离,公共子序列是这种情况

2.2 只涉及一个字符串/数组时(比如本文要讲的最长回文子序列), dp数组的含义如下:
在子数组array[i…j]中,我们要求的子序列(最长回文子序列)的长度为dp[i][j]

之前解决了「最长回文子串」的问题,这次提升难度,求最长回文子序列的长度:
我们说这个问题对 dp 数组的定义是:在子串 s[i…j] 中,最长回文子序列的长度为 dp[i][j]。一定要记住这个定义才能理解算法。

为啥这个问题要这样定义二维的 dp 数组呢?我们前文多次提到,找状态转移需要归纳思维,说白了就是如何从已知的结果推出未知的部分,这样定义容易归纳,容易发现状态转移关系

具体来说,如果我们想求dp[i][j],假设你知道了子问题dp[i+1][j-1]的结果(s[i+1… j-1]中最长回文子序列的长度),你是否能想办法算出dp[i][j]的值(s[i…j]中,最长回文子序列的长度呢?)


可以,这取决于s[i]和s[j]的字符:
如果它俩相等,那么它俩加上s[i+1…j-1]中的最长回文子序列就是s[i…j]的最长回文子序列:

以上两种情况写成代码就是这样:

至此,状态转移方程就写出来了,根据dp数组的定义,我们要求的就是dp[0][n-1],也就是整个s的最长回文子序列的长度。
如果只有一个字符,显然最长回文子序列长度是 1,也就是 dp[i][j] = 1 (i == j)
因为 i 肯定小于等于 j,所以对于那些 i > j 的位置,根本不存在什么子序列,应该初始化为 0。
为了保证每次计算dp[i][j],左下右方向的位置已经被计算出来,只能斜着遍历或者反着遍历:
选择反着遍历,代码如下:

最近很火的一个百度答题项目,真的是你所了解的日入几百上千吗?这个项目我自己并没有实操,但是有朋友在做,我去向他取经之后尝试了一下,给你们解析。好了闲话少叙,细听分说:

百度答题兼职怎么赚钱?

这个答题项目外面都是收费几百不等。具体值不值得做,且往下看

百度知道它是什么:“百度知道”是一种搜索模式,用户根据自己的具体需求提问,并通过积分奖励机制启动其他用户来解决问题。

同时,这些问题的答案将进一步作为搜索结果提供给其他有类似问题的用户,从而达到知识共享的效果。

第一步:下载百度APP

第二步:在百度搜索“百度知道”

第三步:点击下方菜单栏“答主中心”成为答主.回答完这三道模拟考题后才能成为答主,这3道模拟考题会在12个小时内审核通过。我的10分钟后就通过了。

点击成为答主后,先让你选择领域。选领域进来不要选择一些数学,科学,电竞,医疗健康这些专业性比较强的答主领域。建议选择民生,职场,情感,生活这类可以扩展比较广泛的答主领域。然后会让你模拟考试3道题。他会根据你选择的领域给你推荐选题。选择了题目之后,然后去百度搜索答案就行了。

是可以赚到钱,有财富值奖励,这个财富值就是可以兑换奖品的 比如说话费或者是购物卡,还有每天优质回答审核通过也有现金奖励,五毛一块积少成多,不过相比自己的复出来说,那些回报太少了,靠这个养家糊口是绝对不可能的。

至于像那些卖项目的说什么日入几百上千就更扯了,不用相信。至于脚本自动答题,三个大感叹号送给你们!不要信!!!脚本只是一个代替人工的工具,一个粗烂制作的脚本说不定还不如手工操作。而且做的人越多题目自然而然就越少,所以如果没什么项目的可以去试看看多点兼职收入,但是如果规模操作的趁早把这个梦给掐灭!

无聊可以当个打发时间的兼职来做,不建议规模化操作,付出回报率不成正比,更不用去交学费来学习!至于说什么日入几百上千的更是不靠谱,不用信。想操作的有什么不懂得可以直接来问我。

好了,言尽于此谢谢大家,有什么互联网以及实业上的各种问题都可以来问我哦,您的点赞关注是我最大的动力!

我要回帖

更多关于 点击此处设为首页,这句提示 的文章

 

随机推荐