【算法】双指针(续)

news2024/11/25 0:32:57

一、盛最多水的容器

11. 盛最多水的容器 - 力扣(LeetCode)

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明:你不能倾斜容器。

示例 1:

输入:[1,8,6,2,5,4,8,3,7]
输出:49 
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

示例 2:

输入:height = [1,1]
输出:1

提示:

  • n == height.length
  • 2 <= n <= 10^{5}
  • 0 <= height[i] <= 10^{4}

解法思路一:暴力枚举法。让第一个垂线分别与其它垂线组合,然后让第二个垂线分别与其它垂线组合,....最终返回最大容量。

代码实现(C++,时间复杂度为O(N^2)):

//方法一:暴力枚举法
class Solution {
public:
    int maxArea(vector<int>& height) {
        int n = height.size();
        int ret = 0; //记录最大容积
        //两层for循环,暴力遍历所有情况
        for(int i = 0;i < n;i++)
        {
            for(int j = i + 1;j < n;j++)
            {
                int V = min(height[i],height[j])*(j - i); //记录当前容器的容量(长*宽)
                ret = max(ret,V); //更新ret,使ret保持最大
            }
        }
        return ret;
    }
};

这种代码适用于数据量少的情景,如果数据非常多,那么就会超时。

在leetcode提交结果如下:

所以我们需要其它思路来解决问题。

暴力求解法之所以不行是因为循环次数太多了(组合情况太多了),每一种情况都需要求容积,然后比较,拿到最大容积。

那我们可不可以减少循环次数来解决问题呢?

我们先用示例1中的数据进行分析:

这就是思路二:对撞指针法。用left和right(两个"指针")来进行控制流程。

代码实现(C++,时间复杂的为O(N)):

//方法二:对撞指针法
class Solution {
public:
    int maxArea(vector<int>& height) {
        //用left和right来控制区间,ret记录最大容积
        int left = 0,right = height.size() - 1,ret = 0;
        while(left < right)
        {
            //记录一个数与另一个数的组合的最大容积
            int V = min(height[left],height[right])*(right-left);
            //更新ret,使其保持最大容积
            ret = max(ret,V);

            //哪边小,哪边改变
            if(height[left]<height[right]) ++left;
            else --right;
        }
        
        return ret; //返回最大容积
    }
};

二、有效三角形个数

611. 有效三角形的个数 - 力扣(LeetCode)

给定一个包含非负整数的数组 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

提示:

  • 1 <= nums.length <= 1000
  • 0 <= nums[i] <= 1000

看到这题,首先想到可以用暴力枚举求解。从nums中取3个数,将所有情况逐一进行比较。

判断3个数是否为三角形,如果这3个数是无序的,那么我们就需要判断三次(a+b>c,a+c>b,b+c>a),我们可以优化一下,将这3个数先排升序,假设顺序为a,b,c,那么只需判断一次(a+b>c)即可。

代码实现(C++,时间复杂度为O(N^3)):

//方法一:暴力求解法
class Solution {
public:
	int triangleNumber(vector<int>& nums) {
		// 1. 先排升序
		sort(nums.begin(), nums.end()); 
		int n = nums.size(), ret = 0; //ret来计算三元组的个数
		// 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;
	}
};

这道题用暴力求解是可以做出来的。

但是,暴力求解必定会效率低下,我们还可以用另外一种方法来处理问题。

我们解这道题的首要做法是先将数组中的数据排升序,数组一旦有序,那么我们的操作空间就会变大,对于这道题,解法二就是利用数组的单调性,使用双指针算法来解决问题。具体过程如下:

代码实现(C++,时间复杂度O(N^2)):

//方法二:对撞指针法
class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        sort(nums.begin(),nums.end()); //先对数组排升序
        int n = nums.size(); //记录数组元素个数
        int ret = 0; //记录三元组个数
        for(int i = n-1;i>=2;i--) //最外层循环用下标固定c
        {
            int left = 0,right = i-1; //双"指针",本质都是下标
            while(left < right)
            {   
                //a+b>c
                if(nums[left]+nums[right] > nums[i])
                {
                    ret += (right-left); //有right-left个三元组
                    --right; //此时right位置上的数据就没用了,更新right
                }
                //a+b<=c
                else
                {
                    ++left; //此时left位置上的数据就没用了,更新left
                }

            }
        }
        return ret; //返回全部三元组个数
    }
};

三、查找总价格为目标值的两个商品

LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode) 

购物车内的商品价格按照升序记录于数组 price。请在购物车中找到两个商品的价格总和刚好是 target。若存在多种情况,返回任一结果即可。

示例 1:

输入:price = [3, 9, 12, 15], target = 18
输出:[3,15] 或者 [15,3]

示例 2:

输入:price = [8, 21, 27, 34, 52, 66], target = 61
输出:[27,34] 或者 [34,27]

提示:

  • 1 <= price.length <= 10^5
  • 1 <= price[i] <= 10^6
  • 1 <= target <= 2*10^6

方法一:暴力枚举法。将数组中所有两两组合的情况全部列举出来相加看是否为target,两层循环即可搞定。

代码实现(C++,时间复杂度O(N^2)):

//方法一:暴力求解法
class Solution {
public:
    vector<int> twoSum(vector<int>& price, int target) {
        for(int i=0;i<price.size();i++)
        {
            for(int j=i+1;j<price.size();j++)
            {
                if(price[i] + price[j] == target)
                {
                    return {price[i],price[j]};
                }
            }
        }

        return {-1,-1}; //不存在的情况
    }
};

这道题用暴力枚举法会超时: 

方法二: 利用数组单调性,使用双指针算法解决问题。以示例2为例,具体过程如下:

代码实现(C++,时间复杂度为O(N^2)): 

//方法二:对撞指针法
class Solution {
public:
    vector<int> twoSum(vector<int>& price, int target) {
        int left = 0,right = price.size()-1; //双"指针"
        while(left < right) //终止条件是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}; //不存在的情况
    }
};

四、三数之和 

15. 三数之和 - 力扣(LeetCode)

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != 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
  • -10^5 <= nums[i] <= 10^5

先解释一下三元组"不重复","不重复"是指两个三元组中的数字不完全相同,比如[1,2,3]和[2,3,1]这两个三元组就是重复的,因为它们两个中的数字完全相同(与顺序无关)。返回三元组时,如果要返回[1,2,3],那么返回[2,3,1]也没问题,如果返回多个三元组,那么这些三元组的返回顺序也是任意的,因为返回时与顺序无关。三元组中的数可以重复,但是重复数的下标必须不同。

对于这道题解法一:暴力枚举法,将所有的三元组全部枚举出来依次判断。但这会面临一个问题,如果有两个三元组分别是[1,2,3]和[2,3,1],它们其实是一样的,所以我们需要"去重",去重首先会想到set容器,但是在set容器中[1,2,3]和[2,3,1]是不一样的,所以要想解决这个问题,可以事先对数组进行排序,这样从左往右暴力枚举出来的结果都是有序的三元组,比如[2,3,1]经过排序后变为[1,2,3],这样两个[1,2,3]放进set容器中就可以完成去重。

代码实现(C++,时间复杂度为O(N^3)):

//方法一:暴力枚举法
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(),nums.end()); //排升序
        int n = nums.size();
        set<vector<int>> s; //用来存放三元组
        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] == 0)
                        s.insert({nums[i],nums[j],nums[k]});
                }
            }
        }
        vector<vector<int>> vv(s.begin(),s.end());//用于做返回值
        return vv;
    }
};

这道题用暴力枚举法也是会超时的:

为什么要讲一下暴力枚举法呢?

为的是可以使我们可以通过暴力枚举法的思路来在此基础上做出优化。

方法二: 排序+双指针。这道题其实和第三道题差不多,第三道题是求两个数的和是否为一个目标值,而这道题可以转换为两个数的和是否为另外一个数的相反数(因为它们三数相加为0)。另外,这道题还需额外注意两点:1、去重问题 2、不漏数据

举例说明:

这里还有一个小优化的地方:如果固定的数为正数,因为是要排升序,所以正数后面不可能找到两个数相加等于负数,所以如果固定的数是正数,那么从该位置开始向后所有位置就不需要考虑了。 

代码实现(C++,时间复杂度为O(N^2)): 

//方法二:排序+双指针
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {

        vector<vector<int>> vv; //存储满足条件的三元组

        //1、排升序
        sort(nums.begin(),nums.end()); 

        //2、利用双指针解决问题
        int n=nums.size();
        for(int i=0;i<n-1;) //从前往后,控制固定值
        {
            int iVal = nums[i]; //记录当前固定值
            if(iVal > 0) //小优化
                break;
            int left = i+1,right = n-1;//初始区间
            while(left < right)
            {
                if(nums[left] + nums[right] > -nums[i]) //相加大于固定值的相反值
                    --right;
                else if( nums[left] + nums[right] < -nums[i]) //相加小于固定值的相反值
                    ++left;
                else //相加等于固定值的相反值
                {
                    vv.push_back({nums[left],nums[right],nums[i]});//插入满足的三元组
                    int leftVal = nums[left],rightVal = nums[right]; //记录此时left和right位置上的值,为去重做准备
                    //去重
                    while(left < right && nums[left] == leftVal)
                        ++left;
                    while(left < right && nums[right] == rightVal)
                        --right;    
                    //不直接返回继续走循环条件,防止漏数据
                }
            }
            //防止固定值重复从而引起重复数据
            while(i < n - 1 && nums[++i] == iVal);  //这里更新了i的值,那么在循环中就不需要更新了
        }
        return vv;
    }
};

五、四数之和

18. 四数之和 - 力扣(LeetCode)

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

  • 0 <= a, b, c, d < n
  • abc 和 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
  • -10^9 <= nums[i] <= 10^9
  • -10^9 <= target <= 10^9

这道题和第四题差不多一样,它们的区别是由原来的三个数变为现在的四个数,由原来固定的target为0变为现在的target不固定。

它也能暴力求解(时间复杂度为O(N^4)),我们这里只讲优解,这道题的解题思路和第四题一模一样:依次固定一个数a,在a后面的区间内,利用"三数之和"的思想,找到三个数,使它们三个数相加等于target-a。"三数之和"拆分开来就是,先固定一个数b,在b后边的区间内利用"双指针"找到两个数,使它们两个数的和等于target-a-b。

这道题同样需要额外注意两点:1、去重问题 2、不漏数据

代码实现(C++,时间复杂度为O(N^3)):

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> vv; //存放满足条件的四元组

        //1、排序
        sort(nums.begin(), nums.end());

        //2、利用双指针解决问题
        int n = nums.size();
        for (int i = 0;i < n - 1;) //固定a
        {
            //利用"三数之和"思想
            for (int j = i + 1;j < n - 1;) //固定b
            {
                int left = j + 1, right = n - 1;//控制区间
                while (left < right)
                {
                    if (nums[left] + nums[right] > (long long)target - nums[i] - nums[j]) //如果是int类型,可能会发生越界问题,比int小值还要小,所以用long long
                        --right;
                    else if (nums[left] + nums[right] < (long long)target - nums[i] - nums[j])
                        ++left;
                    else
                    {
                        vv.push_back({ nums[left],nums[right],nums[i],nums[j] });
                        int leftVal = nums[left], rightVal = nums[right];
                        //去重
                        while (left < right && nums[left] == leftVal)
                            ++left;
                        while (left < right && nums[right] == rightVal)
                            --right;
                    }
                }
                //b去重
                int jVal = nums[j];
                while (j < n - 1 && nums[++j] == jVal);
            }

            //a去重
            int iVal = nums[i];
            while (i < n - 1 && nums[++i] == iVal);
        }
        return vv;
    }
};

(完结)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2195442.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OJ在线评测系统 微服务 OpenFeign调整后端下 nacos注册中心配置 不给前端调用的代码 全局引入负载均衡器

OpenFeign内部调用二 4.修改各业务服务的调用代码为feignClient 开启nacos注册 把Client变成bean 该服务仅内部调用&#xff0c;不是给前端的 将某个服务标记为“内部调用”的目的主要有以下几个方面&#xff1a; 安全性: 内部API通常不对外部用户公开&#xff0c;这样可以防止…

Nginx05-基础配置案例

零、文章目录 Nginx05-基础配置案例 1、案例需求 &#xff08;1&#xff09;有如下访问 http://192.168.119.161:8081/server1/location1 访问的是&#xff1a;index_sr1_location1.htmlhttp://192.168.119.161:8081/server1/location2 访问的是&#xff1a;index_sr1_loca…

慢接口分析与优化总结

文章目录 1. 慢接口优化的意义2. 接口耗时构成3. 优化技巧3.1. 内部代码逻辑异步执行[异步思想]并行优化拒绝阻塞等待预分配与循环使用[池化思想]线程池合理设计锁粒度避免过粗优化程序结构 3.2. 缓存恰当引入缓存[空间换时间思想]缓存延迟优化提前初始化缓存[预取思想] 3.3. 数…

工具函数(截取文本第一个字为图片)

const subStringToImage (params) > {const { str ,color #FFF,background #4F54FF,size 60,fontSize 20 } paramsif(str.length < 0) return console.error(字符不能为空!)const text str.slice(0, 1)const canvas document.createElement(canvas)const ctx …

github 国内文件加速下载

参看;https://www.cnblogs.com/ting1/p/18356265 在源网址前加上 https://hub.gitmirror.com/ 或https://mirror.ghproxy.com/&#xff0c;例如&#xff1a; https://hub.gitmirror.com/https://github.com/t1m0thyj/WinDynamicDesktop/releases/download/v5.4.1/WinDynamicD…

算法题总结(十)——二叉树上

#二叉树的递归遍历 // 前序遍历递归LC144_二叉树的前序遍历 class Solution {public List<Integer> preorderTraversal(TreeNode root) {List<Integer> result new ArrayList<Integer>(); //也可以把result 作为全局变量&#xff0c;只需要一个函数即可。…

公开数据集网站分享

参考链接&#xff1a;常用的医学组织切片细胞图像数据集_细胞分割数据集-CSDN博客文章浏览阅读1.3w次&#xff0c;点赞32次&#xff0c;收藏133次。乳腺癌细胞图像数据集、血细胞图像数据集、HE染色切片、疟疾细胞图像图像识别、分类、分割_细胞分割数据集https://blog.csdn.ne…

Redis list 类型

list类型 类型介绍 列表类型 list 相当于 数组或者顺序表 list内部的编码方式更接近于 双端队列 &#xff0c;支持头插 头删 尾插 尾删。 需要注意的是&#xff0c;Redis的下标支持负数下标。 比如数组大小为5&#xff0c;那么要访问下标为 -2 的值可以理解为访问 5 - 2 3 …

Linux dlsym和直接调用函数地址解析分析

dlsym 函数是 Linux 下动态链接库&#xff08;shared library&#xff09;编程中的一个重要函数。它用于在运行时获取动态链接库中符号的地址&#xff0c;通常用于获取函数指针或变量的地址。 以下是 dlsym 函数的基本用法和示例。 1. 函数原型 void *dlsym(void *handle, c…

【超详细】基于YOLOv11的PCB缺陷检测

主要内容如下&#xff1a; 1、数据集介绍 2、下载PCB数据集 3、不同格式数据集预处理&#xff08;Json/xml&#xff09;&#xff0c;制作YOLO格式训练集 4、模型训练及可视化 5、Onnxruntime推理 运行环境&#xff1a;Python3.8&#xff08;要求>3.8&#xff09;&#xff…

ubuntu ssh远程执行k8s命令报错the connection to the server localhost:8080 was refused

修改前&#xff1a; ssh root192.168.31.167 kubectl apply -f /root/jenkinsexcute/saas.demo.api.k8s.yml --recordecho "export KUBECONFIG/etc/kubernetes/admin.conf" >> /root/.bashrc 修改后 添加一段&#xff1a;export KUBECONFIG/etc/kubernetes/a…

【专题】操作系统概述

1. 操作系统的目标和作用 操作系统的目标与应用环境有关。 在查询系统中所用的OS&#xff0c;希望能提供良好的人—机交互性&#xff1b; 对于应用于工 业控制、武器控制以及多媒体环境下的OS&#xff0c;要求其具有实时性&#xff1b; 对于微机上配置的OS&#xff0c;则更看…

什么是强基计划?

“强基计划”是中国教育部于2020年推出的一项全新的高等教育招生改革计划&#xff0c;旨在通过更加科学、公正的选拔机制&#xff0c;选拔出有志于基础学科并具备扎实学科功底、创新潜质的优秀学生&#xff0c;从而推动国家基础学科的发展&#xff0c;提升自主创新能力。与传统…

【自动驾驶】UniAD代码解析

1.参考 论文&#xff1a;https://arxiv.org/pdf/2212.10156 代码&#xff1a;https://github.com/OpenDriveLab/UniAD 2.环境配置 docs/INSTALL.md &#xff08;1&#xff09;虚拟conda环境 conda create -n uniad python3.8 -y conda activate uniad &#xff08;2&#…

微信小程序和抖音小程序的分享和广告接入代码

开发完成小程序或者小游戏之后&#xff0c;我们为什么要接入分享和广告视频功能&#xff0c;主要原因有以下几个方面。 微信小程序和抖音小程序接入分享和广告功能主要基于以下几个原因&#xff1a; 用户获取与增长&#xff1a;分享功能可以帮助用户将小程序内容传播给更多人&…

C语言刷题--有关闰年

满足以下一种即是闰年 能被4整除&#xff0c;但不能被100整除能被400整除 //输入年&#xff0c;月&#xff0c;输出该月的天数 //1月 31,28,31,30,31,30,31,31,30,31,30,31int is_leap_year(int y) {if (((y % 4 0) && (y % 100 ! 0)) || y % 400 0)return 1;return…

步进电机和步进电机驱动器详解

一、步进电机的概念 步进电机是通过步进电机配套的驱动器&#xff0c;将控制器传来的脉冲信号转换成角位移的开环电机&#xff08;没有反馈&#xff09;。 步进电机工作时的位置和速度信号不反馈给控制系统&#xff0c;如果电机工作时的位置和速度信号反馈给控制系统&#xff…

《从零开始大模型开发与微调》真的把大模型说透了!零基础入门一定要看!

2022年底&#xff0c;ChatGPT震撼上线&#xff0c;大语言模型技术迅速“席卷”了整个社会&#xff0c;人工智能技术因此迎来了一次重要进展。与大语言模型相关的研发岗薪资更是水涨船高&#xff0c;基本都是5w月薪起。很多程序员也想跟上ChatGPT脚步&#xff0c;今天给大家带来…

apache.poi读取.xls文件时The content of an excel record cannot exceed 8224 bytes

目录 问题描述版本定位&#xff1a;打印size最大的Record定位&#xff1a;RefSubRecord解决代码 问题描述 使用apache.poi读取.xls文件时有The content of an excel record cannot exceed 8224 bytes的报错。待读取的文件的内容也是通过apache.poi写入的&#xff0c;我的文件修…

【sqlmap】sqli-labs速通攻略

sqli-labs工具速通 Less-1 sqlmap -u http://127.0.0.1:8081/Less-1/?id1 --batch --dbs sqlmap -u http://127.0.0.1:8081/Less-1/?id1 --batch -D security --tables sqlmap -u http://127.0.0.1:8081/Less-1/?id1 --batch -D security -T users --columns sqlmap -u ht…