LeetCode每日一题 共十题
902. 最大为 N 的数字组合(2022.10.18)
题目详情
解题思路
设n的长度为n_Length,digits的长度为digits_Length,从digits取值所组+成的结果值digits_num的位数记为k(k取1,2,3,……,n_Length)。
当k<n_Length时,其满足条件的结果为digits_Length的k次方,将每个结果相加存到ans中得到其中一部分结果;
当K==n_Length时,则从digits_num的最高位开始依次考虑,记当前位为第k位(第1位表示个位):
1.如果digits_num的第k位(最高位为k位)的值小于n的第k位的值,那么表示digits_num中其余位可以从digits中任取,则共计digits_Length的k-1次方的结果符合条件,将其算入ans中。
2.如果digits_num的第k位的值等于n的第k位的值,那么就考虑digits_num的后一位与n的后一位的大小关系,即k的值减少1,直到考虑到digits_nums的最低位。(注意:当digits_num的最低位和n的最低位相等时,说明此时组成的digits_num刚好等于n,也需要加入总结果的ans中。边界值问题 )
3.如果digits_num的第k位的值大于n的第k位的值,那么不论后面位取什么都不会满足条件。
代码部分
class Solution {
public:
int atMostNGivenDigitSet(vector<string>& digits, int n) {
int digits_Length = digits.size();
int n_Length = 0; //得到n的位数
int ans = 0;
int n_digit[10]; //存n的每一位数字
while(n/10 != 0){ //将n的每一位存到n_digit中
n_digit[n_Length++]=n%10;
n/=10;
}
if(n!=0) n_digit[n_Length++]=n;
//当组成的位数小于n的位数时,其结果如下
for(int i = n_Length-1;i>0;i--){
ans += pow(digits_Length,i);
}
//当组成的位数等于n的位数时,需要分三种情况进行处理
int j = n_Length-1;
for(int i = 0;i<digits_Length;i++){
//边界值
if(j == -1 ){
ans++;
break;
}
//如果取的数小于n的当前位数
//atoi(digits[i].c_str())是将digit[i]从string类型转为整型
if(atoi(digits[i].c_str())<n_digit[j]){
ans += pow(digits_Length,j);
}
//如果取的数等于当前位数
else if(atoi(digits[i].c_str())==n_digit[j]){
j--;
i = -1;
}
else break;
}
return ans;
}
};
1700. 无法吃午餐的学生数量(2022.10.19)
题目详情
解题思路
使用i,j分别作为students和sandwiches的指针。按照题目所给的思路:
- 如果队列最前面的学生 喜欢 栈顶的三明治,那么会 拿走它 并离开队列。
- 否则,这名学生会 放弃这个三明治 并回到队列的尾部。
- 这个过程会一直持续到队列里所有学生都不喜欢栈顶的三明治为止。
那么需要解决的问题有以下三个:
- 如何表示学生 放弃这个三明治 并回到队列的尾部?
- 当学生喜欢三明治时(students[j] == sandwiches[i]),使用何种方法表示出栈?
- 怎么去判断队列里的所有学生都不喜欢栈顶的三明治?
我的解题方法是:
- 通过移动指针 j 来实现队列循环,当移动前的指针 j 指到 students 的末尾时,将指针 j 指向 students 的起始位置即可。
- 出栈可以将指针 i 后移一位。
- 使用 Vector 的 erase() 函数删除当前匹配到的学生students[j],此时指针 j 的变换情况有两种:如果删除的位置不是students的末尾的话,则不需要移动指针 j;反之则需要将指针 j 指向初始位置。
- 使用一个bool类型的变量 change 来判断队列里的所有学生都不喜欢栈顶的三明治。当一次队列循环(从students的起始位置到结束位置为一个循环)内有学生喜欢三明治时,将 change 置为true;当一次队列循环结束后,判断 change 是否为true,如果为true则将其置为false并继续下一轮队列循环,否则结束队列循环过程。
代码部分
class Solution {
public:
int countStudents(vector<int>& students, vector<int>& sandwiches) {
int j = 0; //student的指针
int i = 0; //sandwiches的指针
bool change = false; //用于判断一轮循环后是否有学生拿了三明治
int ans = students.size(); //初始结果位学生的总人数,用于返回最终结果
while(i != sandwiches.size()){
//当三明治栈顶被拿走时, 学生长度减少一个,输出结果减少一个,栈顶退出:i++
if(students[j] == sandwiches[i]){
//出栈只需让i++
i++;
//匹配到就直接删除该元素,j就会自动指向下一个
students.erase(students.begin() + j);
//如果j当前指向的是末尾元素,要将j归于0号位置
if(j==students.size()) j=0;
//匹配到后无法吃到午餐的学生数量要减少一个
ans--;
change = true;
}
//不匹配时只要改变student的指针j,并判断是否在student循环了一圈后仍然未有sandwiches出栈
else{
if(j<students.size()-1){
j++;
}
else{
if(change){
change = false;
j = 0;
}
else break;
}
}
}
return ans;
}
};
779. 第K个语法符号(2022.10.20)
题目详情
解题思路
找规律:
第一行:0
第二行:01
第三行:01,10 (第一个对称轴【第2个数与第3个数之间】是镜像)
第四行:01,10,10,01 (第二个对称轴【第4个数与第5个数之间】是异或)
第五行:01,10,10,01,10,01,01,10 (第三个对称轴【第8个数与第9个数之间】是镜像)
第六行:01,10,10,01,10,01,01,10,10,01,01,10,01,10,10,01
可以发现在前六行中满足:
从当前行到下一行,数量翻倍,下一行的前半部分等于上一行,下一行变换(除了第一行到第二行以外)只有两种情况:
i.从偶数行到奇数行时,后半部分与前半部分关于中间位置是单个数分别对称
ii.从奇数行到偶数行时,后半部分与前半部分关于中间位置是以两个数为一组分别对称
因此猜测在之后所有行都满足这个关系,该结论可以通过数学归纳法予以证明。
对于其中的 情况ii 还可以进一步转化为,对于单个数而言,是关于中心位置对称的异或关系(即如果后半部分的一个数为1,那么与其关于中心位置对称的数就是0)
根据以上所述,对于第n行的第k个字符的值的求法,显然不论n为多少,都不影响最终的结果,只有k变量才会影响最终结果。
那么我们只需不断通过对称关系(数值上要么相同要么异或)将 第一位或者第二位对应的数 与 原本k所指的数 联系起来,不断减少k的值,直到k<=2时,就能确定原本的k所指的数与第一个数或者第二个数的关系。
代码部分
class Solution {
public:
int kthGrammar(int n, int k) {
if(k==1) return 0;
if(k==2) return 1;
int ans; //存放结果
int m ; //m=1表示是第三行的对称轴,
//进一步转化为m为单数,对称关系就为镜像,m为偶数,对称关系为奇数,通过对称不断缩小k的大小,直至k=1或者k=2
bool turn_over = false; //用变量turn_over来表示,对称之后的数是否需要进行异或操作来改变结果
int minnor_k = k;
while(minnor_k>2){
//找到2^m<k中m的最大值,即找到log(2)k>m中m的最大整数解
m = log2(minnor_k-1); //向下取整,代数即可理解
minnor_k = pow(2,m+1) - minnor_k+1;
if(m%2==0){
turn_over = !turn_over;
}
}
if(minnor_k==1) ans = turn_over?1:0;
if(minnor_k==2) ans = turn_over?0:1;
return ans;
}
};
代码总结反思
在写完这段代码后,发现还可以做进一步的优化:
如果利用对称关系貌似可以进一步将对称轴改成从第二行的数字开始(代码里面是从第三行开始),即只用第一个位置的数值并通过关系判断也能解出本题,只需改变部分变量的值应该可以做到。
901. 股票价格跨度(2022.10.21)
题目详情
解题思路
题目的解释部分过于简洁,以下是我对于题目的诠释:
今天股票价格的跨度指的是:从今天开始往回数,有多少个连续的天数的股票价格是少于或等于今天的。其中天数要取最大值,如果在这之前的一天是大于今天的则返回1.
例如:如果未来7天股票的价格是 [100, 80, 60, 70, 60, 75, 85]
那么
- 第一天的股票价格跨度是1,因为在昨天没有股票价格的。
- 第二天的股票价格跨度是1,因为在昨天的股票价格是100,大于今天的价格。
- 第三天的股票价格跨度是1,因为在昨天的股票价格是80,大于今天的价格。
- 第四天的股票价格跨度是2,因为在昨天的股票价格是60,小于今天的价格,但前天价格是80,大于今天的70,因此跨度为1(本身的一天)+1(昨天)
- 第五天的股票价格跨度是1,因为昨天的股票价格是70,大于今天的价格,因此跨度是1.
- 第六天的股票价格跨度是4,因为往前数且连续的三天内的价格都是小于今天的,因此跨度是1+3=4
- 第七天的股票价格跨度是6,因为往前数且连续的五天内的价格都是小于今天的,因此跨度是1+5=6
根据上述可以发现,我们只需要一直往前找,直到找到一个比当前插入的值大的数之后才返回一个股票价格的跨度值。
因此,一个显而易见的解题方法为:
直接定义一个int类型的数组用于存放所有的股票,从当前天数依次前遍历直到找到大于今天股票价格的那天(方法1:暴力解法)。
在本题中,因为我们只需要关心的是股票价格的跨度,而对于每一天的价格,只是为了与今天价格相比较而存储下来。但又因为只需要比较的是连续小于今天股票价格的最大天数,因此在往前比较过程中,只要碰到一个价格大于今天的日子就终止比较,而在这一天之前的价格都不需要比较。如果对于这部分进行优化,可以大大降低存储空间。
在前面的例子中,关于第七天的股票价格跨度的确定:因为第六天的股票价格跨度是4,说明在第六天时,往前三天的股价都是小于或等于第六天的。如果第七天的股价大于第六天,这也说明第七天的股价同时小于或等于第六天的股价所小于或等于且连续的那三天。
利用这一特点并结合栈的特性,可以使用以下方法:
将当天的价格与跨度以键值对的方式存储到栈中,规则有以下几点:
- 初始时,每个待入栈的元素的股价跨度都为1。
- 栈空,直接入栈。
- 如果待入栈的元素的股价小于栈顶,则直接入栈,且其股价跨度为1。
- 如果待入栈的元素的股价大于或等于栈顶,则将栈顶元素出栈,且其股价跨度=出栈元素的股价跨度+待入栈的股价跨度。再次比较栈顶元素进行该操作直到待入栈元素的股价小于栈顶元素的股价。
代码部分
方法1:暴力解法
class StockSpanner {
public:
StockSpanner() {
index = -1;
}
int next(int price) {
Stockes[++index] = price;
int ans = 1;
if(index == 0 ) return ans;
else{
int i = index - 1;
while(Stockes[i] <= Stockes[index]){
ans++;
if(i>0) i--;
else break;
}
}
return ans;
}
int index; //数组的当前指针
int Stockes[15000] ;
};
方法2:单调栈方法
单调栈指的是栈内数据大小依次递增或递减
class StockSpanner {
public:
StockSpanner() {
}
int next(int price) {
pair<int,int> s(price,1); //股价和股价跨度以键值对形式保存
if(Stockes.empty()){ //栈空,直接入栈
Stockes.push(s);
}
else{
//当price>=栈顶元素的股价时进行出栈,且股价跨度相加
while(!Stockes.empty() && price >= Stockes.top().first){
s.second = s.second + Stockes.top().second;
Stockes.pop();
}
//找到最大连续的日子小于或等于今天的股价之后进行入栈
Stockes.push(s);
}
return s.second;
}
private:
stack <pair<int,int>> Stockes; //以股价(frist)、股价跨度(second)作为键值对,构成栈内元素
};
1235. 规划兼职工作(2022.10.22)
题目详情
解题思路
在写今天题目之前,首先得学习一下什么是 DP(Dynamic Programing)算法 ,即 动态规划 。具体参考文章为 博客园 网站中 博主名为 我的明天不是梦 写的一篇文章:动态规划——DP算法(Dynamic Programing)
在本文中引用该文章中对于 DP算法 的 思想简介 :
动态规划算法——思想简介
- DP算法思想
- 将待求解的问题分解称若干个子问题,并存储子问题的解而避免计算重复的子问题,并由子问题的解得到原问题的解。
- 动态规划算法通常用于求解具有某种最有性质的问题。
- 动态规划算法的基本要素: 最优子结构性质 和 重叠子问题 。
最优子结构性质 :问题的最优解包含着它的子问题的最优解。即不管前面的策略如何,此后的决策必须是基于当前状态(由上一次的决策产生)的最优决策。
重叠子问题 :在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些问题被反复计算多次。对每个子问题只解一次,然后将其解保存起来,以后再遇到同样的问题时就可以直接引用,不必重新求解。
- DP算法——解决问题的基本特征
- 动态规划一般求解最值(最优、最大、最小、最长)问题;
- 动态规划解决的问题一般是 离散的 ,可以分解的(划分阶段的);
- 动态规划结局的问题必须包含最优子结构,即可以有(n-1)的最优推导出n的最优。
- DP算法——解决问题的基本步骤
动态规划算法的四个步骤:
- 刻画最优解的结构特性。(一维、二维、三维数组);
- 递归的定义最优解。(状态转移方程);
- 以自底向上的方法来计算最优解;
- 从计算得到的解来构造一个最优解。
回到本题中,首先将三个容器放入同一个新的容器 jobs 中,得到一共有n份工作。其中,对于二维容器 jobs 的初始化过程,使用vector的push_back()方法,分别将三个一维容器放入jobs中。
接着将这n份工作按照 endTime 容器对应的数值进行升序排列,得到一个按照结束时间排序的jobs容器。(对于jobs[a][b]而言,a的取值范围是0~n-1,b的取值范围是0~2)
参照上述DP算法为本题定义一个状态函数:
d
p
[
i
]
=
m
a
x
(
d
p
[
i
]
,
d
p
[
j
]
+
j
o
b
s
[
i
]
[
2
]
)
dp[i]=max(dp[i], dp[j] + jobs[i][2])
dp[i]=max(dp[i],dp[j]+jobs[i][2])
其中:对于 jobs[a][b] ,当 b=0,1,2 时分别表示容器 startTime , endTime , profit 。从第一份工作开始遍历到第n份工作,其中 i 指的是第i份工作。dp[i]则表示在遍历到第i份工作时,可取得的最大报酬。dp[j] 表示结束时间小于等于第 i 份工作开始时间的最大报酬。
对于如何找到 j 的所在位置,本章采用从第 i-1 份工作开始往前进行查找。
有关为何采用上述决策,参考:动态规划 + 二分查找优化
代码部分
class Solution {
public:
static bool cmp(const vector<int> &a, const vector <int> &b){
return a[1] < b[1];
}
int jobScheduling(vector<int>& startTime, vector<int>& endTime, vector<int>& profit) {
vector<vector<int>> jobs; //将每份工作的StartTime、endTime、profit存储到一起,以方便操作
//jobs[a][b] 表示第b个vector中的第a个元素
int job_num = startTime.size(); //得到总共有多少份工作
//将所有的数据存入jobs中
for(int i = 0;i<job_num;i++){
jobs.push_back({startTime[i],endTime[i],profit[i]});
}
//按照endTime升序排列
sort(jobs.begin(),jobs.end(),cmp);
//状态函数dp的定义,初始值为0
/* dp状态函数,dp[i]=max(dp[i-1],dp[j]+jobs[1][i]) 其中j为结束时间小于等于第 i−1 份工作开始时间的*/
int dp[job_num+1];
dp[0] = 0;
dp[1] = jobs[0][2];
for (int i = 1; i < job_num; i++) {
//找到结束时间小于等于第 i 份工作开始时间的工作j
int j;
for (j = i - 1; j >= 0; j--) {
if (jobs[j][1] <= jobs[i][0]) {
j++;
break;
}
}
if(j==-1)
j++;
dp[i + 1] = max(dp[i], dp[j] + jobs[i][2]);
}
return dp[job_num];
}
};
代码总结反思
- 在写代码过程中,对于二维Vector的使用方法不太清楚,如本题中jobs[a][b]的含义。其含义为容器jobs的第 b+1 个容器的第 a+1 个元素。
- 对于二维Vector,使用sort()对其进行排序的规则不清楚,解决方法参考:二维vector数组排序问题
- 在如何确定 j 的值的时候,首先是采用从第 1 份工作遍历到第 i 份工作的遍历方法,在使用这种方法的时候未曾考虑:对于多个结束时间相同的工作,应该选取最后一个结束时间不大于第i份工作开始时间的dp值。同时未曾切合实际题目,导致时间上的开销过大,最终在LeetCode运行结果为超时。最后采用从后遍历的方法进行查找,大大降低了时间复杂度。
- 使用二分查找算法用于确定 j 的值相对于使用从后遍历的方法在时间复杂度上的表现要更好。
1768. 交替合并字符串(2022.10.23)
题目详情
解题思路
容易想到的一个思路是:使用i,j分别作为word1,word2的指针,依次遍历将结果存储到新的string类型的变量 ans 中,当 i 和 j 都超出对应的范围后,结束循环并返回答案即可。假设 word1 的长度为 n , word2 的长度为 m (之后关于复杂度分析中,长度假设都如此),则该方法的时间复杂度为 o(n+m) ,空间复杂度为 o(n+m) 。
有关时间复杂度与空间复杂度的含义可参考:时间复杂度和空间复杂度(C站最详细的)
考虑到C++中的string 类型变量可以进行修改,因此只需判断word1,word2哪个更长,将 较短 的字符串插入到 较长 的字符串中即可提高运算效率。
有关LeetCode中string类型的使用方法可参考:STL-string(leetcode题库常见操作总结)
采用以上方法,可以将时间复杂度降为 o(n)或者o(m),空间复杂度为 o(1)。
第二种方法则是,不论word1,word2 哪个更长,都将word2中的字符串交替插入word1中。此时,如果word1更长,则不用进行任何操作,返回word1即可。反之,则需要将 word2 中剩余的未插入的部分直接附加到word1的末尾,可以使用append()方法。
采用以上方法,时间复杂度也为 o(n)或者o(m) ,空间复杂度为 o(1)
代码部分
方法1:
class Solution {
public:
string mergeAlternately(string word1, string word2) {
int word1_size = word1.size();
int word2_size = word2.size();
/* 版本1*/
if(word1_size >=word2_size){
int j = 0;
//需要进行插入的部分就是 较短的字符串 的两倍长度,后面剩余的不用管
for(int i =1;i<word2_size*2;i=i+2){
word1.insert(word1.begin()+i,word2[j]);
j++;
}
return word1;
}
else{
int i = 0;
for(int j=0;j<2*word1_size;j=j+2){
word2.insert(word2.begin()+j,word1[i]);
i++;
}
return word2;
}
}
};
方法2:
class Solution {
public:
string mergeAlternately(string word1, string word2) {
int word1_size = word1.size();
int word2_size = word2.size();
int size = word1_size >= word2_size ? word2_size : word1_size;
int j = 0;
//需要进行插入的部分就是 较短的字符串 的两倍长度,后面剩余的按情况进行操作
for(int i = 1;i < 2*size;i=i+2){
word1.insert(word1.begin()+i,word2[j]);
j++;
}
if(j < word2_size)
word1.append(word2.substr(j,word2_size));
//word1 = word1 + word2.substr(j,word2_size);也可
return word1;
}
};
915. 分割数组(2022.10.24)
题目详情
解题思路
设 left_max为 left 数组中的最大值,read_len 为 已经遍历过 的 但未确认是 属于left数组 的 数组长度,n 为数组 nums 的大小,i 为当前遍历数组 nums 的位置。则:
-
依据题目所述,当 left 数组中的最大值 left_max 小于 right 数组中的最小值即可 确定结果。同时,可以确认的是由 left 数组 与 right 数组可以组成 nums 数组。也就是说,如果从右往左遍历 nums 数组,首先经过 left 数组,接着经过 right 数组。
-
当我们确认了 left_max 的值时,循环遍历一次的 nums 数组,一定会有:从某个位置开始,之后的数都是大于 left_max 的。
-
而对于 left_max 值的确认,可以采用以下的思想:
假定 nums[0] 为 left_max ,通过循环遍历不断比较下一个 nums 的值,当 nums[i] 的值大于 left_max,且 nums[i] 仍然在 left 数组中,则更新 left_max。
那么,如何确定当前的 i 是处在 left 数组所属的范围内呢?即找到 left 与 right 的分界点。我们可以利用 2. 所示内容。由此得到以下策略:
- 假定 nums[0] 为 left_max ,比较 left_max 与 nums[i] 的值。
- 当 left_max > nums[i] ,说明当前位置肯定不是两个数组的分界点。
- 当left_max <= nums[i] , 需要比较后续的未遍历过的 nums 数组中的值才能确认。如果后面的值都是大于或等于 left_max 的,说明当前位置就是分界点;在确认的过程中,只要有一个值是不满足大于或等于 left_max ,则说明当前的位置还在 left 数组中。在判断的过程中我们需要记录所有已经遍历过的数组中最大的那个值,当确认了当前位置还在 left 数组中的时候,更新 left_max 的值。
- 对于 left 数组的长度,我使用了一个辅助变量 read_len 用于记录已经遍历过的数组的长度,每当 left_max 有可能要改变的时候,其实相当于是确定了 已遍历过的数组 中都是属于 left 数组的,因此需要将 read_len 存入 结果 ans 中,并对 read_len 做出相应的改变。
- 通过改变 i 的值,让 i 始终指向还未曾遍历过的数组坐标。因为在 策略3 中,为了确认当前位置是否为分界点的过程中,始终存储了当前已经遍历过的数组中的最大值,并且确定了 执行本次策略所遍历的数组 部分是否为 left 数组的部分,因此不需要回溯,再去判断 left_max 与 nums[i] 的关系。
采用上述决策,因为对于长度为 n 的 nums 数组只需遍历一边,同时辅助变量为常数个,故时间复杂度为o(n),空间复杂度为o(1)。
代码部分
class Solution {
public:
int partitionDisjoint(vector<int>& nums) {
int left_max = nums[0]; //得到left数组当前最大值
int read_len = 1; //得到已经遍历过的 但未确认是属于left数组的 数组长度
int ans = 0;
int n = nums.size();
for (int i = 1; i < n; i++) {
//如果当前left_max 大于遍历到的值,说明肯定没有找到
if (left_max > nums[i]) {
read_len++;
}
//当left_max 小于等于遍历到的值,则说明有可能找到,需要将这个值去从[i,n]中对比
//如果没有比这个值小的说明当前位置找对了,如果有说明当前位置不对。
//对于前面长度为read_len的部分是可以确认属于left数组的
else if (left_max <= nums[i]) {
ans += read_len;
read_len = 1;
int maybe_left_max = nums[i]; //用于存储在遍历判断过程中可能出现的新的最大值
//找剩下的[i,nums.size()]中
int j;
for (j = i + 1; j < n; j++) {
if (maybe_left_max < nums[j]) {
maybe_left_max = nums[j];
}
read_len++;
//当发现有比最大值小的值之后,说明当前位置不是,于是让i从j的当前位置的下一个开始
if (left_max > nums[j]) {
ans += read_len;
left_max = maybe_left_max;
read_len = 0;
break;
}
}
i = j;
}
}
return ans;
}
};
934. 最短的桥(2022.10.25)
题目详情
解题思路
由于该题涉及到 图 结构中的 DFS(深度优先算法) 和 BFS(广度优先算法) ,笔者对其掌握程度还不够熟练,本题暂时跳过
代码部分
//略
862. 和至少为 K 的最短子数组(2022.10.26)
题目详情
解题思路
思路1:
-
找到数列中和的值最大的子数列,得到其长度:
(1).遇到正数就加(假设变量为a),当遇到负数的时候,判断当前负数与前面所有的和值a相加之后是否大于0:
i.如果大于0就继续下一关判断,回到(1)
ii.如果小于0,舍弃前面部分数组,并且让数组长度做出相应改变 -
判断该子数列的和值是否小于k:如果小于k直接返回-1,否则进行第三步。
-
找到其中满足条件的最短非空子数组:
(1)当从 [p,r] 范围内没有负数的时候(其中 p 为 1. 中所求的和值最大子数列的起始坐标,r 为结束坐标),可以使用以下方法进行数组筛选:
i.如果 nums[p] > nums[r] ,从尾减少一个长度
ii.如果 nums[p] < nums[r] ,从头减少一个长度
iii.如果二者相等,减少任意一边皆可。(2) [p,r] 范围内有负数的时候
i. 假设在范围内的负数的坐标都存放在 Ne_idx 容器 Vector 中,从中取第一个元素,那么从 [ p , Ne_idx[0] ] 范围内的和值记为 left , 从 **[Ne_idx[0],r]**范围内的和值记为 right 。
ii.比较 left 与 right 的值大小,如果 left 大则删除 right ,如果 right 大则删除 left ,直至找到最短的子数列。
思路2:
待完成…
代码部分
思路1:
int shortestSubarray(vector<int>& nums, int k) {
int pre = 0, rear = -1; //头、尾指针
int sum = 0; //当前和值
int max = 0; //子数列最大值
int p, r;
vector<int> Ne_idx; //负数值的坐标
for (int i = 0; i < nums.size(); i++) {
if (nums[i] >= 0) {
sum += nums[i];
rear++;
}
else {
Ne_idx.push_back(i);
if (sum > max) {
max = sum;
p = pre;
r = rear;
}
if (sum + nums[i] > 0) {
rear++;
sum += nums[i];
}
else {
if (sum > max) {
max = sum;
p = pre;
r = rear;
}
//当前i位置的值是要跳过的 因此pre向前移动两个位置
rear ++;
pre = rear;
pre++;
sum = 0;
}
}
}
if (sum > max) {
max = sum;
p = pre;
r = rear;
}
if (max < k) return -1;
else {
int len = 0; //记录最后一次删除的长度
//如果(q,r)范围内有负数值,采用另一种判断方法
for (int i = 0; i < Ne_idx.size(); i++) {
if (Ne_idx[i]<p || Ne_idx[i]>r) {
Ne_idx.erase(Ne_idx.begin()+i);
i--;
}
}
while (max >= k) {
if (Ne_idx.empty()) {
if (nums[p] > nums[r]) {
max -= nums[r];
r--;
}
else {
max -= nums[p];
p++;
}
len = 1;
}
else {
int left_len = 0; //得到从左开始数连续的负数长度
int right_len = 0; //得到从右开始数连续的负数长度
int Ne_p = 0;
for (int i = 1; i < Ne_idx.size(); i++) {
Ne_p = Ne_idx[i - 1];
if (Ne_p + 1 == Ne_idx[i]) {
left_len++;
}
else {
break;
}
}
for (int i = Ne_idx.size()-2; i >=0; i--) {
Ne_p = Ne_idx[i + 1];
if (Ne_p - 1 == Ne_idx[i]) {
right_len++;
}
else {
break;
}
}
int r_index = Ne_idx.size() - right_len - 1;
int l_index = left_len;
int left = accumulate(nums.begin() + p, nums.begin() + Ne_idx[l_index]+1, 0);
int right = accumulate(nums.begin() + Ne_idx[r_index], nums.begin() + r + 1, 0);
if (left > right) {
//右边和值与左边比较大小,坐标做出相应改变
if (right <= nums[p]) {
max -= right;
len = r - Ne_idx[r_index] + 1;
r = Ne_idx[r_index] - 1;
//删除属于右边的部分
Ne_idx.erase(Ne_idx.begin() + r_index, Ne_idx.end());
}
else {
if (nums[p] > nums[r]) {
max -= nums[r];
r--;
}
else {
max -= nums[p];
p++;
}
}
}
//左边和值与nums右边进行比较,坐标做出相应改变
else if (left < right) {
//删除较小的左边值
if (left <= nums[r]) {
max -= left;
len = Ne_idx[l_index] - p + 1;
p = Ne_idx[l_index] + 1;
Ne_idx.erase(Ne_idx.begin(), Ne_idx.begin() + l_index + 1);
}
else {
if (nums[p] > nums[r]) {
max -= nums[r];
r--;
}
else {
max -= nums[p];
p++;
}
len = 1;
}
}
else {
//如果左边和值长就删左边
if (Ne_idx[0] - p > r - Ne_idx[Ne_idx.size() - 1]) {
if (left > nums[r]) {
if (nums[p] > nums[r]) {
max -= nums[r];
r--;
}
else {
max -= nums[p];
p++;
}
len = 1;
}
else {
max -= left;
len = Ne_idx[l_index] - p + 1;
p = Ne_idx[l_index] + 1;
Ne_idx.erase(Ne_idx.begin(), Ne_idx.begin() + l_index+1);
}
}
//否则删右边
else if (Ne_idx[l_index] - p < r - Ne_idx[r_index]) {
if (right > nums[p]) {
if (nums[p] > nums[r]) {
max -= nums[r];
r--;
}
else {
max -= nums[p];
p++;
}
len = 1;
}
else {
max -= right;
len = r - Ne_idx[r_index] + 1;
r = Ne_idx[r_index] - 1;
//删除属于右边的部分
Ne_idx.erase(Ne_idx.begin() + r_index, Ne_idx.end());
}
}
//左右长度相等时
else {
if (nums[p] > nums[r]) {
max -= nums[r];
r--;
len = 1;
}
else {
max -= nums[p];
p++;
len = 1;
}
len = 1;
}
}
}
}
return r - p + len + 1;
}
}
思路2:
待完成
总结反思(发现了一点自身编程思维上的错误 )
思路1:
- 对于步骤1. 、 2. 部分完成的较好
- 对于步骤 3. ,并没有在逻辑上证明其可行性(使用数学思维验证该方的正确性),仅仅是针对与测试集中出现的错误进行补丁操作,最后使得代码变成 “屎山”。 该解题方法在往后编程学习中切不可取,因在数学上证明该方法的可行性之后再去进行代码编写,否则到最后变成一场空。
- 对于整体而言 ,重复的代码比较多,按照 软件设计 的设计思想,应将代码所对应的功能进行剥离,单独成为一个方法,使得整个代码在维护、更新、复用的时候更加便捷。
1822. 数组元素积的符号(2022.10.27)
题目详情
解题思路
当数组中出现一个 0 ,那么乘积结果必定是 0,返回值也是0.
如果在遍历过程中遇到小于0的数就让 sign=!sign ,其中 sign 用来标识负号是 奇数 个还是 偶数 个。
最后通过判断 负号个数的 奇偶性 来确定最终的返回值。
其中涉及到Vector容器的遍历,可参照以下两篇文章:
代码部分
int arraySign(vector<int>& nums) {
bool sign = true;
for(auto i:nums){
if(i==0) return 0;
else sign = i > 0 ? sign : !sign;
}
return sign?1:-1;
}