今天练了三道题,难度范围:★~★★★。
一开始属于是玩一会练一会,所以没那么煎熬,也感觉没练多久,但是我还要复习期末考(倒),所以只写了两道题,就想先将题放一放,虽然感觉还能写一题的。
是的,我一开始只写了两道题,后面真的还想写,就又写了第三题,没想到我居然会很想练题,不知道是不是不想复习,其实还想再写一题。
今日收获
1.将情况考虑完整,基于什么条件划分的问题,还需要想到这个条件之外的情况
2.写完之后可以观察返回值,说不定可以简化问题
3.连续数字求和------可以使用滑动窗口法,减去原来的第一个元素,新添一个最后的元素。
一.种花问题 ★★★☆☆
题目
605. 种花问题 假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去。
给你一个整数数组 flowerbed 表示花坛,由若干 0 和 1 组成,其中 0 表示没种植花,1 表示种植了花。另有一个数 n,能否在不打破种植规则的情况下种入 n朵花?能则返回 true ,不能则返回 false 。
思路
将空着的花坛分为三种:第一朵花之前的、最后一朵花之后的、中间的。因为可以得知第一朵花之前的以及最后一朵花之后的空着的花坛数若为x,则能种下x/2朵花;中间的空着的花坛数若为x,则能种下(x-1)/2朵花。由此可以写出代码1。
index1表示第一朵花之前的空着的花坛数;flowerbed.size()-1-index2表示最后一朵花之后的空着的花坛数。通过循环可以找出index1和index2的值。用res表示能种下的花的朵数。
中间的空花坛能种下的花朵数,需要记录两朵花之间的空花坛数count,然后计算出count能种下的花的朵数(count-1)/2,不断循环至将整个数组遍历完。
最后直接返回res>=n的结果即可。
代码1
cpp
class Solution {
public:
bool canPlaceFlowers(vector<int>& flowerbed, int n) {
int res=0;
int index1=0,index2=flowerbed.size()-1;
while(flowerbed[index1]!=1){
index1++;
}
while(flowerbed[index2]!=1){
index2--;
}
res+=(index1/2);
res+=(flowerbed.size()-index2-1)/2;
index1++;
while(index1<index2){
int count=0;
while(flowerbed[index1]==0 && index1<index2){
count++;
index1++;
}
res+=(count-1)/2;
index1++;
}
return res==n;
}
};
代码2
原来的思路和代码存在的问题:
1.计算index1和index2时没考虑到可能会越界:当index1++或index2--时可能会越界,索引需要增加限制条件index1<len 和 index2>=0
2.没有考虑数组全为0的情况:全0的情况无法被分为前中后三种,并且能种下的花的朵数和len的关系为 (len+1)/2,所以直接返回(len + 1) / 2 >= n。其次,当index1==len时,就说明数组全为0,所以在第一次循环后就可以处理全0的情况
cpp
class Solution {
public:
bool canPlaceFlowers(vector<int>& flowerbed, int n) {
int len=flowerbed.size();
int index1=0,index2=len-1;
//1.添加限定范围
while(index1<len && flowerbed[index1]!=1){
index1++;
}
//2.全0的情况
if(index1==len){
return (len + 1) / 2 >= n;
}
while(index2>=0 && flowerbed[index2]!=1){
index2--;
}
int res=0;
res+=(index1/2);
res+=(len-index2-1)/2;//3.结尾
index1++;
while(index1<index2){
int count=0;
while(flowerbed[index1]==0 && index1<index2){
count++;
index1++;
}
res+=(count-1)/2;
index1++;
}
return res>=n;
}
};
复杂度
N为数组flowerbed的长度
时间复杂度:O(N)。存在多个while循环,但所有循环的总执行次数之和为N,所以时间复杂度为O(N)
空间复杂度:O(1)。只有基本数据类型变量,占用的空间大小与输入数组的长度 N 无关,始终是常数级的空间
官方题解
官方的思路只有在处理两朵花之间 还可以种多少花的处理不同:设花坛的下标 i 和 j 种了花,其中 j-i >=2,且[ i+1,j-1]区间没有种花,则只有当 j-i>=4,区间内才可种花,且可以种植花的下标范围是 [i+2,j−2]。可以种植花的位置数是 p=j−i−3,最多可以在该范围内种植 (p+1)/2 朵花,即最多可以在该范围内种植 (j−i−2)/2 朵花。
其他情况处理方法一样。
代码
cpp
class Solution {
public:
bool canPlaceFlowers(vector<int>& flowerbed, int n) {
int count = 0;
int m = flowerbed.size();
int prev = -1;//表示上一朵已经种植的花的下标位置
for (int i = 0; i < m; ++i) {
if (flowerbed[i] == 1) {
if (prev < 0) {
//第一朵花之前可以种花的数量
count += i / 2;
} else {
//当前的花和上一朵花的区间内可以种的花的数量
count += (i - prev - 2) / 2;
}
prev = i;
}
}
if (prev < 0) {
//没有花的花坛能种多少花
count += (m + 1) / 2;
} else {
//最后一朵花之后还能种多少朵花
count += (m - prev - 1) / 2;
}
return count >= n;
}
};
优化
当可以种入的花的数量已经达到 n,则可以直接返回 true,不需要继续计算数组剩下的部分
cpp
class Solution {
public:
bool canPlaceFlowers(vector<int>& flowerbed, int n) {
int count = 0;
int m = flowerbed.size();
int prev = -1;
for (int i = 0; i < m; ++i) {
if (flowerbed[i] == 1) {
if (prev < 0) {
count += i / 2;
} else {
count += (i - prev - 2) / 2;
}
//当可以种入的花的数量已经达到 n,则可以直接返回 true
if(count>=n){
return true;
}
prev = i;
}
}
if (prev < 0) {
count += (m + 1) / 2;
} else {
count += (m - prev - 1) / 2;
}
return count >= n;
}
};
复杂度
N为数组flowerbed的长度
时间复杂度:O(N)。需要遍历数组一次。
空间复杂度:O(1)。额外使用的空间为常数。
二.三个数的最大乘积 ★★☆☆☆
题目
628. 三个数的最大乘积 给你一个整型数组 nums ,在数组中找出由三个数组成的最大乘积,并输出这个乘积。
思路
1.当数组长度刚好为3时,先直接返回数组元素的乘积。
2.然后,我将整体情况分为两种:1.全为非正数/非负数;2.正数、负数(零)都有。
2.1 先将数组进行升序排序操作,使得负数绝对值大的在前面,正数大的在后面;'
2.2 然后,当全为非正数/非负数时,直接返回最大的三个数的乘积即可。
2.3 正数和负数都有的时候,也分为两种情况:数组中有3个正数和数组中没有3个正数。
2.3.1 在数组中有3个正数时:设三个最大的正数相乘的乘积为product1,设两个绝对值最大的负数和最大的正数/零的乘积为product2,比较product1和product2,两者的较大值就是数组中三个数的最大乘积。
2.3.2 在数组中没有3个正数时:要么是两个负数相乘,再乘上一个正数/0,要么是乘数包含2/3个0,才能使得结果最大,无论哪一种,都是前面两个数和最后一个数相乘。
代码
cpp
class Solution {
public:
int maximumProduct(vector<int>& nums) {
int len=nums.size();
if(len==3){
return nums[0]*nums[1]*nums[2];
}
//排序
sort(nums.begin(),nums.end());
if(nums[0]>=0 || nums[len-1]<=0){
return nums[len-1]*nums[len-2]*nums[len-3];
}
//有3个正数
if(nums[len-1]>0 && nums[len-2]>0 && nums[len-3]){
int product1=nums[len-1]*nums[len-2]*nums[len-3];//最大的三个正数相乘
int product2=nums[0]*nums[1]*nums[len-1];//绝对值最大的两个负数和最大的正数相乘
//返回较大值
return (product1>product2)?product1:product2;
}
else{
//两负一正或两负一0
return nums[0]*nums[1]*nums[len-1];
}
return 0;
}
};
复杂度
N为数组长度
时间复杂度:O(NlogN)。排序函数的时间复杂度为O(NlogN)。
空间复杂度:O(logN)。排序的递归栈占用O(logN)空间。
优化
观察代码1可得出,最终返回的要么是nums[0]*nums[1]*nums[len-1],要么是nums[len-1]*nums[len-2]*nums[len-3],所以可以在排序后直接返回两者的较大值即可。
无论是哪种情况,最大乘积的组成肯定要么是最大的三个正数相乘 ,要么是最小的两个负数(绝对值最大的两个负数)与最大的正数相乘。这里的0并不特殊,因为即使存在0,结果无非就是包含0,也是乘积乘出来的。
cpp
class Solution {
public:
int maximumProduct(vector<int>& nums) {
//排序
sort(nums.begin(),nums.end());
int len=nums.size();
return max(nums[0]*nums[1]*nums[len-1],nums[len-1]*nums[len-2]*nums[len-3]);
}
};
复杂度还是一样。
官方题解------线性扫描
即使利用排序算法,我们也只需要最大的三个数和最小的两个数,所以也可以通过遍历一次数组来获取。
代码
cpp
class Solution {
public:
int maximumProduct(vector<int>& nums) {
//最小的数和第二小的数
int min1=INT_MAX,min2=INT_MAX;
//最大的数 第二小的数 第三小的数
int max1=INT_MIN,max2=INT_MIN,max3=INT_MIN;
for(int x:nums){
if(x<min1){
min2=min1;
min1=x;
}else if(x<min2){
min2=x;
}
if (x > max1) {
max3 = max2;
max2 = max1;
max1 = x;
} else if (x > max2) {
max3 = max2;
max2 = x;
} else if (x > max3) {
max3 = x;
}
}
return max(min1*min2*max1,max1*max2*max3);
}
};
复杂度
N为数组长度
时间复杂度:O(N)。遍历一次数组,循环N次。
空间复杂度:O(1)。
三.子数组最大平均数Ⅰ ★☆☆☆☆
题目
给你一个由 n 个元素组成的整数数组 nums 和一个整数 k 。
请你找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数。
任何误差小于 10-5 的答案都将被视为正确答案。
思路1------前缀和
利用前缀和数组 s,以索引为 i 的元素作为第一个元素,组成的子数组的元素总和为s[i+k-1]-s[i-1],找出最大值maxSum,最后返回maxSum*1.0/k
代码
cpp
class Solution {
public:
double findMaxAverage(vector<int>& nums, int k) {
int n=nums.size();
vector<int> s(n);
//前缀和
s[0]=nums[0];
for(int i=1;i<n;i++){
s[i]=s[i-1]+nums[i];
}
int maxSum=s[k-1];//数组前k个数
for(int i=1;i<n-k+1;i++){
int sum=s[i+k-1]-s[i-1];
if(sum>maxSum){
maxSum=sum;
}
}
return maxSum*1.0/k;
}
};
复杂度
N为数组nums的长度,k为连续子数组的长度
时间复杂度:O(N)。两次循环,第一次循环N次,第二次循环N-k次,所以整体的时间复杂度为O(N)+O(N-k)=O(N)。
空间复杂度:O(N)。前缀和数组的长度为N。
思路2------滑动窗口
(来源于豆包的提醒)利用滑动窗口。先将前k个元素总和作为连续子数组的元素总和sum的初始值,然后向后遍历数组的其他元素,移动连续子数组的区间,sum相当于减去最前面的元素nums[i-1],然后加上了新进来的元素nums[i+k-1],在每次改变sum后,都保证maxSum始终是最大的连续子数组元素和,最后返回平均值即可。
代码
cpp
class Solution {
public:
double findMaxAverage(vector<int>& nums, int k) {
int n=nums.size();
int sum=0;
for(int i=0;i<k;i++){
sum+=nums[i];
}
int maxSum=sum;
for(int i=1;i<n-k+1;++i){
sum=sum-nums[i-1]+nums[i+k-1];
if(sum>maxSum){
maxSum=sum;
}
}
return maxSum*1.0/k;
}
};
复杂度
N为数组nums的长度,k为连续子数组的长度
时间复杂度:O(N)。两次循环,第一次循环k次,第二次循环N-k次,所以整体的时间复杂度为O(1)+O(N-k)=O(N)。
空间复杂度:O(1)。