双指针习题篇(下)
双指针习题篇(下)
文章目录
- 双指针习题篇(下)
- 1.有效三角形的个数
- 题目描述:
- 解法一:暴力枚举(超时)
- 算法思路:
- 代码实现:
- 解法二:利用单调性,使用双指针算法来解决问题
- 算法思路:
- 算法流程:
- 代码实现:
- 2.和为 s 的两个数字
- 题目描述:
- 解法一:暴力枚举 O(N^2^)
- 算法思路:
- 算法流程:
- 代码实现:
- 解法二:利用单调性,使用双指针算法解决问题 O(N)
- 算法思路:
- 算法流程:
- 代码实现:
- 3.三数之和
- 题目描述:
- 解法一:排序+暴力枚举+利用set去重 O(N^3^)
- 解法二:排序+双指针
- 算法思路:
- 代码实现:
- 4.四数之和
- 题目描述:
- 解法一:排序+暴力枚举+利用set去重 O(N^3^)
- 解法二:排序+双指针 O(N^3^)
- 算法思路:
- 代码实现:
1.有效三角形的个数
题目描述:
给定一个包含非负整数的数组
nums
,返回其中可以组成三角形三条边的三元组个数。示例 1:
输⼊:
nums = [2,2,3,4]
输出:
3
解释:有效的组合是:
2,3,4
(使用第一个 2)
2,3,4
(使用第二个 2)
2,2,3
示例 2:
输⼊:
nums = [4,2,3,4]
输出:
4
解释:
4,2,3
4,2,4
4,3,4
2,3,4
解法一:暴力枚举(超时)
算法思路:
1.三层 for 循环枚举出所有的三元组,并且判断是否能构成三角形。时间复杂度为O(N3);
2.判断三角形方法的优化:
(1).如果能构成三角形,需要满足任意两边之和要大于第三边。但是实际上只需让较小的两条边之和大于第三边即可。
(2).因此我们要先对整个数组排序,然后从小到大枚举三元组,一方面省去枚举的数量,另一方面方便判断是否能构成三角形。
时间复杂度为O(N3+NlogN)。
代码实现:
class Solution {
public:int triangleNumber(vector<int>& nums) {// 1. 排序sort(nums.begin(), nums.end());int n = nums.size(), ret = 0;// 2. 从⼩到⼤枚举所有的三元组for (int i = 0; i < n; i++) {for (int j = i + 1; j < n; j++) {for (int k = j + 1; k < n; k++) {// 当最⼩的两个边之和⼤于第三边的时候,统计答案if (nums[i] + nums[j] > nums[k])ret++;} }}return ret;}
};
解法二:利用单调性,使用双指针算法来解决问题
算法思路:
1.先将数组排序;
2.再固定最大的数;
3.最后最大的数的左区间内,使用双指针算法,快速统计出符合要求的三元组的个数。
算法流程:
设最长边枚举到 i 位置,区间 [left, right] 是 i 位置左边的区间(也就是比它小的区间):
如果 nums[left] + nums[right] > nums[i] :
- 说明 [left, right - 1] 区间上的所有元素均可以与 nums[right] 构成比nums[i] 大的⼆元组;
- 满足条件的有 right - left 种;
- 此时 right 位置的元素的所有情况相当于全部考虑完毕, right-- ,进入下一轮判断.
如果 nums[left] + nums[right] <= nums[i] :
- 说明 left 位置的元素是不可能与 [left + 1, right] 位置上的元素构成满足条件的二元组;
- left 位置的元素可以舍去, left++ 进入下轮循环.
时间复杂度是O(N2).
代码实现:
class Solution
{
public:int triangleNumber(vector<int>& nums) {// 1. 优化sort(nums.begin(), nums.end());int ret = 0, n = nums.size();for(int i = n - 1; i >= 2; i--) // 2.先固定最⼤的数{// 3.利⽤双指针快速统计符合要求的三元组的个数int left = 0, right = i - 1;while(left < right){if(nums[left] + nums[right] > nums[i]){ret += right - left;right--;}else{left++;}}}return ret;}
};
2.和为 s 的两个数字
题目描述:
输入一个递增排序的数组和一个数字 s ,在数组中查找两个数,使得它们的和正好是 s 。如果有多对数字的和等于 s ,则输出任意一对即可。
示例 1:
输入:
nums = [2,7,11,15], target = 9
输出:
[2,7]
或者[7,2]
解法一:暴力枚举 O(N2)
算法思路:
两层 for 循环列出所有两个数字的组合,判断是否等于目标值。
算法流程:
两层 for 循环:
1.外层 for 循环依次枚举第一个数 a ;
2.内层 for 循环依次枚举第二个数 b ,让它与 a 匹配;
注意:我们挑选第二个数的时候,可以不从第⼀个数开始选,因为 a 前面的数我们都已经在之前考虑过了;因此,我们可以从 a 往后的数开始列举。
3.然后将挑选的两个数相加,判断是否符合目标值。
代码实现:
class Solution {
public:vector<int> twoSum(vector<int>& nums, int target) {int n = nums.size();for (int i = 0; i < n; i++) { // 第⼀层循环从前往后列举第⼀个数for (int j = i + 1; j < n; j++) { // 第⼆层循环从 i 位置之后列举第⼆个数if (nums[i] + nums[j] == target) // 两个数的和等于⽬标值,说明我们已经找到结果了return {nums[i], nums[j]};}}return {-1, -1};}
};
解法二:利用单调性,使用双指针算法解决问题 O(N)
算法思路:
注意到本题是升序的数组,我们可以利用单调性,使用左右指针优化时间复杂度。
算法流程:
1.初始化 left , right 分别指向数组的左右两端(这⾥不是我们理解的指针,而是数组的下标)
2.当 left < right 的时候,循环下面操作:
(1).当 nums[left] + nums[right] == target 时,返回结果;
(2).当 nums[left] + nums[right] < target 时:
- left++ ,去比较下⼀组数据;
- right 指针不变.
(3).当 nums[left] + nums[right] > target 时:
- right-- ,去比较下一组数据;
- left 指针不变.
代码实现:
class Solution
{
public:vector<int> twoSum(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1;while(left < right){int sum = nums[left] + nums[right];if(sum > target) right--;else if(sum < target) left++;else return {nums[left], nums[right]};}// 照顾编译器return {-4941, -1};}
};
3.三数之和
题目描述:
给你一个整数数组
nums
,判断是否存在三元组[nums[i], nums[j], nums[k]]
满足i != j
、i != k
且j != k
,同时还满足nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为0
且不重复的三元组。注意:答案中不可以包含重复的三元组。
示例 1:
输入: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] 。 注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1] 输出:[] 解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0] 输出:[[0,0,0]] 解释:唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <= 105
解法一:排序+暴力枚举+利用set去重 O(N3)
解法二:排序+双指针
算法思路:
本题与两数之和类似,与两数之和稍微不同的是,题目中要求找到所有「不重复」的三元组。那我们可以利用在两数之和那里面的双指针思想,来对我们的暴力枚举做优化:
1.排序;
2.固定一个数a;
3.在该数后面的区间内,利用“双指针算法”快速找到两个数,它们的和等于 -a即可。
处理细节问题:
1.去重
- 找到一种结果之后,left和right指针要跳过重复元素
- 当使用完一次双指针算法之后,a也需要跳过重复元素
注意:避免越界!
2.不漏
- 找到一种结果之后,不要“停”,缩小区域,继续寻找.
代码实现:
class Solution
{
public:vector<vector<int>> threeSum(vector<int>& nums) {vector<vector<int>> 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, right = n - 1, 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 和 rightwhile(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;}
};
时间复杂度:O(N2)
4.四数之和
题目描述:
给你一个由
n
个整数组成的数组nums
,和一个目标值target
。请你找出并返回满足下述全部条件且不重复的四元组[nums[a], nums[b], nums[c], nums[d]]
(若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a
、b
、c
和d
互不相同nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
示例 1:
输入:nums = [1,0,-1,0,-2,2], target = 0 输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:
输入:nums = [2,2,2,2,2], target = 8 输出:[[2,2,2,2]]
提示:
1 <= nums.length <= 200
-109 <= nums[i] <= 109
-109 <= target <= 109
解法一:排序+暴力枚举+利用set去重 O(N3)
解法二:排序+双指针 O(N3)
算法思路:
依次固定一个数 a ;
在 a 的后面区间内,利用「三数之和」找到三个数,使这三个数的和等于 target - a 即可。
三数之和:
依次固定一个数 b ;
在 b 的后面区间内,利用“双指针”找到两个数,使这两个数的和等于 target - a - b即可。
处理细节问题:
1.去重
- 找到一种结果之后,left和right指针要跳过重复元素
- 当使用完一次双指针算法之后,a也需要跳过重复元素
- 当结束一次「三数之和」完之后,b也需要跳过重复元素
2.不漏
- 找到一种结果之后,不要“停”,缩小区域,继续寻找.
代码实现:
class Solution
{
public:vector<vector<int>> fourSum(vector<int>& nums, int target) {vector<vector<int>> 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, 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;}
};
最后,本篇文章在此结束,我们了解到双指针法的核心在于通过两个指针的相对移动来减少不必要的遍历,从而提高算法的效率。在实际应用中,掌握这一技巧不仅能够帮助我们更快地解决问题,还能让我们在面对复杂数据结构时更加游刃有余。希望这次的博客能够帮助你更好地理解和运用双指针技巧,并在未来的编程实践中取得更好的成绩。
继续加油,编程路上的每一步都是成长的一部分!