在开启这个新的专题之前,仍然照常写一些前言类的东西吧,这个专题本应该是我在完成学习C++这个专题之后再开始的,然而比赛将至,我目前觉得C++学习这个专题的内容暂时够用,所以不得不先放下C++基础的学习,转而学习算法,当然我还是觉得C++以及后续Linux系统的学习是我学习的主路线,所以这部分是必然不会放下的。那么到了算法这边,我觉得是一种新且不同的学习方法和内容,这可能对学习者个人的逻辑思维方面要求较高,总而言之是一种新的挑战,那么当然也希望在此过程中可以得到收获和提升。
1.移动零

这其实是一个数组划分的问题:

快速排序也是类似的思想,只不过快速排序要划分的是小于等于某个值和大于等于某个值。
题目的要求是把所有数组划分为0和非0的两块区域,为了使以上三个区间保持这个要求,那么先将cur放在0处,将dest放在-1处,当cur遇到非0数时dest++,将此非0数和dest所指位置调换,再cur++;当cur遇到0时直接跳过,cur++。
代码如下:
cpp
class Solution {
public:
void moveZeroes(vector<int>& nums)
{
for(int dest=-1,cur=0;cur<nums.size();cur++)
{
if(nums[cur]!=0)
{
swap(nums[++dest],nums[cur]);
}
}
}
};
2.复写零
如果这道题直接从前往后就地修改的话,逻辑上十分简单,但这么解答是错的,因为复写的数必然会覆盖没被复写的数。这个时候可以尝试从后往前去复写,但难点在于找到最后一个复写的数。找到最后一个复写的数可以通过模拟一遍从前向后复写来实现,其中一个细节是在cur不断向后扫描的过程中注意判断dest是否已经到达终点,也要搞明白终点的判断 :最后一个复写有可能是n-1处(复写非0数),也有可能是n处(复写0),如果满足到终点的条件则及时退出循环,cur不再++,保留此时的cur值,这就是最后一个复写的位置。还有一个细节要注意处理,若最后一个复写的数是0,则dest所指处必然是n,如果从此处开始从后向前复写,则数组越界访问,为了处理这个问题,则要将数组最后一个位置赋为0,将dest向后退后两步,cur向后退后一步,此后即可正常复写。
代码如下:
cpp
class Solution {
public:
void duplicateZeros(vector<int>& arr)
{
int dest=-1;
int cur=0;
int n=arr.size();
while(cur<n)
{
if(arr[cur]==0)
{
dest++;
}
dest++;
if(dest>=n-1)//看是否到尽头了,如果到了就不用再cur++了
{
break;
}
cur++;
}
if(dest>=n)
{
arr[n-1]=0;
dest-=2;
cur--;
}
while(cur>=0)
{
if(arr[cur]==0)
{
arr[dest--]=arr[cur];
}
arr[dest--]=arr[cur];
cur--;
}
}
};
3.快乐数

快乐数和非快乐数的演变其实都可以看成形成环的问题:

不形成环的情况是不存在的,这可以用鸽巢定理来证明:
题目的条件中有1<=n<=,取一个大于
的数9999999999,那么演变过程中最大的数为
,那么经过最多811次之后,一定会出现重复的数
只需要判断快慢指针入环后的值是1还是非1即可区分快乐数和非快乐数。
代码如下:
cpp
class Solution {
public:
bool isHappy(int n)
{
int slow=n;
int fast=func(n);
while(slow!=fast)
{
slow=func(slow);
fast=func(func(fast));
}
if(slow==1)
{
return true;
}
return false;
}
int func(int x)
{
int n=0;
while(x)
{
n+=(x%10)*(x%10);
x/=10;
}
return n;
}
};
4.盛最多水的容器

由于木桶原理,在宽度确定的情况下,决定盛水量的是较低的高度,先从两端看,左右已确定一个容量,则去除低的那端,因为低的那端再进行枚举,若遇到高的端,仍会取低的端;若遇到更低的端,则取更低的端。再加上本身宽度一定减小,枚举的结果都不会优于当前,故保留高端,低段向内移动。
代码如下:
cpp
class Solution {
public:
int maxArea(vector<int>& height)
{
int left=0,right=height.size()-1;
int Area=0;
while(left<right)
{
int sum=min(height[left],height[right])*(right-left);
Area=max(Area,sum);
if(height[left]<height[right])
{
left++;
}
else
{
right--;
}
}
return Area;
}
};
5.查找总价格为目标值的两个商品
从这道题开始,接下来的几道题基本都是同样的思想,但细节处理方面有不同,逐层递进。
LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode)

如果是暴力枚举,代码逻辑并不复杂,就是时间复杂度较高,用双指针的方法时间复杂度会低很多。首先将数组按升序排好序,数组头和尾分别作为left指针和right指针开始的地方,先判断此时left所指位置和right所指位置相加是否等于目标值,若大于目标值,则right--(因为根据数组的单调性,除left所指位置的值以外,其他位置的值与right所指位置的值相加必定大于目标值),若小于目标值,则left++(因为根据数组的单调性,除right所指位置的值以外,其他位置的值与left所指位置的值相加必定小于目标值),若等于目标值,则返回此时两个指针所指的值。
代码如下:
cpp
class Solution {
public:
vector<int> twoSum(vector<int>& price, int target)
{
int right=price.size()-1;
int left=0;
while(left<right)
{
if(price[right]+price[left]<target)
{
left++;
}
else if(price[right]+price[left]>target)
{
right--;
}
else
return {price[left],price[right]};
}
return {-1,-1};
}
};
6.有效三角形的个数

这道题用暴力枚举需要三个循环:
for(i=0;i<n;i++)
for(j=i+1;j<n;j++)
for(k=j+1;k<n;k++)
check(i,j,k);
用双指针的方法,则主要思想和上一题差不多,但这道题是需要三个数满足要求,则需要在固定一个数的基础上,其他两个数走上一道题的逻辑。
先排好序,固定最大的数(即最右边的数)c,除去最大的数之外,余下的数组的左右两端设为指针a、b,若a+b>c,那么中间的数加上b一定大于c,则b可以去掉,即端点向内移,有效三角形的个数由下标相减得到,若a+b<=c,那么中间的数加上a一定小于等于c,则a可以去掉,即端点向内移,有效三角形不算。再固定次大的数...形成循环。
代码如下:
cpp
class Solution {
public:
int triangleNumber(vector<int>& nums)
{
sort(nums.begin(),nums.end());
int c=nums.size()-1;
int sum=0;
while(c>=2)
{
int left=0;
int right=c-1;
while(left<right)
{
if(nums[left]+nums[right]>nums[c])
{
sum+=right-left;
right--;
}
else
{
left++;
}
}
c--;
}
return sum;
}
};
7.三数之和

这道题也是需要三个数满足条件,但与上一题不同的是答案中不能有重复的三元组,这要求一些额外的细节处理,包括越界访问、避免漏查、避免找重复的。
代码如下:
cpp
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums)
{
vector<vector<int>> ret;
int k=nums.size()-1;
sort(nums.begin(),nums.end());
while(k>=2&&nums[k]>=0)
{
int i=0;
int j=k-1;
while(i<j)
{
if(nums[i]+nums[j]+nums[k]<0)
{
i++;
}
else if(nums[i]+nums[j]+nums[k]>0)
{
j--;
}
else
{
ret.push_back({nums[i],nums[j],nums[k]});
i++;//防止漏查
while(i<j&&nums[i]==nums[i-1])//防止重复输出及越界访问
{
i++;
}
j--;//防止漏查
while(i<j&&nums[j]==nums[j+1])//防止重复输出及越界访问
{
j--;
}
}
}
k--;
while(k>=2&&nums[k]==nums[k+1])//处理越界访问及重复检查
{
k--;
}
}
return ret;
}
};
8.四数之和

这道题需要四个数满足条件,无非是在三个数之和的基础上再加一个循环,基本原理是差不多的,需要根据题目的要求做一些修改。另外还有一个要注意的地方,这道题的测试范围如果很大,一些写了多个数相加的语句可能超出整形的储存范围,可以改为用long long类型储存。
代码如下:
cpp
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target)
{
vector<vector<int>> ret;
int l=nums.size()-1;
sort(nums.begin(),nums.end());
while(l>=3)
{
int k=l-1;
while(k>=2)
{
int i=0;
int j=k-1;
while(i<j)
{
long long aim=(long long)target-nums[k]-nums[l];
int sum=nums[i]+nums[j];
if(sum<aim)
{
i++;
}
else if(sum>aim)
{
j--;
}
else
{
ret.push_back({nums[i],nums[j],nums[k],nums[l]});
i++;//防止漏查
while(i<j&&nums[i]==nums[i-1])//防止重复输出及越界访问
{
i++;
}
j--;//防止漏查
while(i<j&&nums[j]==nums[j+1])//防止重复输出及越界访问
{
j--;
}
}
}
k--;
while(k>=2&&nums[k]==nums[k+1])//处理越界访问及重复检查
{
k--;
}
}
l--;
while(l>=3&&nums[l]==nums[l+1])//处理越界访问及重复检查
{
l--;
}
}
return ret;
}
};
