大家好!欢迎回到动态规划的世界!

这一节的题目绝大部分都是选择的Leetcode中的hard(当然也有少部分的medium)。主要是挑选了一些之前看过的高频题。这些题目没有什么通用的解法,每一个题都需要仔细的去建模和考量,才能够写出对应的状态转移方程。读者可以尝试自己先思考,也可以通过解析摸一摸困难的动态规划题,可能会有哪些难点。

那么我们开始吧。

动态规划(下)

好的,接下来我们就用大量实际的真题,来看一下究竟如何解决动态规划类的问题。

Problem 2: Leetcode 416

给你一个 只包含正整数非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

一个例子是,如果nums = [1,5,11,5],那么答案是true,因为可以分为1, 5, 511两组。

这里要控制的参数有两个:元素和,和数组长度。所以可以考虑一个二维数组dp[i][j]。现在的问题可能就是,应该如何考虑的状态转移方程

可以这么想,前个数,要达到和为,可能之前有哪些情况?首先很明显的是,数多的情况,肯定由数少的情况发展而来。所以大概率,个数的情况,肯定由等情况发展而来了。

所以思考一下,假如现在你有个数,并且元素和为(相当于dp[m][n])。它如何由个数的情况变过来?首先你多了这一个数,你自然可以把它放在两个组中的任何一个。那么一定程度上,你可以认为,这个问题划归为了

在前个数中,选择若干个放在一组,是否有可能和为。

因为放在一个组认为是“选择”的话,那么放在另一个组就相当于“不选择”,正好对应了两种情况。这个本质上的思路就是0-1背包问题

这样的话,dp[i][j]的含义就不难想了。不失一般性我们设是数组长度,而是元素和,那么大体来看,我们应该要输出true or false,那么不妨假设,dp[i][j]表示“前个数,选择若干个,是否有可能和为“?这个时候,如果我们认为数组长度是,而元素和为,那么要求的结果就是dp[m][target],这里的target是数组和的一半(想想为什么?),也就是说,如果数组和是奇数,或者最大的数已经超过一半,直接返回false就好,这个要想到,但容易被忽略。

我们知道了最后的结果,那么如何转移呢?上面已经说了,相当于有“选择”和“不选择”的两种方案。假如说前个数已经决定好,那么实际上第个数如果选择,对应的状态就倒退回了dp[m-1][n - nums[m]],这里的nums[m]表示第个数的值。如果不选择,对应的状态就倒退回了dp[m-1][n]。这两个情况有一个是true,结果就应该是true,所以相当于取或运算的关系。

分析到这里,似乎没什么问题。但是要注意,n - nums[m]是有可能为负的。从题目含义上来分析,相当于说,如果你要求的数字和还没有超过第个数,那么自然不可能去“选择”,因此这种情况下,状态只能倒退回dp[m-1][n]

所以总结一下,状态转移方程为

无后效性这个不难观察,这个我们不多说了。最后,我们看看边界情况。

  1. 不选择任何正整数,那么和为0,这是一定可以做到的。所以dp[i][0] = true

  2. 只有一个正整数可以选,对应的是dp[1][nums[1]] = true

好的,最后我们来把核心的代码粘贴过来。

# Ignored the boundary casesfor (int i = 1; i <= n; i++) {dp[i][0] = true;
}
dp[1][nums[1]] = true;
for (int i = 2; i <= n; i++) {int num = nums[i];for (int j = 1; j <= target; j++) {if (j >= num) {dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];} else {dp[i][j] = dp[i - 1][j];}}
}
return dp[n][target];

好的,这个题算分析的比较细了。之后的题目中,我们会适当减少对大流程的思考,而重点关注题目的一些技巧。

Problem 3: Leetcode 1458

给你两个数组 nums1 和 nums2 。

请你返回 nums1 和 nums2 中两个长度相同的 非空 子序列的最大点积。

数组的非空子序列是通过删除原数组中某些元素(可能一个也不删除)后剩余数字组成的序列,但不能改变数字间相对顺序。比方说,[2,3,5] 是 [1,2,3,4,5] 的一个子序列而 [1,5,3] 不是。

比方说,nums1 = [2,1,-2,5], nums2 = [3,0,-6],那么答案是18。因为可以从 nums1 中得到子序列 [2,-2] ,从 nums2 中得到子序列 [3,-6] 。它们的点积为18(你没忘记点积怎么计算,对吧?)。

这一个题的思路也比较直接。考虑dp[i][j]表示“nums1的前个元素,和nums2的前个元素,所能够构成的最大的内积“。所以又到了要考虑状态转移的时候了,很明显,数是一个一个堆起来的,内积也是一个一个对应乘积再求和的。所以可以考虑两个数组中的分别的最后一个元素。

很明显,这还是一个选择或者不选择的问题。两个元素一共可以有四种搭配,其实分别讨论一下就可以知道。

  1. 内积序列中选择nums1[i], nums2[j],那么这个时候除了要加上nums1[i]*nums2[j],还需要加上dp[i-1][j-1],相当于回退到了之前的情况。

  2. 只选择nums1[i],那么这个时候,其实对应的情况是dp[i][j-1],因为不考虑nums2[j],所以第二个数组考虑前个元素,和考虑前个元素,其实是没区别的

  3. 只选择nums2[j],对应dp[i-1][j]

    1. 两个都不考虑,对应dp[i-1][j-1]

总结一下,我们的状态转移方程就是

无后效性也是不难想到的(至少大部分题都不用担心这一点,有特例但是不多)。至于边界条件,只需要给定一个dp[1][1]作为初始解就可以了。

好的,我们放出核心代码。

for (int i = 1; i <= m; ++i) {for (int j = 1; j <= n; ++j) {int xij = nums1[i] * nums2[j];dp[i][j] = xij;if (i > 0) {dp[i][j] = max(dp[i][j], dp[i - 1][j]);}if (j > 0) {dp[i][j] = max(dp[i][j], dp[i][j - 1]);}if (i > 0 && j > 0) {dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + xij);}}
}

最后只需要输出dp[m][n]就可以了,这里的分别对应两个数组的长度。

Problem 4: Leetcode 115

给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE" 是 "ABCDE" 的一个子序列,而 "AEC" 不是)

题目数据保证答案符合 32 位带符号整数范围。

一个简单的案例是,假如s = "rabbbit", t = "rabbit",那么一共其实有3种可能,大家容易发现,就是你这3个字母b选一个去掉就可以。

这个题就更抽象一点了,但是归根到底其实还是一种递推思想。即思考原序列和子序列在不断推进的时候,状态会发生什么样的变化

官方题解中,设dp[i][j]表示的子序列中,出现的个数。这里的就表示从第个字符到最后一个的这一段,是一种从后到前的推断。但实际上这个题完全也可以从前到后来分析,过程上没有太大的差别。

还是一样,我们考虑是否选择s[i]t[j],这种序列类的题目,很多都是这个思路,从前往后看,或从后往前看,目的就是,观察构成的新的原序列和子序列,是否是符合题目要求的。这就需要观察是否有s[i] == t[j]

如果满足,那么你可以选择让t[j]对应上s[i],那么这个时候的方案数就取决于dp[i+1][j+1]。但也可以说让t[j]并不对应上s[i],那么这就对应了dp[i+1][j]。所以这两种情况加在一起,就对应了dp[i][j]

如果这个条件不满足,那自然不可能选择这两个位置上的字符,因为这样就无法构成子序列的关系了。所以这样的话只有一种情况,就是dp[i+1][j]

总结一下,我们的状态转移方程就是

由于这个我们是从后向前枚举的,所以观察初始状态的时候,应该是观察dp[i][n+1]的情况,这里是子序列的长度。由于这个时候,相当于子序列是空序列(想想为什么?),所以应该答案就是1,也就是说。

好的,我们来看看核心代码吧。

for (int i = 1; i <= m+1; i++) {dp[i][n+1] = 1;
}
for (int i = m; i >= 1; i--) {char sChar = s[i];for (int j = n; j >= 1; j--) {char tChar = t[j];if (sChar == tChar) {dp[i][j] = dp[i + 1][j + 1] + dp[i + 1][j];} else {dp[i][j] = dp[i + 1][j];}}
}

最后的结果就是dp[1][1]

不过这里有一个优化点,就是使用滚动数组。大家可以发现一个事情,就是第一个下标不管由哪一个状态变过来,上一个状态的第一个下标都是。这就意味着其实你没有必要去存储这个维度,因此实际你只需要一个参数就可以了。这样的代码相信也不难改,我们交给读者。

Problem 5: Leetcode 1143

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

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

比方说如果text1 = "abcde", text2 = "ace" ,答案就是3,因为最长的就是"ace"

这个题其实就是最长公共子序列(Largest Common Subsequence,LCS)的问题,是一个面试一定要会的题目。思路和之前的题目完全一样,就是在序列不断推进的时候,观察状态如何做的转移。

现在还是一样,假设dp[i][j],表示**“原序列(包括,下同),新序列,对应的公共子序列的最长长度**“。那么很明显,只有s[i] == t[j]才有可能考虑说对公共子序列的长度有增益,这个时候对应的结果是dp[i-1][j-1] + 1(多说两句,你当然也可以选择说,这两个字符都不选择,倒退到dp[i-1][j-1]这种情况。但很明显,这个结果不可能比之前那个大,所以没必要考虑这种可能性了)。否则的话,就只能取dp[i-1][j], dp[i][j-1]这两种情况中,较大的那个了。

所以状态转移方程为

边界情况其实不用多说,毕竟一开始的情况,数值肯定是0。所以核心代码如下

for (int i = 1; i <= m; i++) {char c1 = text1[i - 1];for (int j = 1; j <= n; j++) {char c2 = text2[j - 1];if (c1 == c2) {dp[i][j] = dp[i - 1][j - 1] + 1;} else {dp[i][j] = fmax(dp[i - 1][j], dp[i][j - 1]);}}
}

最后的答案就是dp[m][n]。这个题就当复习了。

Problem 6: Leetcode 312

有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。

现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。

求所能获得硬币的最大数量。

比方说如果nums = [3, 1, 5, 8],那么答案就是167。但这个167怎么变过来的,我就不写了,你们看看原出处就好……

这一个题的思路其实不看确实想不到,但是考虑到它实在是hard中的一个高频题,我们把它当成了例子。

首先要注意的是,戳气球和它的反问题:放气球,其实是相同的。大家可以分一个气球在中间和在两边的情况来看(比方说放中间,下标为,那就得到nums[i - 1] * nums[i] * nums[i + 1] 这么多硬币)。所以这个问题中,我们考虑放气球的方式似乎更好,可以从0开始不断的分布状态到最后的结果。不过这里其实你不这么做也可以,这只是单纯为了方便……

OK,那么这样的话,其实我们就不难思考了,设dp[i][j]表示“从第到第个气球,不包括第个。可以得到的最多的硬币数“,那么假如说我们要计算dp[i][j],应该怎么考虑?事实上就是考虑说,第一次是在哪里放的气球?假如说在坐标下放的,那么这个时候,除去你得到的那个硬币数以外,还需要加上之前的状态已经得到过的硬币。这里就是dp[i][mid]dp[mid][j]。所以实际上,把这个坐标枚举一遍,就可以得到答案。

因此状态转移方程为

好的,我们来看看代码

int solve(int left, int right) {if (left >= right - 1) {return 0;}if (rec[left][right] != -1) {return rec[left][right];}for (int i = left + 1; i < right; i++) {int sum = val[left] * val[i] * val[right];sum += solve(left, i) + solve(i, right);rec[left][right] = fmax(rec[left][right], sum);}return rec[left][right];
}int maxCoins(int* nums, int numsSize) {memset(rec, -1, sizeof(rec));val[0] = val[numsSize + 1] = 1;for (int i = 1; i <= numsSize; i++) {val[i] = nums[i - 1];}return solve(0, numsSize + 1);
}

可以看出,这个时候我们返回的解就是dp[0][n + 1],这里的就是气球的个数。

这个题可能有一个比较难想的点在于,这个为什么要定义为“第一次放入的气球的位置”。一个自然的想法当然是我们考虑“上一次”到“这一次”的状态,但这样问题也很明显,就是你放气球之后,同一个区间范围,同一组气球,却有了不同的下标。这种情况状态转移方程就写不出来了。而如果这么定义,无论你上一次放哪里,其实我都不影响我这个额外增益的计算。而最关键的点在于,这么设计之后,你依然可以枚举出所有的情况(毕竟你第一次在这个范围内放的气球的位置,也不可能超过对吧?),所以枚举完成之后,一定是可以找到最优解的。

还有一个细节是,我为什么要设置为区间为开区间,闭区间可不可以?左开右闭可不可以?当然也是可以的,一切以方便为主。但是要注意的是,其他的情况对应的状态转移方程会略有不同,这个我们交给读者思考。

Problem 7: Leetcode 72

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符 删除一个字符 替换一个字符

比方说word1 = "horse", word2 = "ros",那么对应的答案是3

这个题也是比较传统的编辑距离(Edit DIstance)的问题,也是hard中的高频题。这个题本身也是涉及NLP领域的一个基本算法。

这个题的难点在于,这里一共有6种操作,但需要经过多思考,把它化为3种情况。这3种情况分别为

给字符串A插入一个字符

给字符串B插入一个字符

给字符串A修改一个字符

剩下的3种,其实都和这三种等价了。比方说给字符串B插入一个字符,其实和给字符串A删除一个字符,作用是相同的。读者可以想想为什么,这本质上还是一个逆变换的思想。

那么好,这样的话,其实就可以设dp[i][j]表示word1的前个和word2的前个对应的编辑距离。那么一个好的消息是,不管是哪一种变换,都只会改变一个元素。所以按照序列问题朴素的思考方式,我们从前到后考虑一下

如果最近的一次是第一种(给字符串A插入一个字符),那么插入的字符就是第个,这个时候相当于可以回退到dp[i-1][j]。但是因为多了一步操作数,所以结果要加1。

如果是第二种,类似,结果是dp[i][j-1] + 1。如果是第三种,就要看word1[i] = word2[j]是否成立了。如果成立,其实就不需要修改的操作。但不成立,就要修改,所以这一个其实要根据实际情况去看。

总结一下,状态转移方程就是

对于边界条件,这里就是考虑有一个字符串没有字符,另一个有的情况。比方说word1有,word2是空的,那么只需要把word1也删除干净就可以了。这里相当于对应dp[i][0] = i, dp[0][j] = j

这里多说几句,其实在我们从前到后推理的过程中,其实就已经把所有的字符串的字符的所有可能的插入,删除和修改情况都已经考虑到了。如果对状态转移方程有疑问,就想一下这一点是否make sense就可以了。

好的,我们看一下核心代码。

for (int i = 0; i < n + 1; i++) {dp[i][0] = i;
}
for (int j = 0; j < m + 1; j++) {dp[0][j] = j;
}// 计算所有 DP 值
for (int i = 1; i < n + 1; i++) {for (int j = 1; j < m + 1; j++) {int left = dp[i - 1][j] + 1;int down = dp[i][j - 1] + 1;int left_down = dp[i - 1][j - 1];if (word1[i - 1] != word2[j - 1]) left_down += 1;dp[i][j] = min(left, min(down, left_down));}
}

这里的最后的返回结果就是dp[n][m],其中分别对应word1和word2的长度。

Problem 8: Leetcode 1027

给定一个整数数组 A,返回 A 中最长等差子序列的长度。

回想一下,A 的子序列是列表 A[i_1], A[i_2], ..., A[i_k] 其中 0 <= i_1 < i_2 < ... < i_k <= A.length - 1。并且如果 B[i+1] - B[i] ( 0 <= i < B.length - 1) 的值都相同,那么序列 B 是等差的。

比方说如果输入是[3,6,9,12],那么输出就是4,因为整个大的数组就是最长的等差子数列,长度为4。

这个问题的麻烦点可能在于,你仅仅使用一个参数(就是数的个数)来描述是不够的,因为你的公差不同,你对应的最长等差子数列可能就不一样。所以这里用了第二个参数就是公差。方法也是非常朴素的,最长上升子序列的做法。

现在假设dp[i][diff]表示公差为,考虑前个数,并且以第个数为最后一个数,对应的最长的等差数列的长度(这里我们要求第个数必须在等差数列中,也是为了递推方便,其实也是LIS里的trick)。那么这样的话,只要考虑之前的个数(),然后看从这个状态到的状态,中间应该怎么去变。

这个不难,因为假如说从到,如果我们从“以第个数为最后一个数“,去跳到”以第个数为最后一个数“,那么实际上就相当于,最后的两个数是相邻的两个数。那么必须要求公差为它们俩的差(否则就不是等差数列了)。因此状态转移方程就可以得到为

这里有一个细节,就是**不一定是正数**。所以实际的代码中,会把第二维每一次得到的差去加一个很大的正数。但是很明显,正数加的越大,空间占用越大,所以实际要加多少,取决于题目的条件。比方说如果告诉你,公差不会低于-10000,那么你加10000其实就够了。

好的,我们来看一下核心代码。

for (int i = 0; i < N; i++) {for (int j = 0; j < i; j++) {auto diff = A[i] - A[j];int offset = 10000;diff += offset;dp[i][diff] = max(dp[i][diff], dp[j][diff] + 1);res = max(dp[i][diff], res);}
}

这里的最终的结果就是代码里的res,和其他题不太一样的是,这里你不知道最长的等差数列的公差是多少,也不知道最后一个元素是哪一个,所以你只能把所有情况都考虑一下,然后取个max了。

Problem 9: Leetcode 1335

你需要制定一份 d 天的工作计划表。工作之间存在依赖,要想执行第 i 项工作,你必须完成全部 j 项工作( 0 <= j < i)。

你每天 至少 需要完成一项任务。工作计划的总难度是这 d 天每一天的难度之和,而一天的工作难度是当天应该完成工作的最大难度。

给你一个整数数组 jobDifficulty 和一个整数 d,分别代表工作难度和需要计划的天数。第 i 项工作的难度是 jobDifficulty[i]。

返回整个工作计划的 最小难度 。如果无法制定工作计划,则返回 -1 。

比方说如果我们有jobDifficulty = [6,5,4,3,2,1], d = 2,那么实际上,答案就是7。因为你可以先做前5个工作(cost是6),再做最后一个(cost是1)。

这个问题本质上其实也是一个序列决策问题,所以还是只需要看,之前的决策怎么影响之后的决策,或者反过来

现在我们考虑设dp[i][j]表示“到达第个工作,还需要完成天的任务的时候,所可以达到的最小的难度“(你当然也可以反过来说,考虑“已经完成天“任务,这个没有影响,只是状态转移方程会有些不同)。所以这样的话,我们不难发现的一点是,第个工作之后的几个工作,它对应的工作难度可以计算,并且安排之后,相当于就只差天的任务完成了。所以不难想,假如说我们安排一下完成2个任务(对应第个),那么工作难度就需要增加,并且状态就变成了dp[i + 2][j-1]。类似的,不难想的是,你当然也可以安排一下完成后面的所有任务,这中间的每一个情况,都是一种可能的状态转移,所以状态转移方程就是

这里注意的最大值,这是因为我们有要求每一天必须至少完成一个任务

那么对于它的边界条件,其实不难想,就是“最后只剩下1天任务要完成”的情况。这个时候,最后的所有的任务其实你都需要做完,那么对应的工作难度就变成了最后几个任务中,难度最大的那个。比方说你还剩下[9, 8, 7]这3个任务,那么答案就是9

好的,我们给出这个题目的核心代码。

for (let i = LEN - 1, curMax = 0; i >= 0; --i) {jobDifficulty[i] > curMax && (curMax = jobDifficulty[i]);dp[i][1] = curMax;
}for (int i = 2; i <= d; ++i) {for (int j = 0; j <= LEN - i; ++j) {int Max = 0;for (int k = j; k <= LEN - i; ++k) {jobDifficulty[k] > Max && (Max = jobDifficulty[k]);dp[j][i] = min(dp[j][i], dp[k + 1][i - 1] + Max);}}
}

最后只需要返回dp[0][d]就可以了。

小结

这一篇文章算是给这一个系列的难度试一试水。我们列举了一些相对来说比较高频,也比较困难的动态规划系列的题目。这些题目各有各的tricks,但是也并不是完全没有共通点。正如之前所说,刷题一定要抓住算法与数据结构的本质,而不是为了tricks而舍本逐末。而这一节中,我们已经可以看出,经典模型的一些思路和方法在这一篇文章中也有很多被使用。而同时我们也可以看出动态规划的关键步骤。而抓住这些步骤,了解基本模型之后,即使一些tricks相对比较难想到,其实也只需要强记就好,不会因为hard而耽误了medium和easy的通过率。

下一篇文章我们开始介绍其他的知识(具体要写啥,我目前还没有想好233),当然我必须承认动态规划还有相当多的难题没有写在笔记里,这一部分我们也会挑出一部分,放到后面的难题中,因为它们大部分都需要一些思维量,没有这两节的题目那么直接,因此可能会吓到大家hhh,还是循序渐进为好~

LeetCode动态规划系列教程(下)相关推荐

  1. LeetCode动态规划股票系列整理

    写在前面 股票感觉是LeetCode动态规划中系列最多的一类,交易次数不同,有冷冻期,含手续费,让买卖的最佳时机千奇百怪,但是只要掌握dp的方法,解决起来还是有套路可循的.依据dp的常规思想,股票问题 ...

  2. ASP.NET数据库访问系列教程01-概述篇 创建数据访问层(下)

    ASP.NET数据库访问系列教程 本教程深入探讨了基于ASP.NET 2.0技术的数据库访问方法和模式.这些介绍非常简明,并且提供了一步步的指导和大量的截屏. 该系列教程包括: 概述篇 基础报表 主/ ...

  3. RapidScada免费开源Scada组态软件系列教程7-Linux下系统的运行

    RapidScada免费开源Scada组态软件系列教程 系列文章目录 RapidScada免费开源Scada组态软件系列教程1-初识RapidScada RapidScada免费开源Scada组态软件 ...

  4. 植物大战僵尸变态辅助开发系列教程(E语言实现和VC6实现)(下)

    植物大战僵尸变态辅助开发系列教程(E语言实现和VC6实现)(下) 36.全屏秒杀 37.秒杀实现 37.PVZ聚怪 38.种植CALL的查找与调用 39.OpenProcess错误 40.错误:con ...

  5. ClickHouse系列教程七:centos下源码编译安装及报错解决

    ClickHouse系列教程: ClickHouse系列教程 参考上一篇博客: ClickHouse系列教程六:源码分析之Debug编译运行 先安装 gcc 8, g++ 8, cmake 3, ni ...

  6. linux中的shell有printf吗,Linux Shell系列教程之(八)Shell printf命令详解

    在上一篇:Linux Shell系列教程之(七)Shell输出这篇文章中,已经对Shell printf命令有了一个简略的介绍,本篇给大家详细介绍下Shell中的printf命令. 一.Shell p ...

  7. 八十一、Python | Leetcode 二叉树系列(下篇)

    @Author:Runsen @Date:2020/7/6 人生最重要的不是所站的位置,而是内心所朝的方向.只要我在每篇博文中写得自己体会,修炼身心:在每天的不断重复学习中,耐住寂寞,练就真功,不畏艰 ...

  8. 七十四、Python | Leetcode数字系列(下篇)

    @Author:Runsen @Date:2020/7/3 人生最重要的不是所站的位置,而是内心所朝的方向.只要我在每篇博文中写得自己体会,修炼身心:在每天的不断重复学习中,耐住寂寞,练就真功,不畏艰 ...

  9. 七十三、Python | Leetcode数字系列(上篇)

    @Author:Runsen @Date:2020/7/3 人生最重要的不是所站的位置,而是内心所朝的方向.只要我在每篇博文中写得自己体会,修炼身心:在每天的不断重复学习中,耐住寂寞,练就真功,不畏艰 ...

  10. 七十二、Python | Leetcode字符串系列(下篇)

    @Author:Runsen @Date:2020/7/3 人生最重要的不是所站的位置,而是内心所朝的方向.只要我在每篇博文中写得自己体会,修炼身心:在每天的不断重复学习中,耐住寂寞,练就真功,不畏艰 ...

最新文章

  1. C#程序调用cmd执行命令
  2. linux 压缩 解压 命令大全
  3. 树莓派应用实例2:环境温湿度测量
  4. Redis:内存满了的解决方案
  5. 本地网页服务器 跨域,nodejs搭建本地服务器轻松解决跨域问题
  6. iOS linker command failed with exit code 1 (use -v to see invocation)多种解决方案汇总
  7. LeetCode 961. 重复 N 次的元素
  8. Python | threading05 - 使用有界信号量,实现线程间同步
  9. 产业链人士:部分客户订单减少 联发科四季度营收可能环比下滑
  10. [self addChild:gameLayer z:1 tag:LayerTagGameLayer] z值含义
  11. 从一件数据清洗的小事说起
  12. 被机器学习遗忘的大洲:非洲正尝试建立自己的“NeurIPS会议”
  13. How to change the implementation (detour) of an externally declared function
  14. 已解决-MacBook pro/MacBook air上安装Windows10双系统教程
  15. 电脑上温度符号℃摄氏度怎么打?
  16. 关于uni-app中SegmentedControl 分段器不显示的问题
  17. 手机设备端口9006刷回9008救砖步骤
  18. xml学习(1)xml的几种文件格式
  19. 看PG10文档的笔记
  20. uname命令详解(图文)

热门文章

  1. ASP.NET MVC 对于视图引擎的优化
  2. 有趣的EditView为空时的抖动效果(用户名和密码)--第三方开源--ClearEditText
  3. 函数自执行-一元运算符
  4. 功能选中jquery实现全选反选功能
  5. 天气暖和了,我却感冒了
  6. 猜数字游戏的提示 (Master-Mind Hints, UVa 340)
  7. less知识点总结(一)
  8. 关于浏览器的兼容性问题
  9. 经过多次试验后第一个成功地实现 HTTPService 与 MXML 之间传递数据,ArrayCollection 与DataGrid 之间成功绑定...
  10. linux yum list、search、-y、install、update、remove、grouplist、groupinstall、groupremove