首页 > 其他分享 >双指针习题篇(下)

双指针习题篇(下)

时间:2024-11-01 23:44:30浏览次数:4  
标签:right nums ++ int 习题 指针 left

双指针习题篇(下)

文章目录

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 != ji != kj != 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;(优化:a<=0)

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 和 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;
    }
};

时间复杂度:O(N2)

4.四数之和

题目描述:

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abcd 互不相同
  • 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)

算法思路:
  1. 依次固定一个数 a ;

  2. 在 a 的后面区间内,利用「三数之和」找到三个数,使这三个数的和等于 target - a 即可。

三数之和:

  1. 依次固定一个数 b ;

  2. 在 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;
    }
};

最后,本篇文章在此结束,我们了解到双指针法的核心在于通过两个指针的相对移动来减少不必要的遍历,从而提高算法的效率。在实际应用中,掌握这一技巧不仅能够帮助我们更快地解决问题,还能让我们在面对复杂数据结构时更加游刃有余。希望这次的博客能够帮助你更好地理解和运用双指针技巧,并在未来的编程实践中取得更好的成绩。

继续加油,编程路上的每一步都是成长的一部分!
}
// 去重二
j++;
while(j < n && nums[j] == nums[j - 1]) j++;
}
// 去重三
i++;
while(i < n && nums[i] == nums[i - 1]) i++;
}
return ret;
}
};


最后,本篇文章在此结束,我们了解到双指针法的核心在于通过两个指针的相对移动来减少不必要的遍历,从而提高算法的效率。在实际应用中,掌握这一技巧不仅能够帮助我们更快地解决问题,还能让我们在面对复杂数据结构时更加游刃有余。希望这次的博客能够帮助你更好地理解和运用双指针技巧,并在未来的编程实践中取得更好的成绩。

继续加油,编程路上的每一步都是成长的一部分!

标签:right,nums,++,int,习题,指针,left
From: https://blog.csdn.net/zoelinkailuo/article/details/143443183

相关文章

  • 【无标题】Acwing1238_日志统计(双指针)
    原题链接 :1238.日志统计-AcWing题库https://www.acwing.com/problem/content/1240/题目要求:/***小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有N行。*其中每一行的格式是:tsid表示在ts时刻编号id的帖子收到一个”赞”。*现在小明......
  • 深入理解指针(1)
    1:内存和地址1:内存我们知道计算机上面的CPU在处理数据时,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑时,电脑内存是8GB/16GB/32GB等,那这些内存空间如何高效管理呢?其实也是内存划分为一个个的内存单元,每个内存的大小取1个字节。一个bit(比特)位可以储存一......
  • python练习题
    练习判断下列逻辑语句的布尔值1>1or3<4or4>5and2>1and9>8or7<6 答案:Truenot2>1and3<4or4>5and2>1and9>8or7<6答案:False求出下列逻辑语句的值8or3and4or2and0or9and7答案:80or2......
  • 单链表题+数组题(快慢指针和左右指针)
    @目录说明:本文章用于“单链表题+数组题”“链表”知识一、案例说明(使用快慢指针)问题1.1判断链表是否有环问题1.2:已知链表有环,请返回这个环的起点位置问题1.3:寻找无环单链表的中点,要求:如果偶数个数以左面一个节点为中点问题1.4:寻找无环单链表的中点,要求:如果偶数个数以右面一个节......
  • ”回溯算法“框架及练习题
    @目录一、回溯算法是什么?二、框架如下:本人其他文章链接一、回溯算法是什么?结论:回溯=穷举解决一个回溯问题,实际上就是一个决策树的遍历过程路径:就是已经做出的选择选择列表:就是你当前可以做出的选择结束条件:就是basecase条件,也就是临界条件二、框架如下:框架如下:resu......
  • C语言中指针和数组的相互关系
    在C语言中,指针和数组有着紧密的相互关系。数组是数据的集合,而指针则是一个包含内存地址的变量。指针可以用来访问数组的元素,便于高效的内存访问和操作。更具体来说,数组名本身就是一个指向首元素的指针、通过指针运算,我们可以遍历数组的每个元素、数组和指针的索引操作是等价的、......
  • 第八章 利用CSS制作导航菜单课后习题
    1.利用CSS技术,结合链接和列表,设计并实现“山水之间”页面。参考代码:<!DOCTYPEhtml><html> <head> <metacharset="utf-8"> <title>山水之间</title> <style> .all{ width:900px; } .top{ width:900px; height:100px;......
  • 智能指针使用
    普通指针的不足new和new[]的内存需要用delete和deletel]释放。程序员的主观失误,忘了或漏了释放。程序员也不确定何时释放。普通指针的释放类内的指针,在析构函数中释放。C++内置数据类型,如何释放?new出来的类,本身如何释放?C++11新增三个智能指针类型unique_pt......
  • 【C++】智能指针的正确使用方式
    本文将从这几方面讲解智能指针:智能指针的应用场景分析智能指针的性能分析:为什么shared_ptr性能比unique_ptr差指针作为函数参数时应该传,传值、传引用,还是裸指针?对于智能指针的使用,实际上是对所有权和生命周期的思考1.unique_ptr:专属所有权1.1unique_ptr介绍我们大......
  • 什么是指针数组 和 数组指针
    什么是指针数组答:就是一个数组,里面存的是指针而已它的写法可以如下:int*a[10];看看,它就是一个指针数组,数组名字当然是a,里面有10个元素,每个元素都是一个int*类型(即存放整型地址的指针)的指针。我们可以这样用,比如: #include<stdio.h>Cintmain(){intx=10,y=20,z=3......