【动态规划】深入动态规划:连续子结构的算法剖析
文章目录
- 前言
- 例题
- 一、最大子数组和
- 二、环形子数组的最大和
- 三、 乘积最大子数组
- 四、乘积为正数的最长子数组
- 五、等差数列划分
- 六、最长湍流子数组
- 七、单词拆分
- 八、环绕字符串中唯一的子字符串
- 结语
前言
什么是是动态规划连续子数组、子串系列算法问题?
连续子数组问题通常聚焦于从给定数组中找出满足特定条件的连续片段。例如,最大子数组和问题,目标是在数组中找到一个连续子数组,使其元素之和最大。动态规划通过定义状态来解决这类问题。设dp[i]表示以第i个元素结尾的最大子数组和,状态转移方程为dp[i]=max(nums[i], dp[i - 1]+nums[i])。这意味着要么从当前元素重新开始计算子数组和,要么延续之前的子数组和并加上当前元素。通过这样逐步推导,就能高效找到整个数组中的最大子数组和。
子串系列算法问题同样常见。比如,最长无重复字符子串问题,要在字符串中找出不包含重复字符的最长连续子串。动态规划通过维护一个状态数组,记录以每个位置字符结尾的最长无重复子串长度。利用哈希表记录字符最近出现的位置,以此来判断当前字符是否在之前的子串中出现过。若未出现或距离足够远,状态转移较为简单;若出现且距离较近,则需要调整子串长度。
这些连续子数组、子串问题都能通过动态规划将复杂问题分解为一系列可管理的子问题,通过求解子问题并存储结果,避免了大量重复计算,极大地提升了算法效率,从而高效地解决实际场景中诸如数据统计分析、文本处理等领域的相关问题。
下面本文将通过例题带大家更加深入理解动态规划中的子结构问题!
例题
一、最大子数组和
- 题目链接:最大子数组和
- 题目描述:
给你⼀个整数数组 nums ,请你找出⼀个具有最大和的连续子数组(⼦数组最少包含⼀个元素),返回其最大和。 子数组 是数组中的⼀个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最⼤,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [5,4,-1,7,8]
输出:23
-
解法(动态规划):
算法思路: 状态表示: 对于线性 dp ,我们可以用「经验 + 题目要求」来定义状态表示:
i. 以某个位置为结尾。
ii. 以某个位置为起点, 这里我们选择比较常用的⽅式,以「某个位置为结尾」,结合「题目要求」,定义⼀个状态表示:
dp[i] 表示:以 i 位置元素为结尾的「所有⼦数组」中和的最大和。
状态转移方程:
dp[i] 的所有可能可以分为以下两种:
i. ⼦数组的长度为 1 :此时 dp[i] = nums[i] ;
ii. ⼦数组的长度大于 1 :此时 dp[i] 应该等于 以 i - 1 做结尾的「所有子数组」中和的最大值再加上 nums[i] ,也就是 dp[i - 1] + nums[i] 。 由于我们要的是「最大值」,因此应该是两种情况下的最⼤值,因此可得转移方程:
dp[i] = max(nums[i], dp[i - 1] + nums[i]) 。
初始化: 可以在最前⾯加上⼀个「辅助结点」,帮助我们初始化。使用这种技巧要注意两个点:
i. 辅助结点里面的值要「保证后续填表是正确的」;
ii. 「下标的映射关系」。
在本题中,最前面加上⼀个格子,并且让 dp[0] = 0 即可。
填表顺序:
根据「状态转移方程」易得,填表顺序为「从左往右」。
返回值: 状态表示为「以 i 为结尾的所有子数组」的最大值,但是最大子数组和的结尾我们是不确定的。 因此我们需要返回整个 dp 表中的最大值。 -
代码示例:
public int maxSubArray(int[] nums) {int n = nums.length;int[] dp = new int[n + 1];dp[0] = 0;int ret = Integer.MIN_VALUE;for (int i = 1; i <= n; i++) {dp[i] = Math.max(nums[i - 1], dp[i - 1] + nums[i - 1]);ret = Math.max(dp[i], ret);}return ret;}
二、环形子数组的最大和
- 题目链接:环形子数组的最大和
- 题目描述:
给定⼀个长度为 n 的环形整数数组 nums ,返回 nums 的非空子数组的最大可能和 。 环形数组意味着数组的末端将会与开头相连呈环状。形式上, nums[i] 的下⼀个元素是 nums[(i +1) % n] , nums[i] 的前⼀个元素是 nums[(i - 1 + n) % n] 。 子数组 最多只能包含固定缓冲区 nums 中的每个元素⼀次。形式上,对于子数组 nums[i], nums[i +1], …, nums[j] ,不存在 i <= k1, k2 <= j 其中 k1 % n == k2 % n 。
示例 1:
输入:nums = [1,-2,3,-2]
输出:3
解释:从子数组 [3] 得到最大和 3
示例 2:
输入:nums = [5,-3,5]
输出:10
解释:从子数组 [5,5] 得到最⼤和 5 + 5 = 10
-
解法(动态规划):
算法思路: 本题与「最大子数组和」的区别在于,考虑问题的时候不仅要分析「数组内的连续区域」,还要考虑「数组首尾相连」的⼀部分。结果的可能情况分为以下两种:
i. 结果在数组的内部,包括整个数组;
ii. 结果在数组首尾相连的⼀部分上。 其中,对于第⼀种情况,我们仅需按照「最大子数组和」的求法就可以得到结果,记为 fmax 。 对于第⼆种情况,我们可以分析⼀下:
i. 如果数组首尾相连的⼀部分是最大的数组和,那么数组中间就会空出来一部分;
ii. 因为数组的总和 sum 是不变的,那么中间连续的⼀部分的和⼀定是最小的; 因此,我们就可以得出⼀个结论,对于第二种情况的最大和,应该等于 sum - gmin ,其中gmin 表示数组内的「最小子数组和」。
两种情况下的最大值,就是我们要的结果。 但是,由于数组内有可能全部都是负数,第⼀种情况下的结果是数组内的最⼤值(是个负数),第 ⼆种情况下的 gmin == sum ,求的得结果就会是 0 。若直接求两者的最⼤值,就会是 0 。但 是实际的结果应该是数组内的最⼤值。对于这种情况,我们需要特殊判断⼀下。 由于「最⼤⼦数组和」的⽅法已经讲过,这⾥只提⼀下「最⼩⼦数组和」的求解过程,其实与「最 ⼤⼦数组和」的求法是⼀致的。⽤ f 表⽰最⼤和, g 表⽰最⼩和。
状态表⽰:
g[i] 表⽰:以 i 做结尾的「所有⼦数组」中和的最⼩值。
状态转移⽅程:
g[i] 的所有可能可以分为以下两种:
i. ⼦数组的⻓度为 1 :此时 g[i] = nums[i] ;
ii. ⼦数组的⻓度⼤于 1 :此时 g[i] 应该等于 以 i - 1 做结尾的「所有⼦数组」中和的 最⼩值再加上 nums[i] ,也就是 g[i - 1] + nums[i] 。 由于我们要的是最⼩⼦数组和,因此应该是两种情况下的最⼩值,因此可得转移⽅程:
g[i] = min(nums[i], g[i - 1] + nums[i]) 。
初始化: 可以在最前⾯加上⼀个辅助结点,帮助我们初始化。使⽤这种技巧要注意两个点:
i. 辅助结点⾥⾯的值要保证后续填表是正确的;
ii. 下标的映射关系。 在本题中,最前面加上⼀个格⼦,并且让 g[0] = 0 即可。
填表顺序: 根据状态转移⽅程易得,填表顺序为「从左往右」。
返回值:
a. 先找到 f 表里面的最⼤值 -> fmax ;
b. 找到 g 表⾥⾯的最⼩值 -> gmin ;
c. 统计所有元素的和 -> sum ;
b. 返回 sum == gmin ? fmax : max(fmax, sum - gmin) 。 -
代码示例:
public int maxSubarraySumCircular(int[] nums) {int n = nums.length;int[] f = new int[n+1];int[] g = new int[n+1];int sum = 0, fmax = Integer.MIN_VALUE, gmin = Integer.MAX_VALUE;for(int i = 1;i<=n;i++){int x = nums[i-1];f[i] = Math.max(x,x+f[i-1]);fmax = Math.max(fmax,f[i]);g[i] = Math.min(x,x+g[i-1]);gmin = Math.min(gmin,g[i]);sum+=x;}return sum == gmin ? fmax : Math.max(fmax, sum - gmin);}
三、 乘积最大子数组
- 题目链接:乘积最大子数组
- 题目描述:
给你⼀个整数数组 nums ,请你找出数组中乘积最⼤的非空连续子数组(该⼦数组中至少包含⼀个 数字),并返回该⼦数组所对应的乘积。 测试⽤例的答案是⼀个 32-位 整数。 ⼦数组 是数组的连续⼦序列。
示例 1:
输入: nums = [2,3,-2,4]
输出: 6
解释:子数组 [2,3] 有最⼤乘积 6。
示例 2:
输入: nums = [-2,0,-1]
输出: 0
-
解法(动态规划):
算法思路:
这道题与「最⼤⼦数组和」⾮常相似,我们可以效仿着定义⼀下状态表⽰以及状态转移:
i. dp[i] 表⽰以 i 为结尾的所有⼦数组的最⼤乘积,
ii. dp[i] = max(nums[i], dp[i - 1] * nums[i]) ; 由于正负号的存在,我们很容易就可以得到,这样求 dp[i] 的值是不正确的。因为 dp[i -
1] 的信息并不能让我们得到 dp[i] 的正确值。⽐如数组 [-2, 5, -2] ,⽤上述状态转移得 到的 dp数组为 [-2, 5, -2] ,最⼤乘积为 5 。但是实际上的最⼤乘积应该是所有数相乘,结 果为 20 。 究其原因,就是因为我们在求 dp[2] 的时候,因为 nums[2] 是⼀个负数,因此我们需要的是 「 i - 1 位置结尾的最⼩的乘积 (-10) 」,这样⼀个负数乘以「最⼩值」,才会得到真实的 最⼤值。 因此,我们不仅需要⼀个「乘积最⼤值的 dp 表」,还需要⼀个「乘积最⼩值的 dp 表」。
状态表示:
f[i] 表⽰:以 i 结尾的所有⼦数组的最大乘积,
g[i] 表⽰:以 i 结尾的所有⼦数组的最小乘积。
状态转移⽅程: 遍历每⼀个位置的时候,我们要同步更新两个 dp 数组的值。 对于 f[i] ,也就是「以 i 为结尾的所有⼦数组的最⼤乘积」,对于所有⼦数组,可以分为下面三种形式:
i. 子数组的长度为 1 ,也就是 nums[i] ;
ii. ⼦数组的长度⼤于 1 ,但 nums[i] > 0 ,此时需要的是 i - 1 为结尾的所有子数组 的最大乘积 f[i - 1] ,再乘上 nums[i] ,也就是 nums[i] * f[i - 1] ;
iii. 子数组的⻓度大于 1 ,但 nums[i] < 0 ,此时需要的是 i - 1 为结尾的所有子数组 的最小乘积 g[i - 1] ,再乘上 nums[i] ,也就是 nums[i] * g[i - 1] ; (如果 nums[i] = 0 ,所有⼦数组的乘积均为 0 ,三种情况其实都包含了) 综上所述, f[i] = max(nums[i], max(nums[i] * f[i - 1], nums[i] * g[i - 1]) )。
对于 g[i] ,也就是「以 i 为结尾的所有子数组的最小乘积」,对于所有子数组,可以分为下面三种形式:
i. ⼦数组的长度为 1 ,也就是 nums[i] ;
ii. ⼦数组的长度大于 1 ,但 nums[i] > 0 ,此时需要的是 i - 1 为结尾的所有⼦数组 的最小乘积 g[i - 1] ,再乘上 nums[i] ,也就是 nums[i] * g[i - 1] ;
iii. ⼦数组的⻓度⼤于 1 ,但 nums[i] < 0 ,此时需要的是 i - 1 为结尾的所有⼦数组 的最⼤乘积 f[i - 1] ,再乘上 nums[i] ,也就是 nums[i] * f[i - 1] ;
综上所述, g[i] = min(nums[i], min(nums[i] * f[i - 1], nums[i] * g[i - 1])) 。 (如果 nums[i] = 0 ,所有⼦数组的乘积均为 0 ,三种情况其实都包含了)
初始化: 可以在最前面加上⼀个辅助结点,帮助我们初始化。使用这种技巧要注意两个点:
i. 辅助结点里面的值要保证后续填表是正确的;
ii. 下标的映射关系。 在本题中,最前⾯加上⼀个格⼦,并且让 f[0] = g[0] = 1 即可。
填表顺序: 根据状态转移方程易得,填表顺序为「从左往右,两个表⼀起填」。
返回值: 返回 f 表中的最大值。 -
代码示例:
public int maxProduct(int[] nums) {int n = nums.length;int[] f = new int[n+1];int[] g = new int[n+1];int ret = Integer.MIN_VALUE;f[0] = g[0] = 1;for(int i = 1;i<=n;i++){int x = nums[i - 1], y = f[i - 1] * nums[i - 1], z = g[i - 1] * nums[i - 1];f[i] = Math.max(x, Math.max(y, z));g[i] = Math.min(x, Math.min(y, z));ret = Math.max(ret, f[i]);}return ret; }
四、乘积为正数的最长子数组
- 题目链接:乘积为正数的最长子数组
- 题目描述:
给你⼀个整数数组 nums ,请你求出乘积为正数的最长子数组的长度。
⼀个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。 请你返回乘积为正数的最长子数组长度。
示例 1:
输入:nums = [1,-2,-3,4]
输出:4
解释:数组本身乘积就是正数,值为 24 。
示例 2:
输入:nums = [0,1,-2,-3,-4]
输出:3
解释:最长乘积为正数的⼦数组为 [1,-2,-3] ,乘积为 6 。 注意,我们不能把 0 也包括到子数组中,因为这样乘积为 0 ,不是正数。
示例 3:
输⼊:nums = [-1,-2,-3,0,1]
输出:2
解释:乘积为正数的最⻓⼦数组是 [-1,-2] 或者 [-2,-3] 。
-
解法(动态规划):
算法思路:
继续效仿「最大子数组和」中的状态表示,尝试解决这个问题。 状态表示: dp[i] 表示「所有以 i 结尾的子数组,乘积为正数的最长子数组的长度」。
思考状态转移:对于 i 位置上的 nums[i] ,我们可以分三种情况讨论:
i. 如果 nums[i] = 0 ,那么所有以 i 为结尾的⼦数组的乘积都不可能是正数,此时dp[i] = 0 ;
ii. 如果 nums[i] > 0 ,那么直接找到 dp[i - 1] 的值(这里请再读⼀遍 dp[i - 1] 代表的意义,并且考虑如果 dp[i - 1] 的结值是 0 的话,影不影响结果),然后加一即可,此时 dp[i] = dp[i - 1] + 1 ;
iii. 如果 nums[i] < 0 ,这时候你该蛋疼了,因为在现有的条件下,你根本没办法得到此时 的最长长度。因为乘法是存在「负负得正」的,单单靠⼀个 dp[i - 1] ,我们无法推导 出 dp[i] 的值。
但是,如果我们知道「以 i - 1 为结尾的所有子数组,乘积为负数的最长子数组的长度」g[i - 1] ,那么此时的 dp[i] 是不是就等于 g[i - 1] + 1 呢?
通过上⾯的分析,我们可以得出,需要两个 dp 表,才能推导出最终的结果。不仅需要⼀个「乘积 为正数的最⻓⼦数组」,还需要⼀个「乘积为负数的最长子数组」。
状态表示:
f[i] 表示:以 i 结尾的所有⼦数组中,乘积为「正数」的最长子数组的⻓度;
g[i] 表示:以 i 结尾的所有⼦数组中,乘积为「负数」的最长子数组的⻓度。
状态转移方程: 遍历每⼀个位置的时候,我们要同步更新两个 dp 数组的值。 对于 f[i] ,也就是以 i 为结尾的乘积为「正数」的最长子数组,根据 nums[i] 的值,可以 分为三种情况:
i. nums[i] = 0 时,所有以 i 为结尾的子数组的乘积都不可能是正数,此时 f[i] = 0 ;
ii. nums[i] > 0 时,那么直接找到 f[i - 1] 的值(这里请再读⼀遍 f[i - 1] 代表 的意义,并且考虑如果 f[i - 1] 的结值是 0 的话,影不影响结果),然后加一即可, 此时 f[i] = f[i - 1] + 1 ;
iii. nums[i] < 0 时,此时我们要看 g[i - 1] 的值(这里请再读⼀遍 g[i - 1] 代 表的意义。因为负负得正,如果我们知道以 i - 1 为结尾的乘积为负数的最长子数组的长度,加上 1 即可),根据 g[i - 1] 的值,又要分两种情况:
g[i - 1] = 0 ,说明以 i - 1 为结尾的乘积为负数的最长子数组是不存在的,又因为 nums[i] < 0 ,所以以 i 结尾的乘积为正数的最⻓⼦数组也是不存在的,此 时 f[i] = 0 ;
g[i - 1] != 0 ,说明以 i - 1 为结尾的乘积为负数的最长子数组是存在的,又因为 nums[i] < 0 ,所以以 i 结尾的乘积为正数的最长子数组就等于 g[i - 1] + 1 ; 综上所述, nums[i] < 0 时, f[i] = g[i - 1] == 0 ? 0 : g[i - 1] + 1;
对于 g[i] ,也就是以 i 为结尾的乘积为「负数」的最⻓⼦数组,根据 nums[i] 的值,可以分为三种情况:
i. nums[i] = 0 时,所有以 i 为结尾的子数组的乘积都不可能是负数,此时 g[i] = 0 ;
ii. nums[i] < 0 时,那么直接找到 f[i - 1] 的值(这里请再读⼀遍 f[i - 1] 代表 的意义,并且考虑如果 f[i - 1] 的结值是 0 的话,影不影响结果),然后加⼀即可 (因为正数 * 负数 = 负数),此时 g[i] = f[i - 1] + 1 ;
iii. nums[i] > 0 时,此时我们要看 g[i - 1] 的值(这里请再读⼀遍 g[i - 1] 代 表的意义。因为正数 * 负数 = 负数),根据 g[i - 1] 的值,又要分两种情况:
g[i - 1] = 0 ,说明以 i - 1 为结尾的乘积为负数的最⻓⼦数组是不存在的,又因为 nums[i] > 0 ,所以以 i 结尾的乘积为负数的最⻓⼦数组也是不存在的,此 时 f[i] = 0 ;
g[i - 1] != 0 ,说明以 i - 1 为结尾的乘积为负数的最⻓⼦数组是存在的,又因为 nums[i] > 0 ,所以以 i 结尾的乘积为正数的最⻓⼦数组就等于 g[i -
1] + 1 ;
综上所述, nums[i] > 0 时, g[i] = g[i - 1] == 0 ? 0 : g[i - 1] + 1 ; 这里的推导比较绕,因为不断的出现「正数和负数」的分情况讨论,我们只需根据下⾯的规则,严 格找到此状态下需要的 dp 数组即可:
i. 正数 * 正数 = 正数
ii. 负数 * 负数 = 正数
iii. 负数 * 正数 = 正数 * 负数 = 负数
初始化: 可以在最前⾯加上⼀个「辅助结点」,帮助我们初始化。使⽤这种技巧要注意两个点:
i. 辅助结点里面的值要「保证后续填表是正确的」;
ii. 「下标的映射关系」。
在本题中,最前面加上⼀个格子,并且让 f[0] = g[0] = 0 即可。
填表顺序: 根据「状态转移⽅程」易得,填表顺序为「从左往右,两个表⼀起填」。
返回值: 根据「状态表示」,我们要返回 f 表中的最⼤值。 -
代码示例:
public int getMaxLen(int[] nums) {int n = nums.length;int[] f =new int[n+1];int[] g = new int[n+1];int ret = Integer.MIN_VALUE;for(int i = 1;i<=n;i++){if(nums[i-1]>0){f[i] = f[i-1] + 1;g[i] = g[i-1]==0?0:g[i-1] + 1;}else if(nums[i-1]<0){f[i] = g[i-1]==0?0:g[i-1]+1;g[i] = f[i-1] + 1;}ret = Math.max(ret,f[i]);}return ret;}
五、等差数列划分
- 题目链接:等差数列划分
- 题目描述:
如果⼀个数列 ⾄少有三个元素 ,并且任意两个相邻元素之差相同,则称该数列为等差数列。 • 例如,[1,3,5,7,9]、[7,7,7,7] 和 [3,-1,-5,-9] 都是等差数列。 给你⼀个整数数组 nums ,返回数组 nums 中所有为等差数组的子数组个数。子数组 是数组中的⼀个连续序列。
示例 1:
输入:nums = [1,2,3,4]
输出:3
解释:nums 中有三个⼦等差数组:[1, 2, 3]、[2, 3, 4] 和 [1,2,3,4] ⾃⾝。
示例 2:
输入:nums = [1]
输出:0
-
解法(动态规划): 算法思路:
状态表⽰: 由于我们的研究对象是「⼀段连续的区间」,如果我们状态表⽰定义成 [0, i] 区间内⼀共有多 少等差数列,那么我们在分析 dp[i] 的状态转移时,会无从下手,因为我们不清楚前面那么多 的「等差数列都在什么位置」。所以说,我们定义的状态表⽰必须让等差数列「有迹可循」,让状 态转移的时候能找到「大部队」。因此,我们可以「固定死等差数列的结尾」,定义下面的状态表示:
dp[i] 表示必须「以 i 位置的元素为结尾」的等差数列有多少种。
状态转移方程: 我们需要了解⼀下等差数列的性质:如果 a b c 三个数成等差数列,这时候来了⼀个 d ,其 中 b c d 也能构成⼀个等差数列,那么 a b c d 四个数能够成等差序列吗?答案是:显然 的。因为他们之间相邻两个元素之间的差值都是⼀样的。有了这个理解,我们就可以转而分析我们的状态转移方程了。 对于 dp[i] 位置的元素 nums[i] ,会与前面的两个元素有下面两种情况:
i. nums[i - 2], nums[i - 1], nums[i] 三个元素不能构成等差数列:那么以nums[i] 为结尾的等差数列就不存在,此时 dp[i] = 0 ;
ii. nums[i - 2], nums[i - 1], nums[i] 三个元素可以构成等差数列:那么以nums[i - 1] 为结尾的所有等差数列后⾯填上⼀个 nums[i] 也是⼀个等差数列,此时dp[i] = dp[i - 1] 。但是,因为 nums[i - 2], nums[i - 1], nums[i] 三 者⼜能构成⼀个新的等差数列,因此要在之前的基础上再添上⼀个等差数列,于是dp[i] = dp[i - 1] + 1 。 综上所述:状态转移⽅程为: 当: nums[i - 2] + nums[i] != 2 * nums[i - 1] 时, dp[i] = 0当: nums[i - 2] + nums[i] == 2 * nums[i - 1] 时, dp[i] = 1 + dp[i - 1]
初始化: 由于需要用到前两个位置的元素,但是前两个位置的元素又无法构成等差数列,因此 dp[0] = dp[1] = 0 。
填表顺序: 毫无疑问是「从左往右」。
返回值: 因为我们要的是所有的等差数列的个数,因此需要返回整个 dp 表里面的元素之和。 -
代码示例:
public int numberOfArithmeticSlices(int[] nums) {int n = nums.length;if(n<2) return 0;int[] dp = new int[n];dp[0] = 0;dp[1] = 0;for(int i = 2;i<n;i++){if(nums[i]-nums[i-1] == nums[i-1]-nums[i-2]){dp[i] = dp[i-1] + 1;}else{dp[i] = 0;}}int sum = 0;for(int x :dp){sum+=x;}return sum;}
六、最长湍流子数组
- 题目链接:最长湍流子数组
- 题目描述:
给定一个整数数组 arr ,返回 arr 的 最大湍流子数组的长度 。 如果比较符号在子数组中的每个相邻元素对之间翻转,则该⼦数组是湍流子数组 。 更正式地来说,当 arr 的子数组 A[i], A[i+1], …, A[j] 满足仅满足下列条件时,我们称其为湍流子数 组:若 i <= k < j :当 k 为奇数时, A[k] > A[k+1],且 当 k 为偶数时,A[k] < A[k+1]; 或 若 i <= k < j :当 k 为偶数时,A[k] > A[k+1] ,且当k 为奇数时, A[k] < A[k+1]。
示例 1:
输入:arr = [9,4,2,10,7,8,8,1,9]
输出:5
解释:arr[1] > arr[2] < arr[3] > arr[4] < arr[5]
示例 2:
输入:arr = [4,8,12,16]
输出:2
示例 3:
输入:arr = [100]
输出:1
提示: 1 <= arr.length <= 4 * 10^4
0 <= arr[i] <= 10^9
- 解法(动态规划):
算法思路:
状态表⽰: 我们先尝试定义状态表⽰为:
dp[i] 表⽰「以 i 位置为结尾的最⻓湍流数组的⻓度」。
但是,问题来了,如果状态表⽰这样定义的话,以 i 位置为结尾的最⻓湍流数组的⻓度我们没法从之前的状态推导出来。因为我们不知道前⼀个最长湍流数组的结尾处是递增的,还是递减的。因此,我们需要状态表⽰能表示多⼀点的信息:要能让我们知道这⼀个最长湍流数组的结尾是「递 增」的还是「递减」的。 因此需要两个dp 表:
f[i] 表示:以 i 位置元素为结尾的所有子数组中,最后呈现「上升状态」下的最长湍流数组的长度;
g[i] 表示:以 i 位置元素为结尾的所有子数组中,最后呈现「下降状态」下的最长湍流数组的长度。
状态转移方程: 对于 i 位置的元素 arr[i] ,有下⾯面种情况:
i. arr[i] > arr[i - 1] :如果 i 位置的元素比i - 1 位置的元素大,说明接下来 应该去找 i -1 位置结尾,并且 i - 1 位置元素⽐前⼀个元素⼩的序列,那就是 g[i
- 1] 。更新 f[i] 位置的值: f[i] = g[i - 1] + 1 ;
ii. arr[i] < arr[i - 1] :如果 i 位置的元素比 i - 1 位置的元素小,说明接下来 应该去找 i - 1 位置结尾,并且 i - 1 位置元素比前⼀个元素大的序列,那就是f[i - 1] 。更新 g[i] 位置的值: g[i] = f[i - 1] + 1 ;
iii. arr[i] == arr[i - 1] :不构成湍流数组。
初始化: 所有的元素「单独」都能构成⼀个湍流数组,因此可以将 dp 表内所有元素初始化为 1 。 由于用到前面的状态,因此我们循环的时候从第⼆个位置开始即可。
填表顺序: 毫无疑问是「从左往右,两个表⼀起填」。
返回值: 应该返回「两个 dp 表里面的最大值」,我们可以在填表的时候,顺便更新⼀个最大值。
- 代码示例:
public int maxTurbulenceSize(int[] arr) {int n = arr.length;int[] f = new int[n];int[] g = new int[n];f[0] = 1;g[0] = 1;int ret = 1;for(int i = 0;i<n;i++){f[i] = 1;g[i] = 1;}for(int i = 1;i<n;i++){if(arr[i]>arr[i-1]){f[i] = g[i-1]+1;}else if(arr[i]<arr[i-1]){g[i] = f[i-1] + 1;}ret = Math.max(ret,Math.max(f[i],g[i]));}return ret;}
七、单词拆分
- 题目链接:单词拆分
- 题目描述:
给你⼀个字符串 s 和⼀个字符串列表 wordDict 作为字典。请你判断是否可以利⽤字典中出现的单词
拼接出 s 。 注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例 1:
输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释:返回 true 因为 “leetcode” 可以由 “leet” 和 “code” 拼接成。
示例 2:
输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
输出: true
解释:返回 true 因为 “applepenapple” 可以由 “apple” “pen” “apple” 拼接成。 注意,你可以重复使用字典中的单词。
示例 3:
输入: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出: false
-
解法(动态规划):
算法思路:
状态表示: 对于线性 dp ,我们可以⽤「经验 + 题目要求」来定义状态表示:
i. 以某个位置为结尾
ii. 以某个位置为起点,这里我们选择比较常用的方式,以某个位置为结尾,结合题⽬要求,定义⼀个状态表示:dp[i] 表示: [0, i] 区间内的字符串,能否被字典中的单词拼接而成。
状态转移⽅程: 对于 dp[i] ,为了确定当前的字符串能否由字典里面的单词构成,根据最后⼀个单词的起始位 置 j ,我们可以将其分解为前后两部分:
i. 前面⼀部分 [0, j - 1] 区间的字符串;
ii. 后面⼀部分 [j, i] 区间的字符串。 其中前面部分我们可以在 dp[j - 1] 中找到答案,后⾯部分的子串可以在字典里面找到。 因此,我们得出⼀个结论:当我们在从 0 ~ i 枚举 j 的时候,只要 dp[j - 1] = true并且后面部分的⼦串 s.substr(j, i - j + 1) 能够在字典中找到,那么 dp[i] = true 。
初始化: 可以在最前面加上⼀个「辅助结点」,帮助我们初始化。使用这种技巧要注意两个点:
i. 辅助结点里面的值要「保证后续填表是正确的」;
ii. 「下标的映射关系」。
在本题中,最前面加上⼀个格⼦,并且让 dp[0] = true ,可以理解为空串能够拼接而成。 其中为了⽅便处理下标的映射关系,我们可以将字符串前⾯加上⼀个占位符 s = ’ ’ + s ,这 样就没有下标的映射关系的问题了,同时还能处理「空串」的情况。
填表顺序: 显而易见,填表顺序「从左往右」。
返回值:由「状态表⽰」可得:返回 dp[n] 位置的布尔值 -
代码示例:
public boolean wordBreak(String s, List<String> wordDict) {Set<String> hash =new HashSet(wordDict);int n = s.length();boolean[] dp = new boolean[n+1];dp[0] =true;s = " "+ s;for(int i = 1;i<=n;i++){for(int j = i;j>=1;j--){if(dp[j-1]&&hash.contains(s.substring(j , i+1))){dp[i] = true;break;}}}return dp[n];}
八、环绕字符串中唯一的子字符串
- 题目链接:环绕字符串中唯一的子字符串
- 题目描述:
定义字符串 base 为⼀个 “abcdefghijklmnopqrstuvwxyz” 无限环绕的字符串,所以 base 看起来是 这样的:“…zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd…”. 给你⼀个字符串 s ,请你统计并返回 s 中有多少 不同⾮空⼦串 也在 base 中出现。
示例 1:
输入:s = “a”
输出:1
解释:字符串 s 的⼦字符串 “a” 在 base 中出现。
示例 2:
输入:s = “cac”
输出:2 解释:字符串 s 有两个⼦字符串 (“a”, “c”) 在 base 中出现。
示例 3:
输入:s = “zab”
输出:6 解释:字符串 s 有六个⼦字符串 (“z”, “a”, “b”, “za”, “ab”, and “zab”) 在 base 中出现。
提示:1 <= s.length <= 105
s 由小写英文字母组成
-
解法(动态规划):
算法思路:
状态表示: 对于线性 dp ,我们可以⽤「经验 + 题目要求」来定义状态表示:
i. 以某个位置为结尾
ii. 以某个位置为起点,这里我们选择比较常用的方式,以某个位置为结尾,结合题目要求,定义⼀个状态表示:
dp[i] 表示:以 i 位置的元素为结尾的所有⼦串里面,有多少个在 base 中出现过。
状态转移方程: 对于 dp[i] ,我们可以根据⼦串的「⻓度」划分为两类:
i. 子串的长度等于 1 :此时这⼀个字符会出现在 base 中;
ii. 子串的长度大于 1 :如果 i 位置的字符和 i - 1 位置上的字符组合后,出现在 base中的话,那么 dp[i - 1] 里面的所有子串后面填上⼀个 s[i] 依旧在 base 中出 现。因此 dp[i] = dp[i - 1] 。 综上, dp[i] = 1 + dp[i - 1] ,其中 dp[i - 1] 是否加上需要先做⼀下判断。
初始化: 可以根据「实际情况」,将表⾥⾯的值都初始化为 1 。
填表顺序: 显⽽易⻅,填表顺序「从左往右」。
返回值:
这里不能直接返回 dp 表里面的和,因为会有重复的结果。在返回之前,我们需要先「去重」:
i. 相同字符结尾的 dp 值,我们仅需保留「最⼤」的即可,其余 dp 值对应的⼦串都可以在 最大的里面找到;
ii. 可以创建⼀个大小为 26 的数组,统计所有字符结尾的最大dp 值。 最后返回「数组中所有元素的和」即可。 -
代码示例:
public int findSubstringInWraproundString(String ss) {int n = ss.length();char[] s = ss.toCharArray();// 1. 利⽤ dp 得到每⼀个位置为结尾的最⻓连续数组的⻓度int[] dp = new int[n];for (int i = 0; i < n; i++) dp[i] = 1;for (int i = 1; i < n; i++)if (s[i - 1] + 1 == s[i] || (s[i - 1] == 'z' && s[i] == 'a'))dp[i] += dp[i - 1];// 2. 去重int[] hash = new int[26];for (int i = 0; i < n; i++)hash[s[i] - 'a'] = Math.max(hash[s[i] - 'a'], dp[i]);// 3. 返回结果int sum = 0;for (int x : hash) sum += x;return sum;}
结语
本文到这里就结束了,主要通过几道动态规划(连续子结构)的题目,带大家深入了解了这类算法题目的处理方式以及如何建立状态转移方程。
以上就是本文全部内容,感谢各位能够看到最后,如有问题,欢迎各位大佬在评论区指正,希望大家可以有所收获!创作不易,希望大家多多支持!
最后,大家再见!祝好!我们下期见!
相关文章:
【动态规划】深入动态规划:连续子结构的算法剖析
文章目录 前言例题一、最大子数组和二、环形子数组的最大和三、 乘积最大子数组四、乘积为正数的最长子数组五、等差数列划分六、最长湍流子数组七、单词拆分八、环绕字符串中唯一的子字符串 结语 前言 什么是是动态规划连续子数组、子串系列算法问题? 连续子数组问题通常聚焦…...
结肠镜3D视频数据集-C3VD论文中文版
文章目录 标题作者摘要一、介绍1.1. 相关工作1.1.1. 内镜重建数据集1.1.2. 注册真实和虚拟内窥镜图像1.1.3. 2D-3D注册1.2. 贡献 二、方法2.1. 幻影模型生产2.2. 数据采集2.3. 注册流程概述2.3.1. 数据预处理2.3.2. 目标深度估计2.3.3. 渲染深度帧2.3.4. 边缘损失和优化 2.4. 模…...
封装自己的api签名sdk
api平台接口调用,需要通过签名去核对是不是有效的用户,,一般会给两个key,acceeKey 和 secretKey,第一个相当于用户名,第二个相当于密钥,,,前端通过一定的算法,࿰…...
ASP.NET Core Web API 中 HTTP状态码的分类及对应的返回方法
文章目录 前言一、HTTP状态码分类及常用方法二、具体返回方法示例1) 2xx 成功类2)4xx 客户端错误3)5xx 服务器错误4)其他特殊状态码 三、高级返回方式1)使用 IActionResult 与 ActionResult<T>2)统一…...
函数和模式化——python
一、模块和包 将一段代码保存为应该扩展名为.py 的文件,该文件就是模块。Python中的模块分为三种,分别为:内置模块、第三方模块和自定义模块。 内置模块和第三方模块又称为库内置模块,有 python 解释器自带,不用单独安…...
LeetCode 1817 查找用户活跃分钟数
深入剖析 LeetCode 用户活跃分钟数统计问题 一、题目详情 给定用户在 LeetCode 的操作日志,日志以二维整数数组logs表示,其中每个logs[i][IDi, timei],意味着 ID 为IDi的用户在timei分钟时执行了某个操作。多个用户能够同时执行操作&#x…...
matlab从pytorch中导入LeNet-5网络框架
文章目录 一、Pytorch的LeNet-5网络准备二、保存用于导入matlab的model三、导入matlab四、用matlab训练这个导入的网络 这里演示从pytorch的LeNet-5网络导入到matlab中进行训练用。 一、Pytorch的LeNet-5网络准备 根据LeNet-5的结构图,我们可以写如下结构 import…...
网络:华为HCIA学习笔记:ICMP协议
ICMP(Internet Control Message Protocol)Internet控制消息协议 前言ICMPICMP重定向ICMP差错监测ICMP错误报告ICMP数据包格式ICMP消息类型和编码类型ICMP应用-PingICMP应用-Tracert 总结 前言 Internet控制消息协议ICMP (Internet Control Message Prot…...
导出为更清楚/高质量的图片(.png) | 在Word里面的visio图)
Visio | 将(.vsdx)导出为更清楚/高质量的图片(.png) | 在Word里面的visio图
此时大家在用Visio画完图直接复制到word里面后,如果后期需要重新保存高清图片,但是此时图片在word,是不是很多人会选择直接crtlA截图复制,这样出来的图又不清晰又小,完全不符合你导的审美,接下来跟着我&…...
算法设计学习8
实验目的及要求: 通过深入学习树(Tree)和二叉树(Binary Tree)这两种重要的数据结构,掌握它们的基本概念、性质和操作,提高对树形结构的理解和应用能力。通过本实验,学生将深化对树和…...
Dive into Deep Learning - 2.4. Calculus (微积分)
Dive into Deep Learning - 2.4. Calculus {微积分} 1. Derivatives and Differentiation (导数和微分)1.1. Visualization Utilities 2. Chain Rule (链式法则)3. DiscussionReferences 2.4. Calculus https://d2l.ai/chapter_preliminaries/calculus.html For a long time, …...
kotlin中const 和val的区别
在 Kotlin 中,const 和 val 都是用来声明常量的,但它们的使用场景和功能有所不同: 1. val: val 用于声明只读变量,也就是不可修改的变量(类似于 Java 中的 final 变量)。它可以是任何类型,包括…...
Webpack中loader的作用。
文章目录 前言1. 处理样式文件2. 处理 JavaScript 文件3. 处理其他文件总结 前言 在 Webpack 中,Loader 是用于对模块的源代码进行转换的工具,它能够将不同类型的文件(如 CSS、图片、字体、TypeScript 等)转换为有效的 JavaScrip…...
C++ 极简常用内容
C 极简常用内容 1. 类与对象 定义:封装数据(成员变量)和行为(成员函数)的自定义类型。 Demo: class Car { public:string brand;void drive() { cout << brand << " is moving." …...
如何在windows 环境、且没有显卡的情况下用python跑通从ModelScope下载的大模型的调用
文章目录 背景介绍源代码:安装调试过程1.设置第三方镜像源2.预先安装:3.在python中创建代码:4.最终修改程序,将device_map从“cuda”改成“auto”,大模型调用1.5B(1___5B)的5.最终跑出结果解释:示例&#x…...
MINIQMT学习课程Day10
开始获取股票数据课程的学习: 获取qmt账号的持仓情况后,我们进入下一步,如何获得当前账号的委托状况 还是之前的步骤,打开qmt,选择独立交易, 之后使用pycharm,编写py文件 导入包:…...
如何彻底关闭Windows 10中的Xbox游戏栏
一、打工人的困扰:不速之客“Game Bar” 在日常工作中,许多使用Windows 10的用户常常被一个不起眼却频频打扰的系统功能困扰,那就是“Xbox游戏栏”(Game Bar)。当你正在专注处理紧急的Excel表格或准备PPT汇报…...
26考研资料分享考研资料合集 百度网盘(仅供参考学习)
考研资料分享考研资料合集 百度网盘(仅供参考学习) 通过网盘分享的文件:2026考研英语数学政治最新等3个文件 链接1: https://pan.baidu.com/s/1JXBI9ROng4KAWHoaUHpkug?pwdjydb 提取码: jydb 链接2:https://pan.baidu.com/s/1a…...
59.基于ssm和vue学生考试成绩管理系统
目录 1.系统的受众说明 2.系统关键技术 2.1 java技术 2.2 MYSQL数据库 2.3 B/S结构 3.系统分析 3.1 可行性分析 3.1.1 技术可行性 3.1.2经济可行性 3.2 系统性能分析 3.3 系统功能分析 3.5系统流程分析 3.5.1登录流程 3.5.2注册流程 3.5.3添加信息流程 3.5.4删…...
常见的ETL工具分类整理
一、开源ETL工具 Kettle(Pentaho Data Integration)--Spoon 设计及架构:面向数据仓库建模的传统ETL工具。使用方式:C/S客户端模式,开发和生产环境需要独立部署,任务编写、调试、修改都在本地。底层架构…...
【leetcode100】数组中的第K个最大元素
1、题目描述 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。 请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1: 输入: [3,2,…...
markdown语法学习
三化markdown语法 研究对象系统化全局化结构化markdown语法富文本字体样式*斜体*,主题样式#,表格样式||,代码块样式,待办样式- [ ]样式之间互相协作,互不冲突 待办 斜体 加粗 标题 删除线 public class{ //代码块 …...
Linux_4
开始学习ssh工具 在做开发的时候,肯定不止一台服务器,那么假设每台服务器都是Linux服务器,要在服务器上操作就需要登入终端,即Terminal。ssh的作用就是可以通过一个服务器登陆上其他的服务器。 登陆到哪个服务器看到的就是哪个服务器的终端terminal。 ssh登陆 ssh user@…...
Netty——连接超时 与 断开重连
文章目录 1. 处理连接超时和断开重连的原因2. 处理连接超时和断开重连的方法2.1 处理连接超时2.1.1 步骤一:配置连接超时时间2.1.2 步骤二:监听连接结果 2.2 处理断开重连2.2.1 步骤一:监听连接断开事件2.2.2 步骤二:实现重连逻辑…...
linux 进程/线程设置核亲和性
1,线程绑定内核 #include <pthread.h> #include <stdio.h> #include <stdlib.h> // 定义一个函数,用于设置线程的CPU亲和性 void set_thread_affinity(pthread_t thread, int cpu_id) { cpu_set_t cpuset; int s; // 清空CPU集…...
前端页面鼠标移动监控(鼠标运动、鼠标监控)鼠标节流处理、throttle、限制触发频率(setTimeout、clearInterval)
文章目录 使用lodashjs库手动实现节流(通过判断之前设定的定时器setTimeout是否存在) 使用lodashjs库 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Com…...
【MySQL基础-21】MySQL事务机制详解:原理、实现与最佳实践
在现代数据库系统中,事务机制是确保数据一致性和完整性的核心技术。MySQL作为最流行的开源关系型数据库之一,其事务实现机制对于开发者而言至关重要。本文将深入探讨MySQL的事务机制,包括核心概念、实现原理、隔离级别以及实际应用中的最佳实…...
Transformer+BO-SVM时间序列预测(Matlab)
TransformerBO-SVM时间序列预测(Matlab) 目录 TransformerBO-SVM时间序列预测(Matlab)效果一览基本介绍程序设计参考资料 效果一览 基本介绍 普通的单变量时序已经用腻了,审稿人也看烦了,本期推出一期高创…...
Python循环控制语句
1. 循环类型概述 Python提供两种主要的循环结构: while循环 - 在条件为真时重复执行for循环 - 遍历序列中的元素 2. while循环 基本语法 while 条件表达式:循环体代码示例 count 0 while count < 5:print(f"这是第{count1}次循环")count 13. f…...
4.4日西南竞篮,欧篮联,NBA,全扫盘
欧篮联扫盘 301奥林匹亚 vs 阿尔巴 (11.5),总分167.5:阿尔巴有伤病,点差11.5大,可能是接近比赛,总分较低,预测阿尔巴赢盘,总分低于167.5。 302埃菲斯 vs 贝红星 (-1.5)&a…...
面试可能会遇到的问题回答(嵌入式软件开发部分)
写在前面: 博主也是刚入社会的小牛马,如果下面有写的不好或者写错的地方欢迎大家指出~ 一、四大件基础知识 1、计算机组成原理 (1)简单介绍一下中断是什么。 ①回答: ②难度系数:★★ ③难点分析&…...
AI平台初步规划实现和想法
要实现一个类似Coze的工作流搭建引擎,可以结合SmartEngine作为后端工作流引擎,ReactFlow作为前端流程图渲染工具,以及Ant Design作为UI组件库。以下是实现的步骤和关键点: ### 1. 后端工作流引擎(SmartEngine…...
软件工程面试题(二十七)
1、j a v a 对象初始化顺序 1.类的初始化(initialization class & interface) 2.对象的创建(creation of new class instances) 顺序:应为类的加载肯定是第一步的,所以类的初始化在前。大体的初始化顺序是: 类初始化 -> 子类构造函数 -> 父类构造函数 -&g…...
CCF GESP C++编程 六级认证真题 2025年3月
C 六级 2025 年 03 月 题号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 答案 D B A B B B B A A A A A B C A 一、单选题 第 1 题 在面向对象编程中,类是一种重要的概念。下面关于类的描述中,不正确的是( )。 A. 类是一个抽象的概念&a…...
Cortex-M 上编写汇编函数
在 ARM Cortex-M 系列单片机中使用汇编语言编写函数时,需要特别注意寄存器的使用、栈管理、调用约定以及与 C 语言的兼容性。以下是关键注意事项和示例说明: 1. 遵循 AAPCS 调用约定 ARM 定义了 AAPCS(ARM Architecture Procedure Call Standard),规定了函数调用时寄存器…...
Spring 核心技术解析【纯干货版】- XXII:Spring 扫描效率提升模块 Spring-Context-Indexer 模块精讲
在 Spring 应用 中,组件扫描(Component Scan) 是 Spring 容器启动时的关键任务之一。默认情况下,Spring 通过 反射扫描整个类路径 来找到所有 Component、Service、Repository 等注解的类,并将其注册为 Spring Bean。但…...
jetson AGX orin--ARM64 换源报错Packages 404 Not Found [IP: 2402:f000:1:400::2 443]
问题 原因: ARM64结构不能使用X86结构的源,清华源不完全支持ARM64。使用下面这个源 sudo vim /etc/apt/sources.list 删掉原来的,改成这个 # ARM64 架构专用源 deb [archarm64] http://ports.ubuntu.com/ubuntu-ports focal main restrict…...
对备忘录模式的理解
对备忘录模式的理解 一、场景1、题目【[来源](https://kamacoder.com/problempage.php?pid1095)】1.1 题目描述1.2 输入描述1.3 输出描述1.4 输入示例1.5 输出示例 2、理解需求 二、不采用备忘录设计模式1、代码2、问题3、错误的备忘录模式 三、采用备忘录设计模式1、代码1.1 …...
ngx_ssl_init
定义在 src\event\ngx_event_openssl.c ngx_int_t ngx_ssl_init(ngx_log_t *log) { #if OPENSSL_VERSION_NUMBER > 0x10100003Lif (OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL) 0) {ngx_ssl_error(NGX_LOG_ALERT, log, 0, "OPENSSL_init_ssl() failed")…...
Roo Code(前身为 Roo Cline)一个 AI 驱动的自主编码代理
Roo Code(前身为 Roo Cline) Roo Code 是一个 AI 驱动的自主编码代理,它存在于您的编辑器中。它可以: 用自然语言沟通直接在您的工作区读写文件运行终端命令自动化浏览器操作与任何 OpenAI 兼容或自定义的 API/模型集成通过自定…...
每日一题洛谷P8649 [蓝桥杯 2017 省 B] k 倍区间c++
P8649 [蓝桥杯 2017 省 B] k 倍区间 - 洛谷 (luogu.com.cn) #include <iostream> #include <vector> using namespace std; #define int long long signed main() {int n, k;cin >> n >> k;vector<int> a(n 1);vector<int> sum(n 1);vec…...
CLion安装、配置及使用
目录 1 CLion是什么 2 CLion安装 3 系统环境变量配置 4 CLion配置 4.1 编辑器选择 4.2 编辑器配置 4.3 新建项目 5 总结 1 CLion是什么 CLion 是 JetBrains 推出的一款跨平台集成开发环境(IDE),专为 C 和 C 开发设计,支…...
UE5把动画导出为视频格式
UE5把动画导出为视频格式 步骤一 点击渲染视频或图片按钮旁边的三个圆点按钮 步骤二 点击渲染视频或图片按钮 步骤三 1是修改输出视频的帧率格式 2输出视频的路径 3点击等待视频渲染完成 以上是基本方法 最新的输出视频方法请看这位大佬的视频...
SQL语句(三)—— DQL
目录 基本语法 一、基础查询 1、查询多个字段 2、字段设置别名 3、去除重复记录 4、示例代码 二、条件查询 1、语法 2、条件列表常用的运算符 3、示例代码 三、分组查询 (一)聚合函数 1、介绍 2、常见的聚合函数 3、语法 4、示例代码 &…...
[2017][note]基于空间交叉相位调制的两个连续波在few layer铋Bi中的全光switch——
前言 类型 太赫兹 + 全光开关 太赫兹 + 全光开关 太赫兹+全光开关 期刊 A C S P H O T O N...
第十九节课: python第四周课程:程序分支结构详解
程序分支结构 1. 单分支结构 语法结构: if <条件>:<代码块>执行逻辑: 条件为真时执行代码块类似自然语言中的"如果…则…" 应用案例: # 猜数字示例 guess eval(input("请输入数字:")) if …...
NSSCTF [HGAME 2023 week1]simple_shellcode
3488.[HGAME 2023 week1]simple_shellcode 手写read函数shellcode和orw [HGAME 2023 week1]simple_shellcode (1) motalymotaly-VMware-Virtual-Platform:~/桌面$ file vuln vuln: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpret…...
图形渲染中的定点数和浮点数
三种API的NDC区别 NDC全称,Normalized Device Coordinates Metal、Vulkan、OpenGL的区别如下: featureOpenGL NDCMetal NDCVulkan NDC坐标系右手左手右手z值范围[-1,1][0,1][0,1]xy视口范围[-1,1][-1,1][-1,1] GPU渲染的定点数和浮点数 定点数类型&a…...
报考高校辅导员需要具备哪些条件?
报考高校辅导员通常需要具备以下条件: 基本条件 国籍与政治面貌:具有中华人民共和国国籍,一般要求是中共党员(含预备党员)。道德品质:遵守宪法和法律,具有良好的品行,作风正派&…...
什么是Stop The World
深入解析Stop-The-World(STW):JVM垃圾回收的"世界暂停"现象 1. 什么是Stop-The-World(STW)? Stop-The-World(STW) 是JVM在执行垃圾回收(GC)时的一…...