双指针
目录
双指针
试题1:移动零
算法原理
代码编写
试题2:复写零
算法原理
代码编写
试题3:快乐数
算法原理
编写代码
试题4:盛最多水的容器
算法原理
编写代码
试题5:有效三角形的个数
算法原理
代码编写
试题6:和为s的两个数
算法原理
代码编写
试题7:三数之和
算法原理
代码编写
试题8:四数之和
算法原理
代码编写
试题1:移动零
算法原理
数组划分(数组分块)
提供一个数组,给定一个规则,在这个规则下将数组划分为若干个区间
使用双指针算法(注:这里的指针是数组的下标)
两个指针的作用
**cur(current):**从左往右扫描数组,遍历数组
**dest(destination):**已处理的区间内,非零元素的最后一个位置
三个区间
0,dest\]:非0
\[dest + 1,cur - 1\]:0
\[cur,n - 1\]:待处理

cur从前往后遍历的过程中
遇到0元素:cur++
遇到非0元素:dest++,swap(dest,cur),cur++

> **补充:**双指针算法(数据划分)是快排里面最核心的一步
>
> 
#### 代码编写
* 个人版本
```cpp
class Solution {
public:
void moveZeroes(vector& nums)
{
int dest = -1;
int cur = 0;
while(cur != nums.size())
{
if(nums[cur] == 0)
{
cur++;
}
else
{
dest++;
swap(nums[cur],nums[dest]);
cur++;
}
}
}
};
```
* 标准版本
```cpp
class Solution {
public:
void moveZeroes(vector& nums)
{
for(int cur = 0,dest = -1;cur < nums.size();cur++)
{
if(nums[cur])
{
swap(nums[++dest],nums[cur]);
}
}
}
};
```
### 试题2:复写零

#### 算法原理
双指针算法
根据异地操作,优化成双指针下的就地操作
异地操作

就地操作

Step1:先找到最后一个复写的数
使用双指针算法,先判断cur位置的值,决定dest向后移动一步或者两步
再判断dest是否已经走到结束为止,最后cur++,直到cur遍历整个数组

Step1.5:处理边界情况
将n-1下标的元素改为0,cur -= 1,dest -= 2

Step2:从后向前完成复写操作

总结:画图模拟过程,多次尝试,将大题化为多个小题
#### 代码编写
* 个人版本
```cpp
class Solution
{
public:
void duplicateZeros(vector& arr)
{
int cur = 0;
int dest = -1;
while(cur < arr.size())
{
if(arr[cur] == 0)
{
dest += 2;
}
else
{
dest++;
}
if(dest >= arr.size() - 1)
{
break;
}
cur++;
}
if(dest == arr.size())
{
dest -= 2;
cur--;
arr[arr.size() - 1] = 0;
}
while(cur >= 0)
{
if(arr[cur] == 0)
{
arr[dest] = 0;
arr[dest - 1] = 0;
dest -= 2;
}
else
{
arr[dest] = arr[cur];
dest--;
}
cur--;
}
}
};
```
* 标准版本
```cpp
class Solution
{
public:
void duplicateZeros(vector& arr)
{
//#1:先找到最后一个数
int cur = 0,dest = -1,n = arr.size();
while(cur < n)
{
if(arr[cur])
{
dest++;
}
else
{
dest += 2;
}
if(dest >= n - 1)
{
break;
}
cur++;
}
//#2:处理边界情况
if(dest == n)
{
arr[n - 1] = 0;
cur--;
dest -= 2;
}
//#3:从后向前完成复写操作
while(cur >= 0)
{
if(arr[cur])
{
arr[dest--] = arr[cur--];
}
else
{
arr[dest--] = 0;
arr[dest--] = 0;
cur--;
}
}
}
};
```
### 试题3:快乐数

#### 算法原理
快慢双指针
定义快慢指针
慢指针每次向后移动一步
快指针每次向后移动两步
判断相遇时候的值即可

> **补充:**鸽巢原理(抽屉原理)
>
> n个巢穴,n+1个鸽子:至少有一个巢穴里的鸽子数大于1
>
> 2.1 \* 10\^9 -\> \[1,810\](巢穴)
>
> 9999999999 -\> 9\^2 \* 10 = 810
>
> 随机数x经过811次操作,必定会有一个值落在\[1,810\],所以成环是必定的
#### 编写代码
* 个人版本
```cpp
class Solution {
public:
int happydata(int n)
{
int sum = 0;
while(n)
{
int x = n % 10;
sum += x * x;
n = n / 10;
}
return sum;
}
bool isHappy(int n)
{
int slow = n;
int fast = n;
do
{
slow = happydata(slow);
fast = happydata(happydata(fast));
}while(slow != fast);
if(slow == 1)
{
return true;
}
else
{
return false;
}
}
};
```
* 标准版本
```cpp
class Solution
{
public:
int bitSum(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 = bitSum(n);
while(slow != fast)
{
slow = bitSum(slow);
fast = bitSum(bitSum(fast));
}
return slow == 1;
}
};
```
### 试题4:盛最多水的容器

#### 算法原理
解法一:暴力枚举
两层for循环(超时)

解法二:双指针法

#### 编写代码
* 个人版本
```cpp
class Solution
{
public:
int maxArea(vector& height)
{
int maxv = 0;
int left = 0;
int right = height.size() - 1;
while(left < right)
{
int v;
if(height[left] < height[right])
{
v = (right - left) * height[left];
if(v > maxv)
{
maxv = v;
}
left++;
}
else
{
v = (right - left) * height[right];
if(v > maxv)
{
maxv = v;
}
right--;
}
}
return maxv;
}
};
```
* 标准版本
```cpp
class Solution
{
public:
int maxArea(vector& height)
{
int left = 0,right = height.size() - 1,ret = 0;
while(left < right)
{
int v = min(height[left],height[right]) * (right - left);
ret = max(ret,v);
if(height[left] < height[right])
{
left++;
}
else
{
right--;
}
}
return ret;
}
};
```
### 试题5:有效三角形的个数

#### 算法原理
解法一:暴力枚举
时间复杂度:O(N\^3)
直接枚举:3 \* N\^3
排序后枚举:N \* logN + N\^3
```cpp
for(i = 0;i < n;i++)
{
for(j = i + 1;j < n;j++)
{
for(k = j + 1;k < n;k++)
{
check(i,j,k);
}
}
}
```
解法二:利用单调性,使用双指针算法
先固定最大的数O(N)
在最大的数的左区间内,使用双指针算法O(N)
快速统计出符合要求的三元组的个数
时间复杂度:O(N\^2)

> **补充:**给定三个数,判断是否能够构成三角形
>
> 已知三个数的大小顺序,判断较小的两个数之和大于第三个数,就能构成三角形
#### 代码编写
* 个人版本
```cpp
class Solution
{
public:
int triangleNumber(vector& nums)
{
int count = 0;
sort(nums.begin(),nums.end());
for(int i = nums.size() - 1;i >= 2;i--)
{
int left = 0;
int right = i - 1;
int c = nums[i];
while(left < right)
{
if(nums[left] + nums[right] > c)
{
count += right - left;
right--;
}
else
{
left++;
}
}
}
return count;
}
};
```
* 标准版本
```cpp
class Solution
{
public:
int triangleNumber(vector& nums)
{
//#1:优化
sort(nums.begin(),nums.end());
//#2:利用双指针解决问题
int ret = 0;
int n = nums.size();
for(int i = n - 1;i >= 2;i--)//先固定最大的数
{
//利用双指针快速统计符合要求的三元组个数
int left = 0;
int right = i - 1;
while(left < right)
{
if(nums[left] + nums[right] > nums[i])
{
ret += right - left;
right--;
}
else
{
left++;
}
}
}
return ret;
}
};
```
### 试题6:和为s的两个数

#### 算法原理
解法一:暴力枚举

```cpp
for(i = 0;i < n;i++)
{
for(j = i + i;j < n;j++)
{
check(num[i] + nums[j] == t);
}
}
```
解法二:利用单调性,使用双指针算法解决问题

#### 代码编写
* 个人版本
```cpp
class Solution
{
public:
vector twoSum(vector& price, int target)
{
int left = 0;
int right = price.size() - 1;
while(left < right)
{
if(price[left] + price[right] > target)
{
right--;
}
else if(price[left] + price[right] < target)
{
left++;
}
else
{
return {price[left],price[right]};
}
}
return {1,1};
}
};
```
* 标准版本
```cpp
class Solution
{
public:
vector twoSum(vector& price, int target)
{
int left = 0;
int right = price.size() - 1;
while(right > left)
{
int sum = price[left] + price[right];
if(sum > target)
{
right--;
}
else if(sum < target)
{
left++;
}
else
{
vector ret = {price[left],price[right]};
return ret;
}
}
return {-1,-1};
}
};
```
### 试题7:三数之和

#### 算法原理
解法一:暴力枚举
排序+枚举+利用unordered_set去重

时间复杂度:O(N\^3)
解法二:排序+双指针(有序数组)
时间复杂度:O(N\^2)
排序,固定一个数为a(a \<= 0)
在该数后面的区间内,利用双指针算法
快速找到两个数的和为-a即可
处理细节问题:
去重
找到一种结果之后,left和right指针需要跳过重复元素
当使用完一次双指针算法之后,i也需要跳过重复元素
避免越界
不漏
找到一种结果之后,不要停,缩小区间,继续寻找

#### 代码编写
* 个人版本
```cpp
class Solution
{
public:
vector> threeSum(vector& nums)
{
vector> ret;
sort(nums.begin(),nums.end());
for(int i = 0;i < nums.size();i++)
{
if(nums[i] > 0)
{
break;
}
if(i > 0 && nums[i] == nums[i - 1])
{
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while(left < right)
{
if(nums[left] + nums[right] < -nums[i])
{
left++;
}
else if(nums[left] + nums[right] > -nums[i])
{
right--;
}
else
{
ret.push_back({nums[i],nums[left],nums[right]});
while(left < right && nums[left] == nums[left + 1])
{
left++;
}
while(left < right && nums[right] == nums[right - 1])
{
right--;
}
left++;
right--;
}
}
}
return ret;
}
};
```
标准版本
```cpp
class Solution
{
public:
vector> threeSum(vector& nums)
{
vector> ret;
//#1:排序
sort(nums.begin(),nums.end());
//#2:利用双指针解决问题
int n = nums.size();
for(int i = 0;i < n;)//固定数a
{
if(nums[i] > 0)//小优化
{
break;
}
int left = i + 1;
int right = n - 1;
int target = -nums[i];
while(left < right)
{
int sum = nums[left] + nums[right];
if(sum > target)
{
right--;
}
else if(sum < target)
{
left++;
}
else
{
ret.push_back({nums[i],nums[left],nums[right]});
left++;
right--;
//去重操作 left 和 right
while(left < right && nums[left] == nums[left - 1])
{
left++;
}
while(left < right && nums[right] == nums[right + 1])
{
right--;
}
}
}
//去重操作 i
i++;
while(i < n && nums[i] == nums[i - 1])
{
i++;
}
}
return ret;
}
};
```
### 试题8:四数之和

#### 算法原理
解法一:暴力枚举
排序 + 暴力枚举 + 利用set去重
解法二:双指针法
排序 + 双指针
依次固定一个数a
在a后面的区间,利用三数之和找到三个数
使这三个数的和等于target - a即可
依次固定一个数b
在b后面的区间,利用双指针找到两个数
使这两个数的和等于target - a - b即可
处理细节问题:去重、不漏
时间复杂度:O(N\^3)

#### 代码编写
* 个人版本
```cpp
class Solution
{
public:
vector> fourSum(vector& nums, int target)
{
vector> ret;
sort(nums.begin(),nums.end());
int n = nums.size();
for(int a = 0;a < n;)
{
for(int b = a + 1;b < n;)
{
int left = b + 1;
int right = n - 1;
long long new_target = (long long)target - nums[a] - nums[b];
while(left < right)
{
long long sum = (long long)nums[left] + nums[right];
if(sum < new_target)
{
left++;
}
else if(sum > new_target)
{
right--;
}
else
{
ret.push_back({nums[a],nums[b],nums[left],nums[right]});
left++;
right--;
while(left < right && nums[left] == nums[left - 1])
{
left++;
}
while(left < right && nums[right] == nums[right + 1])
{
right--;
}
}
}
b++;
while(b < n && nums[b] == nums[b - 1])
{
b++;
}
}
a++;
while(a < n && nums[a] == nums[a - 1])
{
a++;
}
}
return ret;
}
};
```
* 标准版本
```cpp
class Solution
{
public:
vector> fourSum(vector& nums, int target)
{
vector> ret;
//#1:排序
sort(nums.begin(),nums.end());
//#2:利用双指针解决问题
int n = nums.size();
for(int i = 0;i < n;)//固定数a
{
//利用三数之和
for(int j = i + 1;j < n;)//固定数b
{
//双指针解法
int left = j + 1;
int right = n - 1;
long long aim = (long long)target - nums[i] - nums[j];
while(left < right)
{
int sum = nums[left] + nums[right];
if(sum < aim)
{
left++;
}
else if(sum > aim)
{
right--;
}
else
{
ret.push_back({nums[i],nums[j],nums[left++],nums[right--]});
//去重一
while(left < right && nums[left] == nums[left - 1])
{
left++;
}
while(left < right && nums[right] == nums[right + 1])
{
right--;
}
}
}
//去重二
j++;
while(j < n && nums[j] == nums[j - 1])
{
j++;
}
}
//去重三
i++;
while(i < n && nums[i] == nums[i - 1])
{
i++;
}
}
return ret;
}
};
```