【九】【算法分析与设计】双指针(3)

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)

暴力枚举:

为了避免出现重复的元素,我们对nums数组进行排序,然后把相同的元素不计算,直接跳过。

题目的要求是不包含重复的三元组,所以我没每一次后移i,j,k的时候不可以简单的后移,而是往后移动到下一个不重复的数字上,这样就不会重复计算相同的元素。

 
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        vector<vector<int>> ret;
        int n = nums.size();
        for (int i = 0; i < n;) {
            for (int j = i + 1; j < n;) {
                for (int k = j + 1; k < n;) {
                    if (nums[i] + nums[j] + nums[k] == 0) {
                        ret.push_back({nums[i], nums[j], nums[k]});
                    }
                    k++;
                    while (k < n && nums[k] == nums[k - 1])
                        k++;
                }
                j++;
                while (j < n && nums[j] == nums[j - 1])
                    j++;
            }
            i++;
            while (i < n && nums[i] == nums[i - 1])
                i++;
        }
        return ret;
    }
};

双指针优化暴力枚举:

首先对nums排序。

固定三元组中最大的一个数,记为nums[i],那么问题就转化为找两数之和为target=-nums[i]的组合。令left=0,right=i-1,我们需要遍历的是[left,right]区间内,每两两组合。

sum=nums[left]+nums[right],如果sum>target,那么对于rightleft+1,left+2.....right-1这些数的组合一定也大于target,因为nums是有序的,下标和值分别看作x与y函数,函数就是递增的。所以nums[left+1],nums[left+2].....一定大于nums[left],所以right与后面的组合都可以不用再考虑,一定也不符合题意。这样对于right的组合情况就全部考虑完毕了。此时对right--,相同的数再次right--,直到数不相同,防止重复计算。

如果sum<target,那么对于leftright-1,right-2....left+1这些数的组合一定也小于target,因为nums是有序的,下标和值分别看作xy函数,函数就是递增的。所以nums[right-1],nums[right-2].....一定小于nums[right],所以left与后面的组合都可以不用再考虑,一定也不符合题意。这样对于left的组合情况就全部考虑完毕了。此时对left++,相同的数再次left++,直到数不相同,防止重复计算。

 
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        vector<vector<int>> ret;
        int n = nums.size();
        for (int i = n - 1; i >= 2;) {
            int left = 0, right = i - 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[left++], nums[right--], nums[i]});

                    while (left < right && nums[left] == nums[left - 1])
                        left++;
                    while (left < right && nums[right] == nums[right + 1])
                        right--;
                }
            }

            i--;
            while (i >= 2 && nums[i] == nums[i + 1])
                i--;
        }

        return ret;
    }
};

class Solution {public: vector<vector<int>> threeSum(vector<int>& nums) {

定义了一个名为 threeSum 的成员函数,它接受一个名为 nums 的整数向量的引用作为参数,并返回一个二维向量,其中包含所有满足条件的三元组。

sort(nums.begin(), nums.end());

首先对输入的向量 nums 进行排序,这是为了后续能够使用双指针技术来避免重复的三元组并简化搜索过程。

vector<vector<int>> ret;

定义了一个名为 ret 的二维向量,用于存储最终的结果。

int n = nums.size();for (int i = n - 1; i >= 2;) {

获取 nums 的大小赋值给 n,并从数组的末尾开始遍历,因为我们要找三个数的组合,所以至少需要3个数。

int left = 0, right = i - 1;int target = -nums[i];

初始化两个指针 leftright,分别指向当前子数组的开始和 i 之前的位置。target 是我们要找的两数之和,它是 nums[i] 的相反数。

while (left < right) {int sum = nums[left] + nums[right];

left 指针小于 right 指针的情况下,计算 leftright 指向的两个数的和。

if (sum > target) { right--;} else if (sum < target) { left++;} else { ret.push_back({nums[left++], nums[right--], nums[i]});

如果 sum 大于 target,需要减小 sum,所以 right 指针左移,可以理解为与right组合的所有情况考虑完毕;如果 sum 小于 target,需要增大 sum,所以 left 指针右移,可以理解为与left组合的所有情况考虑完毕;如果 sum 等于 target,说明找到了一个有效的三元组,将其添加到结果 ret 中,并移动两个指针寻找下一个可能的三元组。

while (left < right && nums[left] == nums[left - 1]) left++;while (left < right && nums[right] == nums[right + 1]) right--;

这两个循环用来跳过相同的元素,避免重复的三元组。

i--;while (i >= 2 && nums[i] == nums[i + 1]) i--;

递减 i 并跳过相同的元素,以便在下一轮循环中处理不同的 nums[i]

时间复杂度和空间复杂度分析

时间复杂度:O(n^2),其中 n 是数组 nums 的长度。外层循环最多执行 n 次,内层循环(双指针)在每次外层循环中最多执行 n 次。

空间复杂度:O(1) 或 O(n),这取决于排序算法的实现,如果排序算法是原地排序,则空间复杂度为 O(1),否则为 O(n)。

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)

暴力枚举:

为了避免重复计算,我们先把nums数组进行排序,然后跳过计算相同的元素。对于i,j,k,l每一次不是简单的往后移一位,而是往后移到下一个不重复的元素位置。

 
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        int n = nums.size();
        vector<vector<int>> ret;
        sort(nums.begin(),nums.end());
        for (int i = 0; i < n;) {
            for (int j = i + 1; j < n;) {
                for (int k = j + 1; k < n;) {
                    for (int l = k + 1; l < n;) {
                        if ((long long)nums[i] + nums[j] + nums[k] + nums[l] == target)
                            ret.push_back({nums[i], nums[j], nums[k], nums[l]});

                        l++;
                        while (l < n && nums[l] == nums[l - 1])
                            l++;
                    }
                    k++;
                    while (k < n && nums[k] == nums[k - 1])
                        k++;
                }
                j++;
                while (j < n && nums[j] == nums[j - 1])
                    j++;
            }
            i++;
            while (i < n && nums[i] == nums[i - 1])
                i++;
        }

        return ret;
    }
};

双指针优化暴力枚举:

首先固定一位数,问题转化为求三数和,再固定一位,问题转化为求两数和,求两数和用双指针,对撞指针进行暴力枚举的优化,快速找到符合条件的情况。

需要注意的是数据的计算过程又可以超出界限,所以aim我们不用int而用longlong,后面的计算表达式只需要把一个强转为longlong即可。

 
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        int n = nums.size();
        vector<vector<int>> ret;
        sort(nums.begin(), nums.end());

        for (int i = 0; i < n;) {
            for (int j = i + 1; j < n;) {
                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]});
                        right--;
                        left++;
                        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;
    }
};

class Solution {public: vector<vector<int>> fourSum(vector<int>& nums, int target) {

定义了一个名为 fourSum 的成员函数,它接受一个整数数组 nums 和一个目标值 target 作为参数,并返回一个二维向量,其中包含所有满足条件的四元组。

int n = nums.size(); vector<vector<int>> ret;sort(nums.begin(), nums.end());

获取 nums 的大小并赋值给 n,定义一个用于存储结果的二维向量 ret,并对 nums 进行排序,排序就作用是便于我们进行去重操作。

for (int i = 0; i < n;) {

使用外层循环遍历 numsi 是四元组中第一个数字的位置。

for (int j = i + 1; j < n;) {

使用内层循环遍历 numsj 是四元组中第二个数字的位置。

int left = j + 1, right = n - 1;long long aim = (long long)target - nums[i] - nums[j];

初始化双指针 leftright,分别指向四元组中第三和第四个数字的起始位置。计算剩余两数的目标和 aim

while (left < right) {int sum = nums[left] + nums[right];

left 小于 right 的情况下,计算 leftright 指针指向的两个数的和。

if (sum < aim) left++;else if (sum > aim) right--;

如果两数之和小于 aim,则将 left 指针右移以增加和,可以理解为对于left的所有组合都考虑完毕;如果大于 aim,则将 right 指针左移以减小和,可以理解为对于right的所有组合都考虑完毕。

else { ret.push_back({nums[i], nums[j], nums[left], nums[right]}); right--; left++;

如果找到满足条件的两数之和等于 aim,将四个数作为一个四元组添加到结果集 ret 中,并同时移动 leftright 指针。

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

内层循环结束,j 指针右移并跳过重复的元素。

} i++;while (i < n && nums[i] == nums[i - 1]) i++;

外层循环结束,i 指针右移并跳过重复的元素。

时间复杂度和空间复杂度分析

时间复杂度:O(n^3),其中 n 是数组 nums 的长度。最外层循环和第二层循环分别执行了 n 次,内层的双指针循环最多执行 n 次。

空间复杂度:O(1) 或 O(n),这取决于排序算法的实现。如果排序算法是原地的,则空间复杂度为 O(1),否则为 O(n)。

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot(数据库操作 + druid监控功能)

文章目录 1.JDBC HikariDataSource&#xff08;SpringBoot2默认数据源&#xff09;1.数据库表设计2.引入依赖 pom.xml3.配置数据源参数 application.yml4.编写一个bean&#xff0c;映射表5.编写测试类来完成测试1.引入依赖 pom.xml2.使用JdbcTemplate进行测试3.成功&#xff0…

STM32信息安全 1.2 课程架构介绍:芯片生命周期管理与安全调试

STM32信息安全 1.2 课程架构介绍&#xff1a;STM32H5 芯片生命周期管理与安全调试 下面开始学习课程的第二节&#xff0c;简单介绍下STM32H5芯片的生命周期和安全调试&#xff0c;具体课程大家可以观看STM32官方录制的课程&#xff0c;链接&#xff1a;1.2. 课程架构介绍&…

01背包问题详解

01背包问题是动态规划问题的子背包问题&#xff0c;算是蓝桥杯以及CSP较为常考的一种题型。 这种问题是有一个板子的&#xff0c;非常简单 #include <bits/stdc.h> using namespace std;int k[200],v[200],dp[130][130]; int main() {int t,m;cin>>t>>m;fo…

【鸿蒙HarmonyOS开发笔记】常用组件介绍篇 —— Toggle切换按钮组件

概述 Toggle为切换按钮组件&#xff0c;一般用于两种状态之间的切换&#xff0c;例如下图中的蓝牙开关。 参数 Toggle组件的参数定义如下 Toggle(options: { type: ToggleType, isOn?: boolean })● type type属性用于设置Toggle组件的类型&#xff0c;可通过ToggleType枚举…

【MIT 6.S081】2020, 实验记录(9),Lab: file system

目录 Task 1&#xff1a;Large filesTask 2&#xff1a;Symbolic links2.1 增加一个系统调用 symlink2.2 新增文件类型2.3 新增 NOFOLLOW 标志位2.4 实现 sys_symlink 系统调用2.5 修改 sys_open 函数2.6 测试 Task 1&#xff1a;Large files 现在的 xv6 系统中&#xff0c;一…

基础:TCP三次握手做了什么,为什么要握手?

1. TCP 三次握手在做些什么 1. 第一次握手 &#xff1a; 1&#xff09;握手作用&#xff1a;客户端发出建立连接请求。 2&#xff09;数据处理&#xff1a;客户端发送连接请求报文段&#xff0c;将SYN位置为1&#xff0c;Sequence Number为x;然后&#xff0c;客户端进入SYN_S…

Swagger Array 使用指南:详解与实践

Swagger 允许开发者定义 API 的路径、请求参数、响应和其他相关信息&#xff0c;以便生成可读性较高的文档和自动生成客户端代码。而 Array &#xff08;数组&#xff09;是一种常见的数据结构&#xff0c;用于存储和组织多个相同类型的数据元素。数组可以有不同的维度和大小&a…

几个精品声音模型

AI技术提取某位歌手的音色&#xff0c;再用其替换另一位歌手音色的方式&#xff0c;可以实现接近歌手本人翻唱的逼真效果。无需学习其他伪音技巧&#xff0c;即可实现实时男女声音互换等等。 使用 RVC 及模型工具&#xff0c;可以实现以下几个功能&#xff1a; 音乐干声分离&…

【兔子机器人】实现从初始状态到站立

一、遥想星空up主的方法 由于我有卡位结构&#xff0c;无法做到劈腿&#xff0c;而且底盘也不一样&#xff0c;无法使用此方法 但是其代码思想是可以借鉴的。 参考视频&#xff1a; 【【开源啦&#xff01;】无刷轮腿平衡机器人】 【精准空降到 01:16】 https://www.bilibili…

uniapp 对video视频组件嵌套倍速按钮

这次接了需求是要求有倍速功能&#xff0c;去看了文档发现并没有倍速按钮的属性&#xff0c;想着手写一个吧 可最后发现原生层级太高&#xff0c;无论怎么样都迭不上去&#xff0c;就只能去找插件看看咯 找了好多插件发现都不可用&#xff0c;因为我这是app端&#xff0c;有些视…

旅游管理系统|基于SpringBoot+ Mysql+Java+Tomcat技术的旅游管理系统设计与实现(可运行源码+数据库+设计文档+部署说明+视频演示)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 前台功能效果图 用户功能 管理员功能登录前台功能效果图 系统功能设计 数据库E-R图设计 lunwen参考 …

深度学习——数据预处理

一、数据预处理 为了能用深度学习来解决现实世界的问题&#xff0c;我们经常从预处理原始数据开始&#xff0c; 而不是从那些准备好的张量格式数据开始。 在Python中常用的数据分析工具中&#xff0c;我们通常使用pandas软件包。 像庞大的Python生态系统中的许多其他扩展包一样…

【JVM篇】类的生命周期

文章目录 &#x1f354;类的生命周期概述⭐加载⭐连接⭐初始化⭐类的卸载 &#x1f354;类的生命周期概述 Java类的生命周期包括加载&#xff08;Loading&#xff09;、验证&#xff08;Verification&#xff09;、准备&#xff08;Preparation&#xff09;、解析&#xff08;R…

TrueNAS怎么设置中文,最新2024版本安装详细说明

首先我们做好安装前的准备工作 1&#xff0c;ISO镜像安装包 2&#xff0c;虚拟机&#xff08;建议使用ESXI虚拟机环境&#xff09; 如果是物理机安装&#xff0c;建议先给底层安装虚拟机系统esxi&#xff0c;再在上面安装方便以后的管理&#xff0c;如果你想物理机直接安装&a…

【Redis】缓存穿透

问题发生背景&#xff1a;客户端请求的数据再缓存中和数据库中都不存在。 导致的问题&#xff1a;缓存永远不会生效&#xff0c;这些请求都会去请求数据库—导致数据库压力增大。 解决方案&#xff1a; 1.缓存空对象 在Redis中缓存空对象&#xff0c;告诉客户端数据库中没有该值…

zookeeper快速入门五:用zookeeper实现服务注册与发现中心

系列&#xff1a; zookeeper快速入门一&#xff1a;zookeeper安装与启动-CSDN博客 zookeeper快速入门二&#xff1a;zookeeper基本概念-CSDN博客 zookeeper快速入门三&#xff1a;zookeeper的基本操作 zookeeper快速入门四&#xff1a;在java客户端中操作zookeeper-CSDN博客…

【Python】线程—GIL—asyncio

文章目录 一、Python 线程二、threading 模块三、例程3.1 基本用法3.2 同步3.21 Lock&#xff08;锁&#xff09;3.22 RLock&#xff08;递归锁&#xff09;3.23 Condition&#xff08;条件变量&#xff09;3.24 Semaphore&#xff08;信号量&#xff09; 四、GIL4.1 简述4.2 详…

MySQL教程-SQL

SQL(Structured Query Language)结构化查询语言&#xff0c;操作关系型数据库的编程语言&#xff0c;定义了一套操作关系型数据库统一标准。 语法 SQL语句可以单行或多行书写&#xff0c;以;为结束标记SQL可以使用空格或缩进来增强语句的可读性SQL分单行注释(-- 注释内容 或 …

跨境电商应该用什么样的服务器?多大带宽?

跨境电商在选择服务器 和带宽时&#xff0c;需要考虑多个因素&#xff0c;包括业务规模、用户数量、网站流量、地理位置等。下面是一些关键考虑因素&#xff1a; 1、服务器类型 跨境电商通常会选择使用云服务器&#xff0c;因为云服务器具有灵活性、可扩展性和高可用性。云服务…

做户用光伏代理赚钱吗

随着全球能源危机的加剧和环境问题的日益严重&#xff0c;清洁能源的开发和利用成为了一个重要的议题。光伏发电作为一种绿色、可再生的能源&#xff0c;在全球范围内得到了广泛的关注和应用。 一、代理农村光伏项目挣钱吗 随着国家对光伏发电的政策支持和补贴&#xff0c;以及…