动态规划 -- 子数组问题
本篇文章中主要讲解动态规划系列中的几个经典的子数组问题。
1 最大子数组和
53. 最大子数组和 - 力扣(LeetCode)
解析题目: 子数组是一个数组中的连续部分,也就是说,如果一个数组以 nums[i]结尾,那么有两种情况,一种就是子数组长度为1,那么子数组只包含nums[i]本身,一种就是子数组的长度大于1,那么此时至少有两个连续的元素,也就是说,nums[i-1]一定是包含在以nums[i]为结尾的长度大于1的子数组中的。而我们的题目要求我们求出数组中的具有最大和的子数组,并返回这个最大和。
那么我们按照常规的状态表示的经验,可以定义:
dp[i] 表示以 nums[i] 为结尾的子数组的最大和
注意,这里的dp[i] 存储的只是以 nums[i] 为结尾的子数组的最大和,并不一定就是整个数组的 [0,i]区间的子数组的最大和,因为[0,i]区间的最大和的子数组并不一定就以nums[i]为结尾,我们只是求出了一个局部的最大和,而不是整体的最大和。
那么接下来分析dp[i]的状态转移方程:
我们可以根据前面的题目解析,将 dp[i] 划分为两种情况:
1 子数组长度为1 ,那么子数组和就是 dp[i] = nums[i]
2 子数组的长度大于1,那么以nums[i]为结尾的子数组其实可以分成两部分看,nums[i]本身 + 以nums[i-1]为结尾的子数组,而我们需要的是具有最大和的子数组,而nums[i]其实已经固定了,那么我们就需要让以 nums[i]为结尾的子数组和最大,那么也就是 dp[i] = nums[i] + dp[i-1]
我们最终需要的是两种情况的最大值,那么其实也很简单,dp[i] = max(dp[i-1]+nums[i],nums[i]),其实更简单的,我们可以直接判断一下dp[i-1]是否大于0,如果大于零那么就是第二种情况,如果小于或者等于0,那么就是第一种情况。
那么状态转移方程就是:
dp[i] = nums[i] + (dp[i-1]>0?dp[i-1]:0)
细节问题:
初始化:由于填表过程中需要用到dp[i-1],那么i=0时我们是需要手动初始化的,而dp[0]其实没得选,就是 nums[0]。或者我们可以多开一个空间,多出的一个空间的值也很简单,就填0。
填表顺序:从左往右填写dp表
返回值:我们需要的是整个数组的最大子数组和,那么结果可能是以任意位置元素为结尾的子数组,所以我们的返回值是整个dp表的最大值,那么我们可以在填表的过程中就记录返回值。
class Solution {
public:int maxSubArray(vector<int>& nums) {int n = nums.size() , res = nums[0];vector<int> dp(n);dp[0] = nums[0];for(int i = 1 ; i < n ; ++i){dp[i] = nums[i] + (dp[i-1] > 0 ? dp[i-1] : 0 );res = max(res,dp[i]);}return res;}
};
注意在这种情况下res初始化为nums[0]
多开一个空间:此时res需要初始化为负无穷大
class Solution {
public:int maxSubArray(vector<int>& nums) {int n = nums.size() , res = -0x3fffffff;vector<int> dp(n + 1);for(int i = 1 ; i <= n ; ++i){dp[i] = nums[i - 1] + (dp[i-1] > 0 ? dp[i-1] : 0 );res = max(res,dp[i]);}return res;}
};
这个题目理解子数组问题的基本入口,我们在思考子数组类问题的时候都可以按照这种思维方式来推导状态表示以及状态转移方程,也就是按照子数组的长度来划分为两类情况,分别讨论,得到我们想要的结果。
2 环形子数组的最大和
918. 环形子数组的最大和 - 力扣(LeetCode)
这个题和上一个题的区别就是数组环形的,也就是第一个元素的前面可以看成最后一个元素。这样一来,我们可以换一种思路,对于环形数组,他的最大和的子数组有两种情况:1 未成环 2 成环
对于第一种情况,我们只需要按照上一个题的思路去求出最大子数组和就行了
而对于第二种情况,最大和的子数组成环的话,我们可以发现,虽然子数组本身不连续,但是除了最大和的子数组之外,剩余的元素是连续的,剩余的元素就是一个普通的不成环的子数组。那么首先数组的总和是固定的,我们要使成环的子数组的和最大,那么我们就需要使得这个剩余元素的子数组和最小,因为这个环形子数组的和就是数组的总和减去中间的连续子数组的和。
综上,我们这个题可以定义两个状态表示:
f[i] 表示以nums[i]为结尾的子数组最大和
g[i] 表示以nums[i]为结尾的子数组最小和
那么f[i] 的最大值就是不成环的最大子数组和,而 数组总和 - g[i]的最小值 就是成环的子数组的最大和,那么整体的最大值就是二者之中的较大值。
而f[i]的状态表示我们上一个题已经分析过了
f[i] = nums[i] + (f[i-1] > 0 ?f[i-1]:0)
对于g[i]的状态表示,我们则需要反着来:
首先还是根据子数组的长度进行讨论,以nums[i]为结尾的子数组有两种情况:
1、 长度为1,此时子数组和就是 nums[i]
2、长度大于1,那么需要使得以nums[i]为结尾的子数组和最小,就需要保证以nums[i]为结尾的子数组和最小,也就是 g[i] = nums[i] +g[i=1]
我们需要去两种情况的最小值,也就是min(nums[i] ,nums[i] + g[i-1]),那么综上:
g[i] = nums[i] + (g[i-1] < 0 ? g[i-1] : 0)
环形子数组的最大和我们需要预处理求出整个数组的和。
我们求最小和的时候,有可能数组的所有元素都是负数,那么就会导致最小和的子数组就是整个数组,所以我们再填g表的时候是需要判断一下中间是否出现断层,否则使用数组总和减去最小和会出错,此时的结果为空子数组,但是题目要求子数组的元素个数至少为1。
细节问题:
需要处理边界 f[0] = g[0] = nums[0];
填表顺序:两个表都是从左往右填。
最终需要的是 f 表中的最大值和 g 表中的最小值,我们可以一变填表一遍记录。
返回值就是两种情况的最大值。
代码:
class Solution {
public:int maxSubarraySumCircular(vector<int>& nums) {int n = nums.size(), sum = 0;for(auto&e:nums) sum += e;vector<int> f(n); //f表用于求最大子数组和auto g = f; //g表用于求最小子数组和bool gflag = false; //用来表示最小和的子数组是否是整个nums数组f[0] = g[0] = nums[0];int fmax = f[0] , gmin = g[0];for(int i = 1 ; i < n ; ++i){f[i] = nums[i] +(f[i-1] > 0 ? f[i-1] : 0);fmax = max(fmax,f[i]);g[i] = nums[i] + (g[i-1] < 0 ? g[i-1] : 0);if(g[i-1] >= 0) gflag = true; //说明最小和的子数组并不是整个nums数组gmin =min(gmin,g[i]);}int res = fmax;if(gflag) res = max(res , sum - gmin);return res;}
};
当然对于 gflag 还有一种更简单的判断方法,就是在求 sum 的时候顺便判断一下 nums 中的元素是否全为负数和0,如果全为负数,那么就说明最小和的子数组一定是整个数组。 如果出现了0或者整数,那么最小子数组和一定是nums数组的一部分而不是全部。
当然这样的话我们会将数组全为 0 的情况漏掉,但是这仅仅是在 g 表中漏掉,在f表中是能够将 0 统计好的,当数组全部都是 负数和0 的时候,f表的最大值就是最终的结果。
3 最大乘积数组
152. 乘积最大子数组 - 力扣(LeetCode)
解析题目:题目给一个整数数组nums,我们需要求出nums中的一个乘积最大的非空子数组。
那么我们还是按照子数组的分析思路来分析,定义状态表示为:
dp[i] 表示以 nums[i] 为结尾的子数组中,最大的乘积。
然后我们分析 dp[i] 的转移方程,还是按照子数组的长度来划分问题:
1 子数组长度为1,此时只有nums[i],那么乘积就是nums[i]
2 子数组的长度大于1,那么此时我们可以将以nums[i]为结尾的子数组划分为 nums[i] 和 以nums[i-1]为结尾的子数组。 但是在这个题目中,我们需要的是乘积,乘法运算中,dp[i-1]是以nums[i]为结尾子数组最大的乘积,但是 nums[i] * dp[i-1] 则不一定是以nums[i]为结尾的子数组的最大乘积,因为 nums[i] 本身有可能是负数,这就会导致 nums[i] * dp[i-1] 最终是一个负数,此时的意义就变成了以 nums[i] 为结尾的子数组的最小乘积。
也就是,在长度大于 1 的时候,我们需要将 nums[i] 分为正数和负数两种情况来讨论:
当nums[i] 为正数时,此时我们需要求出以 nums[i-1] 为结尾的子数组的最大乘积 f[i-1], nums[i]*f[i-1]就是以nums[i]为结尾的子数组的最大乘积。我们可以自己试验一下。比如以nums[i-1]为结尾的子数组的乘积中全是正数,那么自然是正数越大越好,而如果有正有负,也是需要使用最大的正数,而如果全部都是负数,那么就意味着最终结果一定是负数了,那么自然是负数的绝对值越小越好,也就是负数本身越大越好,三种情况都是需要子数组的最大的乘积。
当nums[i] 为负数的时候,我们需要求出以 num[i-1] 为结尾的子数组的最小乘积 g[i-1] ,nums[i]*g[i-1]就是有nums[i]为结尾的额子数组的最大乘积。 我们也可以按照上面的方法试验一下。
按照上面的推导,我们只定义一个状态表示是不够的,需要两个状态表示,如下:
f[i] 表示以 nums[i] 为结尾的子数组中,最大的乘积。
g[i] 表示以 nums[i] 为结尾的子数组中,最小的乘积。
状态转移方程按照上面的推导思路,最大乘积使用 f[i-1] 还是 g[i-1] 取决于 nums[i] 的正负性,而最小成绩使用 f[i-1] 还是 g[i-1] 也是取决于nums[i]的正负性,但是与最大乘积相反。
所以不管怎么说,我们的最大乘积和最小乘积一定是在三种情况下取出: nums[i] , nums[i]*f[i-1] , nums[i]*g[i-1] ,这三者中的最大值就是最大成绩,最小值就是最小乘积,所以状态转移方程就是:
f[i] = max( nums[i] , nums[i] * f[i-1] , nums[i] * g[i-1] )
g[i] = min( nums[i] , nums[i] * f[i-1] , nums[i] * g[i-1] )
细节问题:
边界值的初始化,f[0] 和 g[0] 都需要初始化为 nums[0] ,如果我们开辟额外空间,那么虚拟空间的值可以初始化为 1.
填表顺序:从左往右,两个表一起填。
返回值: 需要的是所有子数组中的最大乘积,那么返回值就是 f 表的最大值,可以边填表边记录,res可以初始化为nums[0]
代码如下:
class Solution {
public:int maxProduct(vector<int>& nums) {int n = nums.size() , res = nums[0];vector<int> f(n),g(n);f[0] = g[0] = nums[0];for(int i = 1 ; i < n ; ++i){f[i] = max(nums[i] , max(nums[i] * f[i-1] , nums[i] * g[i-1]));g[i] = min(nums[i] , min(nums[i] * f[i-1] , nums[i] * g[i-1]));res = max(res,f[i]);}return res;}
};
4 乘积为正数的最长子数组长度
1567. 乘积为正数的最长子数组长度 - 力扣(LeetCode)
这个题与上一题类似,是一道乘法相关的问题,还是按照子数组系列问题的动态规划分析思路,我们可以分析以 nums[i] 为结尾的子数组的情况,用于延伸到所有的 0 <= i < n
根据题目,我们需要子数组的乘积为正数,要求的是最大子数组的长度,那么还是将以 nums[i] 为结尾的子数组按照长度分为两个大类
1 长度为1的子数组,也就是只包含nums[i],此时只取决于nums[i]是否为正数
2 长度大于1 的子数组,那么此时以nums[i]为结尾的子数组可以分为两个部分,nums[i] 和以nums[i-1]为结尾的子数组,那么此时需要分为三种情况来讨论:
2.1:nums[i] 为正数,那么要使nums[i]*dp[i-1]的乘积为正数,就需要dp[i-1]为正数,而我们要的是乘积为正的最长的子数组的长度,那么就需要知道以nums[i-1]为结尾的乘积为正的子数组的最长长度
2.2:nums[i]为负数,那么要使 nums[i] * dp[i-1] 的乘积为正数,就需要dp[i-1]为负数,而我们要的是成绩为正的最长的子数组的长度,那么就需要知道以nums[i-1]为结尾的乘积为负数的子数组的最长长度。
2.3:nums[i] = 0 ,那么乘积不可能为正,以nums[i]为结尾的子数组中不存在乘积为正数的子数组,所以dp[i] = 0 ,表示空数组。
按照上面的推导过程,我们至少需要两个状态表示:
f[i] 表示以nums[i] 为结尾的乘积为负数的子数组中,最长的子数组长度
g[i] 表示以nums[i]为结尾的成绩为正数的子数组中,最长的子数组长度
状态转移方程推导:
如果nums[i] 为0,那么以nums[i] 为结尾的子数组中,子数组的乘积都为0,不可能为正数或者负数,所以 f[i] = g[i] = 0;
如果nums[i] < 0,那么要使以nums[i] 为结尾的子数组的乘积为正,就需要以nums[i-1]为结尾的子数组的乘积为负数,同时需要子数组的长度最长,那么就需要以 nums[i-1]为结尾的乘积为负数的子数组的长度最长,在该子数组后面加上一个 nums[i] 形成一个新的子数组,那么f[i] = g[i-1]+1;而对于g[i],要使以nums[i] 为结尾的子数组的乘积为负数,那么就需要以nums[i-1]为结尾的子数组的长度为正数,同时要使得子数组的长度最长,那么就需要以 nums[i-1]为结尾的乘积为正数的子数组的长度最长,也就是f[i-1],在稿子数组大的后面加上一个nums[i]形成新的子数组,那么g[i] = f[i-1]+1;
如果nums[i]为正数,那么跟第二种情况就是反着来:f[i] = f[i-1]+1,g[i] = g[i-1]+1;
if(nums[i] == 0) f[i] = g[i] = 0;
if(nums[i] < 0 ) f[i] = g[i-1] + 1 , g[i] = f[i-1] + 1
if(nums[i] > 0 ) f[i] = f[i-1] + 1 , g[i] = g[i-1] +1
细节问题:
初始化:我们需要初始化f[0] 和g[0],很简单,如果nums[0]为正数,那么f[0]=1,g[0]=0,如果nums[0]为负数,那么f[0] = 0 ,g[0] = 1,如果nums[0]=0,那么f[0]=g[0]=0;这样一来初始化就很复杂,我们可以在f表和g表都多开一块空间,那么这一块虚拟的空间怎么填呢? 为了不影响后续的f[1]和g[1]的值,我们可以直接填0,甚至我们可以直接根据状态定义来填值,躲开一块空间之后的f[0]和g[0]表示以nums[-1]为结尾的子数组的乘积为正和为负的最长长度,而他们本身就是空数组,自然就是0.注意多开一个空间之后,f[i]和g[i]映射的就是nums[i-1]了。
填表顺序:从左往右,f表和g表一起填
返回值:返回f表的最大值,可以一边填表一边记录最大值,res可以初始化为0,表示空数组。
本体是允许空数组的,因为可能存在这样的情况[-1],那么此时不可能出现为乘积正数的非空子数组,所以只能选择空数组。
代码如下(额外空间):
class Solution {
public:int getMaxLen(vector<int>& nums) {int n = nums.size(),res = 0;vector<int> f(n+1 , 0),g(n+1 , 0);for(int i = 1 ; i <= n ; ++i){if(nums[i-1] == 0) f[i] = g[i] = 0;if(nums[i-1] < 0 ) f[i] = g[i-1] > 0 ? g[i-1] + 1 : 0 , g[i] = f[i-1]+1;if(nums[i-1] > 0 ) f[i] = f[i-1] + 1 , g[i] = g[i-1] > 0 ?g[i-1] +1:0;res = max(res,f[i]);} return res;}
};
5 等差数列划分
413. 等差数列划分 - 力扣(LeetCode)
解析题目:给定一个nums,要求我们求出nums中为等差数列的子数组的数目。等差子数组的定义:1 至少要有三个元素 2 相邻元素之间的差值相等。
对于子数组类的题目,我们还是以结尾位置来定义状态,
dp[i] 表示以nums[i] 为结尾的等差子数组的个数
然后对 dp[i] 进行分析,以nums[i] 为结尾的子数组可以按照长度分为两类:
1 长度为1的子数组,也就是nums[i]自身,此时不满足等差数列
2 长度大于1的子数组,那么我们可以看成是 nums[i] 和以nums[i-1]的子数组 组合而成。如果我们想要以nums[i]为结尾的子数组是等差数组,那么至少要满足 nums[i] - nums[i-1] = nums[i-1] - nums[i-1],如果nums[i-2] , nums[i-1] , nums[i] 这三个元素构成了等差子数组,对于以nums[i-1]为结尾的等差子数组,他一定是包含 nums[i-2]的,而nums[i-1] - nums[i-2] 是确定的,那么就说明了以nums[i-1]为结尾的等差子数组的公差一定是 nums[i-1] - nums[i-2] 。那么如果nums[i] - nums[i-1] 与nums[i-1] - nums[i-2] 相等,说明我们将nums[i] 拼接到以nums[i-1]为结尾的等差子数组之后,照样是等差子数组,同时,以nums[i]为结尾的等差子数组除了所有的以nums[i-1]为结尾的等差子数组后面加上一个nums[i]之外,还会多出一个长度为3的等差子数组,也就是{nums[i-2].nums[i-1],nums[i]} ,所以 dp[i] = dp[i-1] + 1;
但是如果 nums[i] - nums[i-1] != nums[i-1] - nums[i-2] ,那么就说明这三个数不构成等差数列了,而以nums[i-1]为结尾的所有的等差子数组的公差都是 nums[i-1] - nums[i-2] ,那么以nums[i]为结尾的子数组中没有等差子数组了,也就是dp[i] = 0;
那么综上所述,状态转移方程如下:
dp[i] = (nums[i-1] * 2 == nums[i] + nums[i-2] ?dp[i-1]+1:0)
细节问题:
初始化:填表过程中我们需要用到dp[i-1] 和 dp[i-2],那么当 i = 0 和 i = 2时会越界,所以我们需要手动初始化 dp[0] 和 dp[1] ,根据状态表示,以nums[0] 和以nums[1]为结尾的子数组中不可能构成等差子数组,因为长度一定小于3,所以 dp[0] = dp[1] = 0;
填表顺序:从左往右填表
返回值:题目要求返回所有的等差子数组,那么我们需要返回dp表的所有值之和,我们可以一变填表一边记录res。
代码如下:
class Solution {
public:int numberOfArithmeticSlices(vector<int>& nums) {int n = nums.size() , res = 0;vector<int> dp(n);for(int i = 2 ; i < n ; ++i){dp[i] = (nums[i-1] * 2 == nums[i] + nums[i-2] ? dp[i-1] + 1 : 0);res += dp[i];}return res;}
};
6 最长湍流子数组
978. 最长湍流子数组 - 力扣(LeetCode)
解析题目:首先理解湍流子数组,比较符号在子数组中的每个相邻元素对之间翻转。其实很简单,对于每一个i,如果nums[i] > nums[i-1] ,那么只有当 nums[i+1] < nums[i] 的时候,nums[i-1],nums[i].nums[i+1]这三个数才构成湍流子数组。
我们可以更抽象一点,将比较关系用上升和下降来表示,如果nums[i]>nums[i-1],我们称在nums[i-1]处处于上升趋势,如果nums[i] < nums[i-1] ,那么我们称在nums[i-1]处处于下降趋势。
那么如果一个数组是湍流子数组,那么对于 k ,在[0,n]或者说在 [0,n-1]范围内,每一个位置的上升和下降是轮着出现的,用图画出来就是类似于这样的图:
每一个元素本身就是一个湍流子数组,同时,每两个相邻的元素也是一个湍流子数组,因为两个元素只存在一个关系,并没有违反湍流子数组的规则。
那么我们还是来分析中间的任意一个nums[i] ,要使得以nums[i]为结尾的子数组是湍流子数组,还是根据子数组长度划分为两类:
1 子数组长度为1 ,那么此时表示的是 nums[i] 一个元素构成的子数组,是湍流子数组,长度为1
2 子数组长度大于1,此时如果nums[i] 与nums[i-1] 不相等,那么此时长度就至少为2了,我们需要知道nums[i] 与nums[i-1]的关系,如果nums[i] > nums[i-1] ,那么说明在i-1 位置处于上升趋势,那么只有在 i-2 位置处于下降趋势的时候, 也就是以nums[i-1]为结尾的湍流子数组的最后一对元素呈下降趋势,那么nums[i] 就可以和以nums[i-1]为结尾的湍流子数组组合构成一个新的更长的湍流子数组。相反的,如果nums[i] < nums[i-1] ,那么nums[i] 只有与以nums[i-1]为结尾的且最后一对元素处于上升趋势的湍流子数组构成新的更长的湍流子数组。
如果nums[i] == nums[i-1] ,那么以nums[i]为结尾的湍流子数组其实就只能是长度为1的子数组了。
那么我们的状态表示如下:
dp[i] 表示以nums[i]为结尾的子数组中,最长的湍流子数组的长度
其实在dp[i]中隐藏了一个状态,就是最后一对元素处于下将状态还是上升状态,但是在填写dp[i]的时候,我们只会用到nums[i-1]和nums[i-2]的关系,我们可以使用一个变量来记录,避免每一次都取出数组元素来比较。
状态转移方程:
首先我们根据上面的推导,如果nums[i] == nums[i-1] ,那么dp[i] = 1,因为nums[i]无法和其前面的元素构成湍流子数组。
如果 nums[i] > nums[i-1] ,那么有两种情况,1 nums[i-1] > nums[i-2] ,那么说明nums[i]最多只能和nums[i-1]构成湍流子数组,如果再加入一个nums[i-2],就不符合湍流子数组的定义了,此时dp[i] = 2; 2 如果nums[i-1] < nums[i-2],那么说明nums[i]和nums[i-1]以及nums[i-2]能够构成湍流子数组,更深入的说: nums[i] 能够连接在 以nums[i-1] 为结尾的湍流子数组 之后构成新的湍流子数组,而我们dp[i]需要的是以nums[i]为结尾的最长的湍流子数组的长度,那么我们就需要以nums[i-1]为结尾的最长湍流子数组的长度dp[i-1] ,而dp[i] = dp[i-1]+1;
如果nums[i] < nums[i-1] , 那么与上面的情况相反。
我们前面也说了,可以使用一个变量来记录 nums[i-1] 和nums[i-2] 的关系,我们可以使用一个flag来记录,定义:nums[i-1] == nums[i-2] ,flag = 0 , nums[i-1] > nums[i-2] ,那么flag = 1,如果nums[i-1] < nums[i-2] ,flag = 2.
那么上面的状态转移方程可以这样总结:
if(nums[i] == nums[i-1]) {
dp[i] = 1;
flag = 0;
}
else if(nums[i] > nums[i-1]){
if(flag == 2) dp[i] = dp[i-1] + 1 ;
else dp[i] = 1;
flag = 1; //更新flag
}
else{
if(flag == 1) dp[i] = dp[i-1] + 1;
else dp[i] = 2;
flag = 2;
}
细节问题:
初始化: 我们上面的状态转移方程是基于flag来推导的,意思就是至少要能够初始化flag的值,也就是需要手动初始化 do[0] 和 dp[1] 的值,来获取一个初始的 flag
填表顺序: 从左往右填表
返回值: 返回整个dp表的最大值,一变填表一边记录
代码如下:
class Solution {
public:int maxTurbulenceSize(vector<int>& nums) {int n = nums.size() , res = 0 ,flag = 0; if(n < 2) return n;if(nums[1] == nums[0]) flag = 0;else if(nums[1] > nums[0]) flag = 1;else flag = 2;vector<int>dp(n);dp[0] = 1,dp[1] = (flag == 0 ? 1 : 2);res = dp[1];for(int i = 2 ; i < n ; ++i){if(nums[i] == nums[i-1]){flag = 0 ; dp[i] = 1;}else if(nums[i] > nums[i-1]){if(flag == 2) dp[i] = dp[i-1] + 1;else dp[i] = 2;flag = 1; }else{if(flag == 1) dp[i] = dp[i-1] + 1;else dp[i] = 2;flag = 2; }res = max(res , dp[i]);} return res;}
};
对于本题,直接贪心肯定是要比动态规划好的,而贪心的做法也很简单,利用我们上面使用的flag,然后再使用了两个变量来记录历史长度以及最长长度就行了。
7 单词拆分
139. 单词拆分 - 力扣(LeetCode)
解析题目:给定一个字符串 s ,在给定我们一个单词string数组,要我们判断能不能由数组中的单词组成字符串s。
对于这个题目,其实本质上还是一个子数组的问题,我们要让 s 能够由单词组成,那么对于s的最后一个字符,一定能和前面的0个或者多个字符组成一个在数组中出现的单词,然后再判断剩余部分能否由单词组成。
那么分析思路还是一样的,我们想要判断以 s[i] 为结尾的子串能否由给定的单词构成,那么我们还是将这个字串分为两部分看,结尾为s[i]的单词 + 子串剩余部分。只有这两个部分都能够有给定的单词组成,那么我们才认为这个子串能够给定的单词组成。
两个条件: 1、 [j,i] 子串是一个给定的单词
2、剩余部分[0,j-1] 能够由给定的单词组成
那么要满足第一个条件,我们的j其实可以在[0,i] 范围内,只要有一个 j 满足就行了,但是要于此同时满足第二个条件,而第2个条件显然比第 1 个条件更加严格,那么其实我们在选择 j 的时候,其实只能够在[0,j-1]的字串部分能够被给定的单词组成的前提下来选择。
那么我们可以定义状态表示为:
dp[i] 表示以s[i]为结尾的子串能否被给定的单词组成,如果能dp[i] = true,不能的话dp[i] = false
那么对于状态转移方程,我们其实是需要从i开始,往前遍历,找到一个 j ,要满足dp[j] = true ,然后判断 [j+1,i] 这个子串是不是给定的单词,如果是,那么就说明 [0,i] 这个子串是能够被所给定的单词组成的,如果不是,那么还需要继续向前遍历找 j ,如果j = 0 还是找不到一个符合条件的,那么就说明 [0,i] 子串无法被给定的单词组成。
那么这样一来,遍历 j 的时候其实很多的时候 dp[j] 都是false,为true的在少数,但是dp[j]=false时其实对于后面的状态是没用的,所以其实填写 dp[i] 的时候,我们只会用到前面的为 true 的状态,那么优化一下,我们是不是可以不使用一个数组来保存所有的dp[i] ,而是只有当dp[i] = true的时候,我们将此时的 i 尾插到数组中。那么后续在判断一个 dp[i] 的时候,要找到[0,i]之间的dp[j] = true的时候,那么我们只需要遍历这个数组就行了。
最后我们的返回值其实就看为true的dp[i] 中,是否有 i=n-1.如果有,说明s能够被给定的单词组成,如果没有,那么就是不能的。
同时,为了方便判断[j+1,i]子串是否是一个给定的单词,我们可以将所有的单词用一个unordered_set存储起来,方便后续查找。
代码:
class Solution {
public:bool wordBreak(string s, vector<string>& wordDict) {//dp[i] 表示以s[i]为结尾的子串能否由给定的单词组成//但是由于大部分的dp[i]无效//我们只保存dp[i] = true的下标i,用一个数组 OK 来存储int n = s.size();unordered_set<string> ss;vector<int> OK;OK.push_back(-1); //初始条件,表示空串for(auto&str:wordDict) ss.insert(str);for(int i = 0 ; i < n ; ++i){cout<<i<<endl;for(int j = OK.size() - 1 ; j >= 0 ; --j){if(ss.find(s.substr(OK[j]+1,i-OK[j]))!= ss.end()){ //[j+1,i] 是否是一个给定的单词OK.push_back(i);cout<<i<<":"<<OK[j]<<endl;break;}}}return OK.back() == n-1;}
};
当然其实还可以进一步优化,比如在遍历OK[j]的时候,当 [ OK[j]+1 , i ] 这个子串的长度已经大于给定的单词中最长的单词的长度,那么根本不需要set中查找,一定是不存在的的。
8 环绕字符串中的唯一子字符串
467. 环绕字符串中唯一的子字符串 - 力扣(LeetCode)
解析题目:题目定义了环绕字符串,其实就是一个 a~z 的无限循环的字符串。而给定一个s,要求找出s中有多少子串(相同的子串只计算一边)在环绕字符串中出现过。
s中的环绕子串,我们可以简单理解,就是字符成递增序列的子串,比如 "a" 或者 "abc" 或者"yza"这样的,而类似的,"ba","ca" 这样的子串就不是环绕字符串的子串。
那么还是按照数组的动态规划思路,定义状态表示为:
dp[i] 表示以 s[i]为结尾的子串中,是环绕字符串的子串的个数。
那么分析dp[i] 的状态转移方程:
长度为1时,s[i]自身就是一个环绕字符串的子串。
如果s[i] 是s[i-1] +1 或者说 s[i] == 'a' && s[i-1] == 'z' 那么s[i-1]和s[i] 就构成长度为2的环绕子串,同时,以 s[i-1]为结尾的环绕子串都可以在后面加上一个s[i]构成一个更长的环绕子串,那么这样一来 dp[i] = dp[i-1] + 1 ,+1是因为多了一个子串也就是 s[i] 自身。
我们需要手动初始化dp[0] = 1,但是由于所有的dp[i] ,不管怎么说,即便不满足场面的条件,他的环绕字串的数量也会是1,所以我们干脆将dp表全部初始化为1,在填表过程中我们就不需要考虑长度为1的情况了。
而返回值就是dp表的元素之和。
那么我们初步将代码写出来就是这样的:
class Solution {
public:int findSubstringInWraproundString(string s) {int n = s.size();vector<int> dp(n,1);int res = 1;for(int i = 1 ; i < n ; ++i){if(s[i] == s[i-1] + 1 ||(s[i] == 'a' && s[i-1] == 'z')) dp[i] = dp[i-1] + 1;res += dp[i];}return res;}
};
但是如果只是这样的话,我们会发现一个问题:
测试用例2结果不对,原因也很简单,因为 "c" 这个环绕子串被算了两次,结果自然就不同了。
也就是我们其实还有一个关键问题没有处理,就是去重。
那么针对本题如何去重呢? 或者说我们可以思考一下在什么情况下会出现重复子串呢?
首先,只要出现重复的字符,这些重复的字符一定会被计算多次。
还有就是这样的情况: s = "abczabcdyzabc",在这个子串中,"abc" 其实出现了三次,那么"abc"的环绕子串也被计算了三次。
我么可以思考一下对于 "abc" 和 "zabc"这样的子串,怎么进行去重? 其实很简单,我们把这两个字符串叫做完整环绕子串,如果完整环绕子串的结尾字符相同的情况下,那么长度更长的那个完整子串,是包含了长度较短的那个完整子串的,也就是说,在计算长度更长的完整子串的所有环绕子串的时候,其实会把长度较短的完整子串的所有环绕子串计算一遍。前提是结尾字符相同,因为我们的状态表示中就是定义了dp[i] 的环绕子串一定是以s[i]为结尾的。
那么优化的思路就很简单,我们可以统计所有字符为结尾的完整子串的最长长度,比如s存在"abc"和"yzabc"的时候,我们记录 c 的完整子串的最长长度为 5 ,那么这样一来,我们就可以计算出以每一个字符为结尾的最长完整环绕子串,那么以其为结尾的环绕子串的数量其实就是最长完整子串的长度。 比如"abc" 和 "yzabc",以c为结尾的环绕子串其实我们只需要计算"yzabc"的以c为结尾的环绕子串的数量,其实就是长度从 1 一直到 length ,也就是length个。
那么综上所述,其实我们只需要记录每一个字符为结尾的环绕子串的最长的长度,这个最长的长度其实也可以理解为以该字符为结尾的环绕子串的数量,这样就能达到去重的目的了。记录最大值我们可以使用一个unordered_map来记录每一个字符为结尾的环绕子串的最长的长度。
当然dp表我们还是正常填,最后的返回值就是返回每一个字符为结尾的最长环绕字串的长度之和,也就是把哈希表中的所有值加起来。
代码如下:
class Solution {
public:int findSubstringInWraproundString(string s) {int n = s.size();unordered_map<char,int> mm;vector<int> dp(n,1);int res = 0;mm[s[0]] = 1;for(int i = 1 ; i < n ; ++i){if(s[i] == s[i-1] + 1 ||(s[i] == 'a' && s[i-1] == 'z')) dp[i] = dp[i-1] + 1;mm[s[i]] = max(mm[s[i]] , dp[i]);}for(auto&[ch,len] : mm) res += len; //只看需要计算最长的就行了return res;}
};
总结:关于动态规划的子数组的问题,我们都可以按照一个思路来思考,首先状态表示的定义一般定义为以数组的某个元素为结尾,来分析该元素的子数组的情况,而分析子数组的时候,将所有的子数组划分为两类,长度为1和长度大于1.。
相关文章:
动态规划 -- 子数组问题
本篇文章中主要讲解动态规划系列中的几个经典的子数组问题。 1 最大子数组和 53. 最大子数组和 - 力扣(LeetCode) 解析题目: 子数组是一个数组中的连续部分,也就是说,如果一个数组以 nums[i]结尾,那么有两…...
Sehll编程的函数于数组
目录 一、函数 1.1、定义函数 1.2、查看、删除函数 1.3、函数的返回值 1.4、函数的参数传递 1.5、函数的作用范围 1.6、函数递归 二、数组 2.1、声明数组 2.2、数组格式定义 2.3、数组调用 2.4、删除数组 一、函数 shell编程中,函数用于封装一段可以重…...
flutter 专题 六十四 在原生项目中集成Flutter
概述 使用Flutter从零开始开发App是一件轻松惬意的事情,但对于一些成熟的产品来说,完全摒弃原有App的历史沉淀,全面转向Flutter是不现实的。因此使用Flutter去统一Android、iOS技术栈,把它作为已有原生App的扩展能力,…...
AI生成Flutter UI代码实践(一)
之前的杂谈中有提到目前的一些主流AI编程工具,比如Cursor,Copilot,Trea等。因为我是Android 开发,日常使用Android Studio,所以日常使用最多的还是Copilot,毕竟Github月月送我会员,白嫖还是挺香…...
spring boot中@Validated
在 Spring Boot 中,Validated 是用于触发参数校验的注解,通常与 JSR-303/JSR-380(Bean Validation)提供的校验注解一起使用。以下是常见的校验注解及其用法: 1. 基本校验注解 这些注解可以直接用于字段…...
VBA代码解决方案第二十四讲:EXCEL中,如何删除重复数据行
《VBA代码解决方案》(版权10028096)这套教程是我最早推出的教程,目前已经是第三版修订了。这套教程定位于入门后的提高,在学习这套教程过程中,侧重点是要理解及掌握我的“积木编程”思想。要灵活运用教程中的实例像搭积木一样把自己喜欢的代码…...
SpringBoot+EasyExcel+Mybatis+H2实现导入
文章目录 SpringBootEasyExcelMybatisH2实现导入1.准备工作1.1 依赖管理1.2 配置信息properties1.3 H2数据库1.4 Spring Boot 基础概念1.5 Mybatis核心概念 1.6 EasyExcel核心概念 2.生成Excel数据工具类-随机字符串编写生成Excel的java文件 3.导入功能并且存入数据库3.1 返回结…...
算法四 习题 1.3
数组实现栈 #include <iostream> #include <vector> #include <stdexcept> using namespace std;class MyStack { private:vector<int> data; // 用于存储栈元素的数组public:// 构造函数MyStack() {}// 入栈操作void push(int val) {data.push_back…...
el-tabs与table样式冲突导致高度失效问题解决(vue2+elementui)
背景 正常的el-table能根据父容器自动计算剩余高度,并会在列表中判断自适应去放出滚动条。而el-tabs本身就是自适应el-tab-pane内容的高度来进行自适应调节,这样就会导致el-table计算不了当前剩余的高度,所以当el-tabs里面包含el-table时&am…...
Access开发:轻松一键将 Access 全库表格导出为 Excel
hi,大家好呀! 在日常工作中,Access 常常是我们忠实的数据管家,默默守护着项目信息、客户列表或是库存记录。它结构清晰,录入便捷,对于许多中小型应用场景来说,无疑是个得力助手。然而ÿ…...
合并多个Excel文件到一个文件,并保留格式
合并多个Excel文件到一个文件,并保留格式 需求介绍第一步:创建目标文件第二步:创建任务列表第三步:合并文件第四步:处理合并后的文件之调用程序打开并保存一次之前生成的Excel文件第五步:处理合并后的文件之…...
使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第十讲)
这一期我们讲解demo中登录、ok按键的回调函数以及界面的美化,以下是上期界面的图片如图所示: 首先点击界面在右侧的工具栏中调配颜色渐变色,具体设置如下图所示: 然后是关于界面内框也就是容器的美化,具体如下图所示…...
论文笔记(八十二)Transformers without Normalization
Transformers without Normalization 文章概括Abstract1 引言2 背景:归一化层3 归一化层做什么?4 动态 Tanh (Dynamic Tanh (DyT))5 实验6 分析6.1 DyT \text{DyT} DyT 的效率6.2 tanh \text{tanh} tanh 和 α α α 的消融实验…...
Mysql之数据库基础
🌟 各位看官好,我是maomi_9526! 🌍 种一棵树最好是十年前,其次是现在! 🚀 今天来学习Mysql的相关知识。 👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更…...
shell(5)
位置参数变量 1.介绍 当我们执行一个shell脚本时,如果希望获取到命令行的参数信息,就可以使用到位置参数变量. 比如:./myshell.sh100 200,这就是一个执行shell的命令行,可以在myshell脚本中获取到参数信息. 2.基本语法 $n(功能描述:n为数字,$0代表命令…...
VARIAN安捷伦真空泵维修清洁保养操作SOP换油操作流程内部转子图文并茂内部培训手侧
VARIAN安捷伦真空泵维修清洁保养操作SOP换油操作流程内部转子图文并茂内部培训手侧...
动画震动效果
项目场景: 提示:这里简述项目相关背景: 在有的相关目中特别是在C端一般都要求做的炫酷一些,这就需要一些简易的动画效果,这里就弄了一个简易的震动的效果如下视频所示 让图标一大一小的震动视频 分析: 提…...
DB-GPT V0.7.1 版本更新:支持多模态模型、支持 Qwen3 系列,GLM4 系列模型 、支持Oracle数据库等
V0.7.1版本主要新增、增强了以下核心特性 🍀DB-GPT支持多模态模型。 🍀DB-GPT支持 Qwen3 系列,GLM4 系列模型。 🍀 MCP支持 SSE 权限认证和 SSL/TLS 安全通信。 🍀 支持Oracle数据库。 🍀 支持 Infini…...
C++23 std::invoke_r:调用可调用 (Callable) 对象 (P2136R3)
文章目录 引言背景知识回顾可调用对象C17的std::invoke std::invoke_r的诞生提案背景std::invoke_r的定义参数和返回值异常说明 std::invoke_r的使用场景指定返回类型丢弃返回值 std::invoke_r与std::invoke的对比功能差异使用场景差异 结论 引言 在C的发展历程中,…...
pymysql
参数(会导致SQL注入) import pymysql# 创建数据库连接 conn pymysql.connect(user "root",password "root",host "127.0.0.1",port 3306,database "test" )# 创建游标对象 cur conn.cursor(cursorpymysql.…...
基于Spring Boot + Vue 项目中引入deepseek方法
准备工作 在开始调用 DeepSeek API 之前,你需要完成以下准备工作: 1.访问 DeepSeek 官网,注册一个账号。 2.获取 API 密钥:登录 DeepSeek 平台,进入 API 管理 页面。创建一个新的 API 密钥(API Key&#x…...
Spring Boot集成Kafka并使用多个死信队列的完整示例
以下是Spring Boot集成Kafka并使用多个死信队列的完整示例,包含代码和配置说明。 1. 添加依赖 (pom.xml) <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId&…...
全面了解CSS语法 ! ! !
CSS(层叠样式表)是网页设计的灵魂之一,它赋予了网页活力与美感。无论是为一个简单的个人博客增添色彩,还是为复杂的企业网站设计布局,CSS都是不可或缺的工具。那么,CSS语法到底是什么样的呢?它背…...
Springboot使用ThreadLocal提供线程局部变量,传递登录用户名
文章目录 概述使用创建ThreadLocalUtil工具类在登录拦截器中使用ThreadLocal存储登录用户名在/userInfo接口中获取登录用户名 注意事项参考视频 概述 使用 创建ThreadLocalUtil工具类 utils/ThreadLocalUtil.java package org.example.utils;/*** ThreadLocal 工具类*/ Supp…...
排序算法——选择排序
一、介绍 「排序算法sortingalgorithm」用于对一组数据按照特定顺序进行排列。排序算法有着广泛的应用,因为有序数据通常能够被更有效地查找、分析和处理。 如图所示,排序算法中的数据类型可以是整数、浮点数、字符或字符串等。排序的判断规则可根据需求…...
AlphaFold蛋白质结构数据库介绍
AlphaFold Protein Structure Database (AlphaFold DB) 是 DeepMind + EMBL-EBI 合作开发的公开蛋白质结构预测数据库,是利用 AlphaFold2/AlphaFold3 AI模型 预测的全基因组级蛋白质三维结构库。 网址: https://alphafold.ebi.ac.uk 项目内容主办单位DeepMind + EMBL-EBI上线…...
Roboflow标注数据集
使用Roboflow进行标注 关键点标注目标检测标注图像分类标注分割标注 Roboflow是一款易于使用的在线 图像标注。 关键点标注 每个图像的标注包括: 1、边界框坐标(每个物品应该有一个边界框,用*[x1,y1,x2,y2]*格式即左上角和右下角点描述&…...
大厂经验:第三方包Paramunittest参数化 VS Unittest内置参数化文本管理器subtest
大厂经验:第三方包Paramunittest参数化 VS Unittest内置参数化文本管理器subtest 代码解析 Paramunittest 核心逻辑 paramunittest.parametrized((Testerr, test, Invalid Login or Password., test_login_admin is passed),(Sam, test, Invalid Login or Passwo…...
[特殊字符]适合五四青年节的SVG模版[特殊字符]
宝藏模版 往期推荐(点击阅读): 趣味效果|高大上|可爱风|年终总结I|年终总结II|循环特效|情人节I|情人节II|情人节IIII|妇女节I&…...
当插入排序遇上“凌波微步“——希尔排序的奇幻漂流
文章目录 一、排序江湖的隐藏高手二、分而治之的魔法1. 核心思想拆解2. 动态演示(脑补版) 三、C语言实现大揭秘代码要点解析: 四、性能分析与实战技巧1. 时间复杂度迷思2. 实测性能对比 五、为什么说它永不过时?六、进阶思考题 一…...
【Hot 100】 148. 排序链表
目录 引言十大排序算法1. 冒泡排序 (Bubble Sort)2. 选择排序 (Selection Sort)3. 插入排序 (Insertion Sort)4. 希尔排序 (Shell Sort)简单代码说明关键特点 5. 归并排序 (Merge Sort)6. 快速排序 (Quick Sort)7. 堆排序 (Heap Sort)8. 计数排序 (Counting Sort)9. 桶排序 (Bu…...
大连理工大学选修课——机器学习笔记(9):线性判别式与逻辑回归
线性判别式与逻辑回归 概述 判别式方法 产生式模型需要计算输入、输出的联合概率 需要知道样本的概率分布,定义似然密度的隐式参数也称为基于似然的分类 判别式模型直接构造判别式 g i ( x ∣ θ i ) g_i(x|\theta_i) gi(x∣θi),显式定义判别式…...
[特殊字符] 开发工作高内存占用场景下,Windows 内存压缩机制是否应该启用?实测分析与优化建议
在日常开发中,我们往往需要同时运行多个高占用内存的工具,例如: IntelliJ IDEA VMware 虚拟机 多个 Java 后端程序 这些应用程序非常“吃内存”,轻松就能把 16GB、甚至 24GB 的物理内存用满。那么,Windows 的“内存…...
相机的基础架构
📷 相机相关基础架构学习路径 一、了解手机相机系统架构 Android Camera HAL(如果你是做 Android 平台) 学习 Camera HAL3 架构(基于 camera_device_t, camera3_device_ops 接口) 熟悉 CameraService → CameraProvid…...
C# 类成员的访问:内部与外部
在 C# 编程中,了解如何从类的内部和外部访问成员是非常重要的。本文将详细介绍这两种访问方式,并通过示例代码展示其具体应用。 从类的内部访问成员 类的成员可以在类的内部自由地互相访问,即使这些成员被声明为private。在类的方法中&…...
DAPO:对GRPO的几点改进
DAPO:对GRPO的几点改进 TL; DR:对 GRPO 的几处细节进行了优化,包括去除 KL 约束、解耦 ppo-clip 的上下界,上界设置更高以鼓励探索、超长回答过滤、token level 损失计算等。相比于原始 GRPO,在 AIME24 上提升非常显著…...
从零构建 MCP Server 与 Client:打造你的第一个 AI 工具集成应用
目录 🚀 从零构建 MCP Server 与 Client:打造你的第一个 AI 工具集成应用 🧱 1. 准备工作 🛠️ 2. 构建 MCP Server(服务端) 2.1 初始化服务器 🧩 3. 添加自定义工具(Tools&…...
2025.4.27 Vue.js 基础学习笔记
一、Vue.js 简介 Vue.js(简称 Vue)是一个用于构建用户界面的渐进式 JavaScript 框架。它具有以下特点: 轻量级 :核心库体积小,性能优秀,不占用过多资源,加载速度快,适合各种规模的应…...
基于用户场景的汽车行驶工况构建:数据驱动下的能耗优化革命
行业现状:标准工况与用户场景的割裂 全球汽车行业普遍采用WLTC工况进行能耗测试,但其与真实道路场景差异显著。据研究,WLTC工况下车辆能耗数据比实际道路低10%-30%,导致用户对续航虚标投诉激增(数据来源:东…...
IoTDB集群部署中的网络、存储与负载配置优化
一、引言 在现代计算机系统和应用程序中,网络I/O性能是决定整体系统表现的关键因素之一。特别是在IoTDB集群环境中,网络I/O的重要性尤为突出,特别是在处理大量测点数据、客户端请求以及集群内部通信时。本文将介绍IoTDB数据库集群部署过程中…...
Unity URPShader:实现和PS一样的色相/饱和度调整参数效果(修复)
目录 前言: 一、问题原因 二、算法修复 三、全代码 前言: 在之前的文章我已经实现了标题所述的内容功能:Unity URPShader:实现和PS一样的色相/饱和度调整参数效果-CSDN博客 但在偶然测试的时候,发现当采样的图片为…...
告别手动时代!物联网软件开发让万物自动互联
清晨,智能窗帘随着阳光自动拉开;运动时,手表精准记录着健康数据;回到家,室温早已调节至最舒适状态...这些场景的实现,都离不开物联网软件开发的技术支撑。在智能家居软件开发、智能穿戴软件开发、医疗器械软…...
Vue ui初始化项目并使用iview写一个菜单导航
winR 输入命令 vue ui浏览器会自动打开http://localhost:8000/ 找到创建 image.png 选择一个目录创建vue项目 image.png 点击再此创建新项目 image.png 我一般都是再已经有git仓库的目录进行项目创建,所以这个勾去掉 点击下一步 image.png 这里可以选择默认&#x…...
函数调用及Chain——SQL+GLM
Langchainchain数据库操作_langchain 操作数据库-CSDN博客 本文和基于上述链接 进一步。 初始化数据库&模型 # temperature0,此处仅需要SQL语句,不需要多样化返回。 from langchain.chains.sql_database.query import create_sql_query_chain from …...
数据科学与计算
Seaborn的介绍 Seaborn 是一个建立在 Matplotlib 基础之上的 Python 数据可视化库,专注于绘制各种统计图形,以便更轻松地呈现和理解数据。 Seaborn 的设计目标是简化统计数据可视化的过程,提供高级接口和美观的默认主题,使得用户…...
【AI提示词】二八法则专家
提示说明 精通二八法则(帕累托法则)的广泛应用,擅长将其应用于商业、管理、个人发展等领域,深入理解其在不同场景中的具体表现和实际意义。 提示词 # Role: 二八法则专家## Profile - language: 中文 - description: 精通二八法…...
PostgreSQL Patroni集群组件作用介绍:Patroni、etcd、HAProxy、Keepalived、Watchdog
1. Watchdog 简介 1.1 核心作用 • 主节点故障检测 Watchdog 会定时检测数据库主节点(或 Pgpool 主节点)的运行状态。 一旦主节点宕机,它会发起故障切换请求。 • 协调主备切换 多个 Pgpool 节点时,Watchdog 保证只有一个 Pg…...
【计算机视觉】图像分割:Segment Anything (SAM):通用图像分割的范式革命
Segment Anything:通用图像分割的范式革命 技术突破与架构创新核心设计理念关键技术组件 环境配置与快速开始硬件要求安装步骤基础使用示例 深度功能解析1. 多模态提示融合2. 全图分割生成3. 高分辨率处理 模型微调与定制1. 自定义数据集准备2. 微调训练配置 常见问…...
改进系列(10):基于SwinTransformer+CBAM+多尺度特征融合+FocalLoss改进:自动驾驶地面路况识别
目录 1.代码介绍 1. 主训练脚本train.py 2. 工具函数与模型定义utils.py 3. GUI界面应用infer_QT.py 2.自动驾驶地面路况识别 3.训练过程 4.推理 5.下载 代码已经封装好,对小白友好。 想要更换数据集,参考readme文件摆放好数据集即可,…...
大型连锁酒店集团数据湖应用示例
目录 一、应用前面临的严峻背景 二、数据湖的精细化构建过程 (一)全域数据整合规划 (二)高效的数据摄取与存储架构搭建 (三)完善的元数据管理体系建设 (四)强大的数据分析平台…...