设置两个指针的技巧:
- 1.有时候所谓的双指针技巧,就单纯是代码过程用双指针的形式表达出来而已。没有单调性(贪心)方面的考虑。
- 2.有时候的双指针技巧包含单调性(贪心)方面的考虑,牵扯到可能性的取舍。对分析能力的要求会变高。其实是先有的思考和优化,然后代码变成了 双指针的形式。
- 3.所以,双指针这个“皮”不重要,分析题目单调性(贪心)方面的特征,这个能力才重要。
常见的双指针类型:
- 1.同向双指针
- 2.快慢双指针
- 3.从两头往中间的双指针
- 4.其他
下面通过几个题目加深理解。
题目一
测试链接:https://leetcode.cn/problems/sort-array-by-parity-ii/
分析:可以对奇数下标和偶数下标分别设置一个指针,每次循环判断相应下标的对应数字是否是奇数或者偶数。如果是,则加2;如果不是则看另一下标的数字是否符合条件。遍历数组即可得到答案。代码如下。
class Solution {
public:
void swap(vector<int>& nums, int even, int odd){
nums[even] = nums[even] ^ nums[odd];
nums[odd] = nums[even] ^ nums[odd];
nums[even] = nums[even] ^ nums[odd];
}
vector<int> sortArrayByParityII(vector<int>& nums) {
int even = 0;
int odd = 1;
int length = nums.size();
for(;even < length && odd < length;){
if(nums[even] % 2 == 0){
even += 2;
}else{
if(nums[odd] % 2 == 0){
swap(nums, even, odd);
even += 2;
}else{
odd += 2;
}
}
if(nums[odd] % 2 == 1){
odd += 2;
}else{
if(nums[even] % 2 == 1){
swap(nums, even, odd);
}else{
even += 2;
}
}
}
return nums;
}
};
其中,因为odd和even不可能相等,所以交换时使用异或写法,省去一个变量。
如果优化一下思路,将每次判断2个下标改为1个,只去判断最末尾的值是奇数还是偶数,将其放入相应奇数下标或者是偶数下标。代码如下。
class Solution {
public:
void swap(vector<int>& nums, int index1, int index2){
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
vector<int> sortArrayByParityII(vector<int>& nums) {
int even = 0;
int odd = 1;
int length = nums.size();
for(;even < length && odd < length;){
if((nums[length-1] & 1) == 1){
swap(nums, length-1, odd);
odd += 2;
}else{
swap(nums, length-1, even);
even += 2;
}
}
return nums;
}
};
其中,判断奇偶性采用异或写法。
题目二
测试链接:https://leetcode.cn/problems/find-the-duplicate-number/
分析:因为题目的限制可以看出,数组中的值在数组下标范围内,所以可以将下标对应的值看为指向下一个下标的指针,而重复的值就会将其形成一个回路。可以类比于链表中寻找回路起点的写法,采用快慢指针的方法即可求得。代码如下。
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int fast = nums[nums[0]];
int slow = nums[0];
while (fast != slow)
{
fast = nums[nums[fast]];
slow = nums[slow];
}
fast = 0;
while (fast != slow)
{
fast = nums[fast];
slow = nums[slow];
}
return fast;
}
};
其中,fast是快指针,一次跳2次,slow是慢指针,一次跳1次。
题目三
测试链接:https://leetcode.cn/problems/trapping-rain-water/
分析:可以看出,最左边和最右边是不可能接到雨水的,所以遍历时只需遍历1~length-2。对于一个下标,可以看出决定其接到多少雨水,取决于这个下标左侧高度的最大值和右侧高度的最大值。在左侧和右侧的最大高度取最小值减去这个下标的高度,如果大于零就是接到多少雨水,如果小于零就代表接不到雨水。代码如下。
class Solution {
public:
int trap(vector<int>& height) {
int length = height.size();
int ans = 0;
vector<int> left;
vector<int> right;
left.assign(length, 0);
right.assign(length, 0);
left[0] = height[0];
for(int i = 1;i < length-2;++i){
left[i] = left[i-1] > height[i] ? left[i-1] : height[i];
}
right[length-1] = height[length-1];
for(int i = length-2;i > 1;--i){
right[i] = right[i+1] > height[i] ? right[i+1] : height[i];
}
for(int i = 1;i <= length-2;++i){
int temp = (left[i-1] > right[i+1] ? right[i+1] : left[i-1]) - height[i];
temp = temp > 0 ? temp : 0;
ans += temp;
}
return ans;
}
};
其中,left[i]代表0~i范围最大高度,right[i]代表i~length-1范围最大高度。
如果优化一下思路,将左起点作为左指针,右终点作为右指针,设置两个变量分别代表左指针左侧最大高度和右指针右侧最大高度,对于最大高度较小侧的指针即可进行结算,然后将指针变换位置。代码如下。
class Solution {
public:
int trap(vector<int>& height) {
int length = height.size();
int left = 1;
int right = length - 2;
int left_max = height[0];
int right_max = height[length-1];
int ans = 0;
while (left <= right)
{
if(left_max < right_max){
ans += ((left_max - height[left]) > 0 ? (left_max - height[left]) : 0);
left_max = left_max > height[left] ? left_max : height[left];
++left;
}else{
ans += ((right_max - height[right]) > 0 ? (right_max - height[right]) : 0);
right_max = right_max > height[right] ? right_max : height[right];
--right;
}
}
return ans;
}
};
其中,left_max就是左指针左侧最大高度,right_max是右指针右侧最大高度。
题目四
测试链接:https://leetcode.cn/problems/boats-to-save-people/
分析:首先可以对这个数组进行排序,在最小值也就是最左边设置左指针,最大值也就是最右边设置右指针。看左右指针所代表的值相加,是否小于等于limit。如果是,则代表一艘救生艇可以放下两个人;如果不是,就只能放下右指针的人。第一种情况,左右指针同时向中间移动;第二种情况,只移动右指针。代码如下。
class Solution {
public:
int numRescueBoats(vector<int>& people, int limit) {
int ans = 0;
int length = people.size();
int left = 0;
int right = length - 1;
sort(people.begin(), people.end());
while (left < right)
{
if(people[left] + people[right] <= limit){
++ans;
++left;
--right;
}else{
++ans;
--right;
}
}
if(left == right){
++ans;
}
return ans;
}
};
其中,排序是直接使用algorithm头文件的sort函数。
此题还有一个拓展就是如果二人一船则要求体重和为偶数。偶数只能通过偶数与偶数相加或者奇数与奇数相加得到,所以将原数组分为奇数组和偶数组,分别得出需要的船数相加即可得到答案。
题目五
测试链接:https://leetcode.cn/problems/container-with-most-water/
分析:有了题目三的铺垫,我们可以在最左边设置左指针,最右边设置右指针。哪个指针所代表的值小就代表此下标可以进行结算,然后向相应的方向进行移动。遍历数组即可得到最大值。代码如下。
class Solution {
public:
int maxArea(vector<int>& height) {
int length = height.size();
int ans = 0;
int left = 0;
int right = length - 1;
while (left < right)
{
if(height[left] < height[right]){
int temp = height[left] * (right - left);
ans = temp > ans ? temp : ans;
++left;
}else{
int temp = height[right] * (right - left);
ans = temp > ans ? temp : ans;
--right;
}
}
return ans;
}
};
题目六
测试链接:https://leetcode.cn/problems/heaters/
分析:基本思路就是对每一个房子进行判断,得出需要的最短半径,然后从这些最短半径中取一个最大值。那么就有一个指针在房子数组,一个指针在供暖器数组。对于一个房子指针,如果房子指针所指向的房子减去供暖器指针指向的供暖器的距离为正,则供暖器指针后移,直到得到供暖器指针指向的供暖器的位置大于等于房子的位置。此时,供暖器指针指向的供暖器和供暖器指针减1指向的供暖器中和房子指针指向的房子的距离的较小值为此房子指针指向的房子的需要的最短半径。加上边界判断遍历数组即可得到答案。代码如下。
class Solution {
public:
int findRadius(vector<int>& houses, vector<int>& heaters) {
int houses_length = houses.size();
int heaters_length = heaters.size();
int ans = 0;
int house_pointer = 0;
int heater_pointer = 0;
sort(houses.begin(), houses.end());
sort(heaters.begin(), heaters.end());
for(;house_pointer < houses_length;++house_pointer){
while (heater_pointer < heaters_length > 0 && houses[house_pointer] - heaters[heater_pointer] > 0)
{
++heater_pointer;
}
int temp;
if(heater_pointer == 0){
temp = heaters[heater_pointer] - houses[house_pointer];
}else if(heater_pointer == heaters_length){
temp = houses[house_pointer] - heaters[heater_pointer-1];
}else{
temp = ((houses[house_pointer] - heaters[heater_pointer-1] < heaters[heater_pointer] - houses[house_pointer]) ?
houses[house_pointer] - heaters[heater_pointer-1] : heaters[heater_pointer] - houses[house_pointer]);
}
ans = ans > temp ? ans : temp;
}
return ans;
}
};
其中,heater_pointer == 0和heater_pointer == heaters_length为边界判断。
题目七
测试链接:https://leetcode.cn/problems/first-missing-positive/
分析:设置left指针和right指针分别代表0~left-1位置为从1开始的连续正整数也就是1~left,right~length-1位置为垃圾区。将left初始化为0,right初始化为length。同时,right的值代表此时可以获得的从1开始的连续正整数最大长度。开始判断left指针的值,分为五种情况。1.arr[left] == left+1,则++left。2.arr[left] < left+1,则left指针的值进垃圾区。3.arr[left] > right,则left指针的值进垃圾区。4.arr[left] == arr[arr[left]-1],则left指针的值进垃圾区。5.交换arr[left]和arr[arr[left]-1]的值。代码如下。
class Solution {
public:
void swap(vector<int>& nums, int index1, int index2){
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
int firstMissingPositive(vector<int>& nums) {
int left = 0;
int right = nums.size();
while (left < right)
{
if(nums[left] == left+1){
++left;
}else if(nums[left] > right || nums[left] < left+1 || nums[nums[left]-1] == nums[left]){
swap(nums, left, --right);
}else{
swap(nums, left, nums[left]-1);
}
}
return left+1;
}
};