一、双指针
1.1移动零
链接:283. 移动零 - 力扣(LeetCode)
给定一个数组
nums
,编写一个函数将所有0
移动到数组的末尾,同时保持非零元素的相对顺序。请注意 ,必须在不复制数组的情况下原地对数组进行操作。示例 1:
输入: nums =[0,1,0,3,12]
输出:[1,3,12,0,0]
解法:
通过两个指针(并非是真的指针,只是数组的下标),dest和cur将长度为n的数组划分为三个部分;
[0,dest]:cur已经遍历过的地方,处理过不为0的部分;
[dest+1,cur-1]:cur已经遍历过的地方,处理过为0的部分;
[cur,n-1]:cur未遍历的地方;
第一种情况: cur指向数组元素为0
[0,dest]部分是处理过并且元素不为0的部分,cur指向0,所以处理后[0,dest]不变;
[dest+1,cur-1]部分是处理过为0的部分,所以处理后[dest+1,cur-1]长度加一;
[cur,n-1]部分长度减一;
第二种情况: cur指向数组元素不为0
[0,dest]部分是处理过并且元素不为0的部分,cur指向1,所以处理后[0,dest]长度加一,dest往后移一位,用来存放1;
[dest+1,cur-1]部分是处理过为0的部分,所以处理后[dest+1,cur-1]长度不变;
[cur,n-1]部分长度减一;
dest往后移一位,dest指向为0的部分,只需要将此时的dest指向和cur指向元素交换即可,之后cur++;
初始状态,dest=-1,cur=0;
1.nums[cur]为0,cur++;
2.nums[cur] 不为0,dest++后,在交换nums[dest]和nums[cur];
c++解法:
class Solution {
public:
void moveZeroes(vector<int>& nums)
{
for(int dest=-1,cur=0; cur<nums.size(); cur++)
{
if(nums[cur])
swap(nums[cur], nums[++dest]);
}
}
};
c语言解法:
void moveZeroes(int* nums, int numsSize)
{
for(int dest=-1,cur=0; cur<numsSize; cur++)
{
if(nums[cur])
{
int temp = 0;
temp = nums[++dest];
nums[dest] = nums[cur];
nums[cur] = temp;
}
}
}
1.2复写零
链接:1089. 复写零 - 力扣(LeetCode)
给你一个长度固定的整数数组
arr
,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。注意:请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上述修改,不要从函数返回任何东西。
示例 1:
输入:arr = [1,0,2,3,0,4,5,0] 输出:[1,0,0,2,3,0,0,4] 解释:调用函数后,输入的数组将被修改为:[1,0,0,2,3,0,0,4]
解法:
在不用额外的数组情况下,从前往后复写会造成数据覆盖的影响,因此需要从后往前复写;
但从什么位置从后向前进行复写,需要额外确定;
1.找出从后往前开始复写的位置;
2.从后往前复写;
初始状态 :
1.cur指向的元素不为0,dest往后移一位;
2.cur指向的元素为0,dest往后移两位;
初始状态:cur指向1,dest++;
cur再往后移一位,指向元素为0;
dest往后移两位;
当dest移动到最后一位时,停止移动,记录此刻cur的位置;
特殊情况:
当cur指向元素0时,dest往后移动两位,此刻dest越界;
手动将数组最后一位,将其元素改为0;
cur往前移动一位,dest往前移两位;
从此刻开始复写;
1.arr[cur]不为0,arr[dest] = arr[cur];
2. arr[cur]为0,arr[dest] = arr[dest-1] = 0;
直到cur=0;
c++实现:
class Solution {
public:
void duplicateZeros(vector<int>& arr)
{
int cur = 0, dest = -1;
int n = arr.size();
for(cur=0,dest=-1; dest<n-1; cur++)
{
if(arr[cur])
dest++;
else
dest += 2;
}
if(dest == n)
{
arr[n-1] = 0;
dest -= 2;
cur--;
}
cur--;
for(; cur>=0; cur--)
{
if(arr[cur])
{
arr[dest] = arr[cur];
dest--;
}
else
{
arr[dest] = 0;
arr[dest-1] = 0;
dest -= 2;
}
}
}
};
1.3快乐数
链接:202. 快乐数 - 力扣(LeetCode)
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果
n
是 快乐数 就返回true
;不是,则返回false
。示例 1:
输入:n = 19 输出:true 解释: 1^2 + 9^2 = 82 8^2 + 2^2 = 68 6^2 + 8^2 = 100 1^2 + 0^2 + 0^2 = 1 示例 2: 输入:n = 2 输出:false
对应两种情况:
1.重复操作最后结果会变成1:
例如19,经过4步操作后变为1;
2.重复一定操作步骤后陷入无限循环中:
例如2,平方后变成4,又经过几次操作后又出现了4,所以会一直陷入循环中;
上述两种情况都会进入 一个循环,第一种进入的循环全为1;第二种循环中不会出现1;根据此判断是否为快乐数;
快慢指针可以解决是否是环形链式结构,只需判断出slow指针和fast指针相遇时的值是否为1即可;slow++,fast+=2;
class Solution
{
public:
int func(int n)
{
int sum = 0;
while(n)
{
int t = n%10;
sum += t*t;
n /= 10;
}
return sum;
}
bool isHappy(int n)
{
int slow = n,fast = func(n);
while(slow != fast)
{
slow = func(slow);
fast =func(func(fast));
}
return slow == 1;
}
};
1.4盛最多水的容器
链接:11. 盛最多水的容器 - 力扣(LeetCode)
给定一个长度为
n
的整数数组height
。有n
条垂线,第i
条线的两个端点是(i, 0)
和(i, height[i])
。找出其中的两条线,使得它们与
x
轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。说明:你不能倾斜容器。
输入:[1,8,6,2,5,4,8,3,7] 输出:49 解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
解法1:暴力解法
0-1,0-2,...0-8;
1-2,1-3,...1-8;
...
7-8;找出他们中的最大值
解法2:
选取两个边5和7,所盛水的体积,高度为min(5,7)=5,底为4,v=高×底 = 20;
1.因数1×因数2 = 乘积1; 因数1减小,因数2不变,乘积1减小;
2.因数1×因数2 = 乘积2; 因数1减小,因数2减小,乘积2减小;
下图:
(固定较矮的一侧,移动较高的一侧)
1.固定左边蓝色条,右边蓝色条移动,向左移动一位,右边蓝色条高度变为3(比左边蓝色条高),因此此刻高不变(仍为1),底减小,容积变小;
容积最大时为初始状态(下标0-8),向里缩只会减小;
上述较矮的一方向里侧移动,左侧蓝色条向右移动一位后如下图所示。
1.固定右边蓝色条,左边蓝色条移动,向右移动一位,右边蓝色条高度变为6(比右边蓝色条矮),因此此刻高减小(为6),底减小,容积变小;
容积最大时为初始状态(下标1-8),向里缩只会减小;
重复上述操作:
class Solution
{
public:
int maxArea(vector<int>& height)
{
int left = 0, right = height.size()-1, ret = 0;
while(left<right)
{
int v = min(height[left],height[right]) * (right - left);
if(ret < v)
ret = v;
if(height[left] < height[right])
left++;
else
right--;
}
return ret;
}
};
1.5查找总价格为目标值的两个商品
链接:LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode)
购物车内的商品价格按照升序记录于数组
price
。请在购物车中找到两个商品的价格总和刚好是target
。若存在多种情况,返回任一结果即可。输入:price = [3, 9, 12, 15], target = 18 输出:[3,15] 或者 [15,3] 输入:price = [8, 21, 27, 34, 52, 66], target = 61 输出:[27,34] 或者 [34,27]
解法:
1.if(price[left] + price[right] > taget)
8 + 66 = 74 > 61,所以left以右加上right相加均大于61,所以必须改变right,right--;
2.if(price[left] + price[right] < taget)
8 + 52 = 60 < 61,所以left加上right以左均小于61,所以必须改变left,left++;
21 + 52 = 73 >61,所以right--;
21 + 34 = 55 < 61,所以left++;
3.if(price[left] + price[right] == taget)
27 + 34 = 61,返回结果;
class Solution {
public:
vector<int> twoSum(vector<int>& price, int target)
{
int left = 0,right = price.size()-1;
while(left<right)
{
int ret = price[left]+price[right];
if(ret<target)
left++;
else if(ret > target)
right--;
else
return {price[left],price[right]};
}
return {-1,-1};
}
};
1.6三数之和
链接:15. 三数之和 - 力扣(LeetCode)
给你一个整数数组
nums
,判断是否存在三元组[nums[i], nums[j], nums[k]]
满足i != j
、i != k
且j != k
,同时还满足nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为0
且不重复的三元组。注意:答案中不可以包含重复的三元组。输入:nums = [-1,0,1,2,-1,-4] 输出:[[-1,-1,2],[-1,0,1]] 解释: nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。 nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。 nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。 不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。 注意,输出的顺序和三元组的顺序并不重要。
解法:
先对原数组进行排序,先从i=0开始,left指向i+1的位置,right指向n-1位置;
如果nums[left]+nums[right] = -nums[i],放到三元组中,left++,right--继续;
如果不相等,则按照单调性方法进行处理;
边界处理:
例如left指向-1,left++后指向-1,此刻可省略判断,直接跳过;因此left++后指向的数与之前指向的数字一样时,并且left<right直接下一步;
例如i=-4,i++后i仍为-4,不需要判断,直接跳过,注意i<n边界问题;
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums)
{
int n = nums.size();
vector<vector<int>> list;
sort(nums.begin(),nums.end());
for(int i=0; i<n;)
{
int left = i+1, right = n-1, ret = -nums[i];
while(left < right)
{
if(nums[i]>0) break;
if(nums[left]+nums[right] < ret) left++;
else if(nums[left]+nums[right] > ret) right--;
else
{
list.push_back({nums[i],nums[left],nums[right]});
left++,right--;
while(left<right && nums[left-1] == nums[left]) left++;
while(left<right && nums[right] == nums[right+1]) right--;
}
}
i++;
while(i<n && nums[i] == nums[i-1]) i++;
}
return list;
}
};