【优选算法篇】双指针的华丽探戈:深入C++算法殿堂的优雅追寻

news2024/10/17 18:51:06

文章目录

  • C++ 双指针详解:进阶题解与思维分析
    • 前言
    • 第一章:有效三角形的个数
      • 1.1 有效三角形的个数
        • 示例 1:
        • 示例 2:
        • 解法一(暴力求解)
        • 解法二(排序 + 双指针)
        • 易错点提示
        • 代码解读
    • 第二章:和为 s 的两个数字
      • 2.1 和为 s 的两个数字
        • 示例 1:
        • 解法一(暴力解法)
        • 解法二(双指针 - 对撞指针)
    • 第三章:三数之和与四数之和
      • 3.1 三数之和
        • 示例 1:
        • 示例 2:
        • 示例 3:
        • 提示:
        • 解法(排序+双指针)
      • 3.2 四数之和
        • 示例 1:
        • 示例 2:
        • 提示:
        • 解法(排序 + 双指针)
    • 写在最后

C++ 双指针详解:进阶题解与思维分析

💬 欢迎讨论:如有疑问或见解,欢迎在评论区留言互动。

👍 点赞、收藏与分享:如觉得这篇文章对您有帮助,请点赞、收藏并分享!
🚀 分享给更多人:欢迎分享给更多对 C++ 感兴趣的朋友,一起学习双指针的基础与进阶!


前言

接上篇【优选算法篇】双指针的优雅舞步:C++ 算法世界的浪漫探索

本篇文章将带领大家进入双指针的进阶领域。在基础篇中,我们已经学习了如何利用双指针优化简单数组问题,而在这一篇中,我们将进一步深入探讨双指针的高级应用场景,包括排序问题、多数之和等经典题型的双指针解法,以及如何利用双指针快速解决复杂的数组与链表问题。

通过更加深入的题目分析和双指针的高级策略,我们希望大家能够更加熟练地运用这一算法技巧,应对更具挑战性的编程问题。让我们继续双指针的优雅舞步,开启 C++ 算法世界的浪漫探索!


第一章:有效三角形的个数

1.1 有效三角形的个数

题目链接:611. 有效三角形的个数
题目描述:给定一个包含非负整数的数组 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

解法一(暴力求解)

算法思路

  • 三层 for 循环枚举出所有的三元组,并判断是否能构成三角形。
  • 判断三角形的优化:
    • 如果能构成三角形,需要满足任意两边之和大于第三边。但实际上只需让较小的两条边之和大于第三边即可。
    • 因此可以先将原数组排序,然后从小到大枚举三元组,一方面省去枚举的数量,另一方面方便判断是否能构成三角形。

代码实现

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

复杂度分析

  • 时间复杂度O(n^3),对于较大的输入,三层循环会导致性能问题,适合小规模数据。
  • 空间复杂度O(1),只使用了常数个变量存储结果和中间值。

解法二(排序 + 双指针)

算法思路

  • 先将数组排序。
  • 根据「解法一」中的优化思想,可以固定一个「最长边」,然后在比这条边小的有序数组中找出一个二元组,使这个二元组之和大于这个最长边。由于数组是有序的,我们可以利用「对撞指针」来优化。
  • 设最长边枚举到位置 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++ 进入下一轮循环。

代码实现

class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        // 1. 排序
        sort(nums.begin(), nums.end());
        // 2. 利用双指针解决问题
        int ret = 0, n = nums.size();
        for (int i = n - 1; i >= 2; i--) { // 先固定最大的数
            // 利用双指针快速统计符合要求的三元组的个数
            int left = 0, right = i - 1;
            while (left < right) {
                if (nums[left] + nums[right] > nums[i]) {
                    ret += right - left;
                    right--;
                } else {
                    left++;
                }
            }
        }
        return ret;
    }
};

复杂度分析

  • 时间复杂度O(n^2),排序的时间复杂度为 O(n log n),之后每个元素使用双指针进行一次遍历,时间复杂度为 O(n^2)
  • 空间复杂度O(1),只使用了常数个变量存储结果和指针位置。

易错点提示
  1. 指针移动逻辑:在双指针遍历时,根据条件选择移动 leftright,确保找到所有满足条件的三元组。
  2. 数组排序:在开始双指针遍历之前,必须对数组进行排序,否则无法保证正确性。
  3. 三角形判定条件:确保只需判断两边之和是否大于第三边,简化条件判断,避免遗漏有效三元组。

代码解读

该算法的时间复杂度为 O(n^2),空间复杂度为 O(1),适合大规模输入。通过排序和双指针优化,能够有效减少暴力求解中的不必要计算,提升性能。此方法非常适合在数组问题中应用,能够快速找到所有满足条件的组合。


第二章:和为 s 的两个数字

2.1 和为 s 的两个数字

题目链接:剑指 Offer 57. 和为s的两个数字
题目描述:输入一个递增排序的数组和一个数字 s,在数组中查找两个数,使得它们的和正好是 s。如果有多对数字的和等于 s,则输出任意一对即可。

示例 1:
  • 输入:nums = [2, 7, 11, 15], target = 9
  • 输出:[2, 7] 或者 [7, 2]

解法一(暴力解法)

算法思路

  • 两层 for 循环列出所有两个数字的组合,判断是否等于目标值。

算法流程

  • 两层 for 循环:
    • 外层 for 循环依次枚举第一个数 a
    • 内层 for 循环依次枚举第二个数 b,让它与 a 匹配;
    • 然后将挑选的两个数相加,判断是否符合目标值。

代码实现

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++) {
                if (nums[i] + nums[j] == target)
                    return {nums[i], nums[j]};
            }
        }
        return {-1, -1};
    }
};

解法二(双指针 - 对撞指针)

算法思路

  • 注意到本题是升序的数组,因此可以使用「对撞指针」优化时间复杂度。

算法流程

  • 初始化 leftright 分别指向数组的左右两端:
    • left < right 的时候,一直循环:
      • nums[left] + nums[right] == target 时,说明找到结果,记录结果,并返回;
      • nums[left] + nums[right] < target 时:
        • 说明当前和小于目标值,需要增大和,left++
      • nums[left] + nums[right] > target 时:
        • 说明当前和大于目标值,需要减小和,right--

代码实现

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 {-1, -1};
    }
};

第三章:三数之和与四数之和

3.1 三数之和

题目链接:15. 三数之和
题目描述:给你一个整数数组 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
  • -10^5 <= nums[i] <= 10^5

解法(排序+双指针)

算法思路

  • 本题与两数之和类似,是非常经典的面试题。
  • 与两数之和稍微不同的是,题目中要求找到所有「不重复」的三元组。我们可以利用双指针思想来对暴力枚举做优化:
    1. 先排序;
    2. 然后固定一个数 a
    3. 在这个数后面的区间内,使用「双指针算法」快速找到两个数之和等于 -a 即可。
  • 需要注意的是,这道题中需要有「去重」操作:
    1. 找到一个结果之后,leftright 指针要「跳过重复」的元素;
    2. 当使用完一次双指针算法之后,固定的 a 也要「跳过重复」的元素。

代码实现

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

3.2 四数之和

题目链接:18. 四数之和
题目描述:给你一个由 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
  • -10^9 <= nums[i] <= 10^9
  • -10^9 <= target <= 10^9

解法(排序 + 双指针)

算法思路

  1. 依次固定一个数 a
  2. 在这个数 a 的后面区间上,利用「三数之和」找到三个数,使这三个数的和等于 target - a 即可。

代码实现

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--]});
                        // 去重操作 left 和 right
                        while (left < right && nums[left] == nums[left - 1]) left++;
                        while (left < right && nums[right] == nums[right + 1]) right--;
                    }
                }
                // 去重 j
                j++;
                while (j < n && nums[j] == nums[j - 1]) j++;
            }
            // 去重 i
            i++;
            while (i < n && nums[i] == nums[i - 1]) i++;
        }
        return ret;
    }
};

写在最后

在本篇文章中,我们沿着双指针的足迹,走进了更为复杂的算法世界。从基础的排序与两数之和,到多元问题的优化解法,双指针以其灵活而高效的策略,为我们提供了简洁优雅的解题思路。无论是解锁数组中的隐藏结构,还是精确处理链表中的循环,双指针始终如同算法中的舞者,轻巧地穿梭于问题的复杂性之间,帮助我们化繁为简。

希望通过这些进阶题解,大家能不仅熟悉双指针的运用技巧,更能深刻体会到算法设计中的思维之美。未来的算法旅程中,无论面对怎样的挑战,双指针这一工具都能在你的编程工具箱中,成为应对复杂问题时得心应手的利器。

以上就是关于【优选算法篇】双指针的华丽探戈:深入C++算法殿堂的优雅追寻的内容啦,各位大佬有什么问题欢迎在评论区指正,或者私信我也是可以的啦,您的支持是我创作的最大动力!❤️

在这里插入图片描述

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

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

相关文章

C++的魔法世界:类和对象的终章

文章目录 一、再探构造函数二、类型转换2.1隐式类型转换2.2内置类型的类型转化2.3explicit关键字2.4多参数构造 三、static成员四、友元五、内部类内部类的特性 六、匿名对象 一、再探构造函数 类和对象(中)里介绍的构造函数&#xff0c;使用的是赋值实现成员变量的初始化。而…

【word】文章里的表格边框是双杠

日常小伙伴们遇到word里插入的表格&#xff0c;边框是双杠的&#xff0c;直接在边框和底纹里修改边框的样式就可以&#xff0c;但我今天遇到的这个有点特殊&#xff0c;先看看表格在word里的样式是怎么样&#xff0c;然后我们聊聊如何解决。 这个双杠不是边框和底纹的设置原因…

亚洲 Web3 市场:Q3 监管变化与市场驱动力探析

概述&#xff1a; 亚洲的 Web3 市场在2024年第三季度继续表现出强劲增长势头。得益于技术精通的人口基础、政府的积极政策导向和企业的大规模参与&#xff0c;韩国、日本、越南等国家已然走在行业前沿。此外&#xff0c;随着越来越多的监管框架落地&#xff0c;区块链创新不断…

Ubuntu20.04下安装多CUDA版本,以及后续切换卸载

本方案的前提是假设机子上已经有一个版本的cuda&#xff0c;现在需要支持新的torch2.1.2和torchvision0.16.2&#xff0c;于是来安装新的cuda 一、选择版本 如果我想安装支持torch2.1.2的cuda版本&#xff0c;到官网&#xff08;https://pytorch.org/get-started/previous-ve…

【Python文件操作】掌握文件读写和目录管理的技巧!

【Python文件操作】掌握文件读写和目录管理的技巧&#xff01; 在现代编程中&#xff0c;文件操作是不可避免的一部分&#xff0c;尤其是在处理数据、日志、配置文件等场景下。Python 提供了强大而简洁的文件操作方法&#xff0c;可以轻松完成文件的读取、写入和目录管理等操作…

005_django基于Python的乡村居民信息管理系统设计与实现2024_106f2qg9

目录 系统展示 开发背景 代码实现 项目案例 获取源码 博主介绍&#xff1a;CodeMentor毕业设计领航者、全网关注者30W群落&#xff0c;InfoQ特邀专栏作家、技术博客领航者、InfoQ新星培育计划导师、Web开发领域杰出贡献者&#xff0c;博客领航之星、开发者头条/腾讯云/AW…

SpringMVC源码-异常处理机制

定义一个异常处理类TestErrorController: Controller public class TestErrorController {RequestMapping("/exception")public ModelAndView exception(ModelAndView view) throws ClassNotFoundException {view.setViewName("index");throw new ClassNot…

Mysql主从集群搭建+分库分表+ShardingSphere(实战)

什么是 ShardingSphere 介绍 Apache ShardingSphere 是一款分布式的数据库生态系统&#xff0c; 可以将任意数据库转换为分布式数据库&#xff0c;并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。 Apache ShardingSphere 设计哲学为 Database Plus&#xff0c;…

CRMEB标准版Mysql修改sql_mode

数据库配置 1.宝塔控制面板-软件商店-MySql-设置 2.点击配置修改&#xff0c;查找sql-mode或sql_mode &#xff08;可使用CtrlF快捷查找&#xff09; 3.复制 NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION 然后替换粘贴&#xff0c;保存 注&#xff1a;MySQL8.0版本的 第三步用…

Redis --- 第四讲 --- 常用数据结构 --- string类型

一、认识数据类型和编码方式 有序集合&#xff0c;相当于除了存储member之外&#xff0c;还需要存储一个score&#xff08;权重&#xff0c;分数&#xff09; Redis底层在实现上述数据结构的时候&#xff0c;会在源码层面&#xff0c;针对上述实现进行特定的优化&#xff0c;来…

文生图:Stable Diffusion、Midjourny

前言 Stable Diffusion&#xff08;SD&#xff09;和Midjourney&#xff08;MJ&#xff09;是当前流行的两款AI图像生成工具&#xff0c;它们各有特点和优势&#xff1a; **- Stable Diffusion是完全开源的&#xff0c;**这意味着用户可以免费使用&#xff0c;并且有技术能力…

excel如何把年龄转换为日期

总体的思路 我们从一个核心的日期函数出发 我们首先需要年月日 我的数据大概是这样的。 获取年份 第一步&#xff1a;提取岁前面的数字 left(目标单元格&#xff0c;“从左到右获取第几个字符”)第二步:替换掉数字后面的岁 第三步:新增一个单元格 在里面填入年 第四步:用…

Android系統Audio hal

一.Android系統Audio hal简介 Android系统的音频硬件抽象层(HAL)是系统与硬件之间的桥梁,允许音频应用和服务访问底层音频硬件,而无需直接与硬件交互。 主要组件: 音频 HAL 接口:定义了应用和服务如何调用音频硬件的规范。典型的音频操作包括播放、录制、音量控制等。 …

N1060A 50/85GHz精密型波形分析模块

N1060A 50/85GHz精密型波形分析模块 苏州新利通 概述 Keysight N1060A 精密型波形分析仪是一款数字通信分析仪&#xff08;DCA&#xff09;模块&#xff0c;可与 Keysight N1000A 主机兼容。 与是德科技的所有其他 DCA 模块一样&#xff0c;N1060A 提供了广泛的配置和性能选…

【C语言】数组函数冒泡排序bubble sort

数组&#xff1a;对于n个数字进行排序&#xff0c;就必须定义n个变量来存储。那么为了统一处理&#xff0c;选择数组就十分便捷了。 函数&#xff1a;将排序算法写到函数中&#xff0c;后续遇到所有的排序需求&#xff0c;都可以直接进行调用。 冒泡排序&#xff1a;受气泡在水…

HDFS详细分析

目录 一、HDFS架构 &#xff08;1&#xff09;Block - 数据块 &#xff08;2&#xff09;MetaData - 元数据 &#xff08;3&#xff09;NameNode - 主结点 &#xff08;4&#xff09;DataNode - 从结点 &#xff08;5&#xff09;SecondaryNameNode 二、HDFS的特点 &…

【19楼-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

2024年下软考——信息系统运行管理员考前30天冲刺学习指南!!!

2024下半年软考已经迫在眉睫了&#xff0c;还没有开始备考的小伙伴赶紧行动起来。为了帮助大家更好的冲刺学习&#xff0c;特此提供一份信息系统运行管理员考前30天学习指南。本指南包括考情分析、学习规划、冲刺攻略三个部分&#xff0c;可以参考此指南进行最后的复习要领&…

javaWeb项目-Springboot+vue-校园论坛系统功能介绍

本项目源码&#xff08;点击下方链接下载&#xff09;&#xff1a;java-springbootvue-xx学校校园论坛信息系统实现源码(项目源码-说明文档)资源-CSDN文库 项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot…

鼠标移入盒子,盒子跟随鼠标移动

demo效果&#xff1a; 鼠标移入盒子&#xff0c;按下鼠标,开启移动跟随移动模式,再次按下关闭移动模式 涉及主要属性 在元素上单击鼠标按钮时输出鼠标指针的坐标&#xff1a; var x event.pageX; // 获取水平坐标 var y event.pageY; // 获取垂直坐标元素offsetL…