优选算法专题1:双指针

双指针

目录

双指针

试题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\]:待处理 ![](https://i-blog.csdnimg.cn/direct/9d46d73912e546e18c80cb0fbe139837.jpeg) cur从前往后遍历的过程中 遇到0元素:cur++ 遇到非0元素:dest++,swap(dest,cur),cur++ ![](https://i-blog.csdnimg.cn/direct/39d9dded25654c9d9b1eb4cd1083ccd8.jpeg) > **补充:**双指针算法(数据划分)是快排里面最核心的一步 > > ![](https://i-blog.csdnimg.cn/direct/b46dfb315b5d42b482a922171375663b.jpeg) #### 代码编写 * 个人版本 ```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:复写零 ![](https://i-blog.csdnimg.cn/direct/238ec4621a4f435e95d46defe67c768d.png) #### 算法原理 双指针算法 根据异地操作,优化成双指针下的就地操作 异地操作 ![](https://i-blog.csdnimg.cn/direct/39125d2121a94632b8b65d7512f11a8c.jpeg) 就地操作 ![](https://i-blog.csdnimg.cn/direct/6b7b5b95e9774b4eb4fefb1510fd2c23.jpeg) Step1:先找到最后一个复写的数 使用双指针算法,先判断cur位置的值,决定dest向后移动一步或者两步 再判断dest是否已经走到结束为止,最后cur++,直到cur遍历整个数组 ![](https://i-blog.csdnimg.cn/direct/a3b911fc637b45d7bc38a9719c4005bf.jpeg) Step1.5:处理边界情况 将n-1下标的元素改为0,cur -= 1,dest -= 2 ![](https://i-blog.csdnimg.cn/direct/9c20413931d643dc8d3cad5b2aac0f1e.jpeg) Step2:从后向前完成复写操作 ![](https://i-blog.csdnimg.cn/direct/26a9aa34c62c4000bda1a551a8eac917.jpeg) 总结:画图模拟过程,多次尝试,将大题化为多个小题 #### 代码编写 * 个人版本 ```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:快乐数 ![](https://i-blog.csdnimg.cn/direct/19b806cd518e42c59042b00127d4128e.png) #### 算法原理 快慢双指针 定义快慢指针 慢指针每次向后移动一步 快指针每次向后移动两步 判断相遇时候的值即可 ![](https://i-blog.csdnimg.cn/direct/a719a907f6ac486a80d49381994aa710.jpeg) > **补充:**鸽巢原理(抽屉原理) > > 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:盛最多水的容器 ![](https://i-blog.csdnimg.cn/direct/c1b43c1f9a104ad09a94db8f388ae73c.png) #### 算法原理 解法一:暴力枚举 两层for循环(超时) ![](https://i-blog.csdnimg.cn/direct/1b9f9044c3d846fbaff064f9e81873a5.jpeg) 解法二:双指针法 ![](https://i-blog.csdnimg.cn/direct/bb4d05c8e0f74b28b8256a00b8b3d5c1.jpeg) #### 编写代码 * 个人版本 ```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:有效三角形的个数 ![](https://i-blog.csdnimg.cn/direct/96cfec420f3f4eb0bd5eb052d5d28310.png) #### 算法原理 解法一:暴力枚举 时间复杂度: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) ![](https://i-blog.csdnimg.cn/direct/82efc5df10d247cbb210caa93cd53b53.jpeg) > **补充:**给定三个数,判断是否能够构成三角形 > > 已知三个数的大小顺序,判断较小的两个数之和大于第三个数,就能构成三角形 #### 代码编写 * 个人版本 ```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的两个数 ![](https://i-blog.csdnimg.cn/direct/efcf33a0970f4d94b2c2e022c86a3c72.png) #### 算法原理 解法一:暴力枚举 ![](https://i-blog.csdnimg.cn/direct/f34010eb41dc434dbc465cdc7cc49b6a.jpeg) ```cpp for(i = 0;i < n;i++) { for(j = i + i;j < n;j++) { check(num[i] + nums[j] == t); } } ``` 解法二:利用单调性,使用双指针算法解决问题 ![](https://i-blog.csdnimg.cn/direct/08cfbade129a45409eeb9d47dea25aab.jpeg) #### 代码编写 * 个人版本 ```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:三数之和 ![](https://i-blog.csdnimg.cn/direct/6a9b11e0553246bea8093ea374e0c80a.png) #### 算法原理 解法一:暴力枚举 排序+枚举+利用unordered_set去重 ![](https://i-blog.csdnimg.cn/direct/080baa36c30748128abdbf717af194a9.jpeg) 时间复杂度:O(N\^3) 解法二:排序+双指针(有序数组) 时间复杂度:O(N\^2) 排序,固定一个数为a(a \<= 0) 在该数后面的区间内,利用双指针算法 快速找到两个数的和为-a即可 处理细节问题: 去重 找到一种结果之后,left和right指针需要跳过重复元素 当使用完一次双指针算法之后,i也需要跳过重复元素 避免越界 不漏 找到一种结果之后,不要停,缩小区间,继续寻找 ![](https://i-blog.csdnimg.cn/direct/d4b869e6b6e3488dbcdf08bebb6f4af0.jpeg) #### 代码编写 * 个人版本 ```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:四数之和 ![](https://i-blog.csdnimg.cn/direct/854d0418f8d4484cb86623f05e414d2e.png) #### 算法原理 解法一:暴力枚举 排序 + 暴力枚举 + 利用set去重 解法二:双指针法 排序 + 双指针 依次固定一个数a 在a后面的区间,利用三数之和找到三个数 使这三个数的和等于target - a即可 依次固定一个数b 在b后面的区间,利用双指针找到两个数 使这两个数的和等于target - a - b即可 处理细节问题:去重、不漏 时间复杂度:O(N\^3) ![](https://i-blog.csdnimg.cn/direct/b07e7abc2eff4435ac64428227137a3b.jpeg) #### 代码编写 * 个人版本 ```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; } }; ```

相关推荐
zsc_1182 小时前
pvz3解码小游戏求解算法
算法
程序员学习随笔2 小时前
深入剖析 std::optional:实现原理、性能优化与安全编程实践
c++·安全·空值
汀、人工智能2 小时前
[特殊字符] 第107课:LRU缓存(最后一课[特殊字符])
数据结构·算法·链表·数据库架构·哈希表·lru缓存
Stella Blog2 小时前
狂神Java基础学习笔记Day01
java·笔记·学习
tq10862 小时前
时间、决断与主体性:从“存在决定自我”到对“存在即本质”的批判
笔记
数据知道2 小时前
claw-code 源码分析:结构化输出与重试——`structured_output` 一类开关如何改变「可解析性」与失败语义?
算法·ai·claude code·claw code
tankeven2 小时前
HJ172 小红的矩阵染色
c++·算法
2301_822703202 小时前
Flutter 框架跨平台鸿蒙开发 - 智能植物生长记录应用
算法·flutter·华为·harmonyos·鸿蒙
每日任务(希望进OD版)2 小时前
线性DP、区间DP
开发语言·数据结构·c++·算法·动态规划