【五】【算法分析与设计】双指针的初见

167. 两数之和 II - 输入有序数组

给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index(1)]numbers[index(2)] ,则 1 <= index(1) < index(2) <= numbers.length

以长度为 2 的整数数组 [index(1), index(2)] 的形式返回这两个整数的下标 index(1) index(2)

你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

你所设计的解决方案必须只使用常量级的额外空间。

示例 1:

输入:numbers = [2,7,11,15], target = 9 输出:[1,2] 解释:2 与 7 之和等于目标数 9 。因此 index(1) = 1, index(2) = 2 。返回 [1, 2] 。

示例 2:

输入:numbers = [2,3,4], target = 6 输出:[1,3] 解释:2 与 4 之和等于目标数 6 。因此 index(1) = 1, index(2) = 3 。返回 [1, 3] 。

示例 3:

输入:numbers = [-1,0], target = -1 输出:[1,2] 解释:-1 与 0 之和等于目标数 -1 。因此 index(1) = 1, index(2) = 2 。返回 [1, 2] 。

提示:

  • 2 <= numbers.length <= 3 * 10(4)

  • -1000 <= numbers[i] <= 1000

  • numbers非递减顺序 排列

  • -1000 <= target <= 1000

  • 仅存在一个有效答案

 
vector<int> twoSum(vector<int>& numbers, int target) {
    int l = 0, r = numbers.size() - 1, sum;
    while (l < r) {
        sum = numbers[l] + numbers[r];
        if (sum == target) break;
        if (sum < target) ++l;
        else --r;
    }
    return vector<int> {l + 1, r + 1};
 }

这段代码是一个实现“两数之和”问题的函数,使用了双指针技巧。它的目标是在一个已排序的数组中找到两个数,使得它们的和等于给定的目标值target。找到后,返回这两个数的索引+1(题目假定索引从1开始而不是从0开始)。

int l = 0, r = numbers.size() - 1, sum;这一行初始化了两个指针,l(左指针)和r(右指针),分别指向数组的开始位置和结束位置。同时,声明了一个sum变量用于存储两个指针所指元素的和。

while (l < r) {这是一个while循环,条件是左指针l小于右指针r。这个条件保证了在数组内部移动指针时不会越界或重叠。

sum = numbers[l] + numbers[r];在循环体内,计算当前左指针和右指针所指向的元素的和。

if (sum == target) break;如果这个和等于目标值target,则跳出循环,因为已经找到满足条件的两个数。

if (sum < target) ++l; else --r;如果这个和小于目标值target,则将左指针向右移动一位(因为数组已排序,这样做可以增加和的值)。否则,将右指针向左移动一位(这样做可以减少和的值)。

return vector<int> {l + 1, r + 1};最后,返回一个新的vector<int>,其中包含满足条件的两个数的索引+1(按照题目要求,索引从1开始计数)。

时间复杂度分析

这个算法的时间复杂度为O(n),其中n是数组numbers的长度。因为每个元素最多被访问一次,且左右指针最多移动n次。

空间复杂度分析

空间复杂度为O(1),因为使用的额外空间不随输入数据的大小而改变,仅使用了固定大小的空间来存储指针和中间变量。

88. 合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1 nums2,另有两个整数 mn ,分别表示 nums1nums2 中的元素数目。

请你 合并 nums2 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n

示例 1:

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3 输出:[1,2,2,3,5,6] 解释:需要合并 [1,2,3] 和 [2,5,6] 。 合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。

示例 2:

输入:nums1 = [1], m = 1, nums2 = [], n = 0 输出:[1] 解释:需要合并 [1] 和 [] 。 合并结果是 [1] 。

示例 3:

输入:nums1 = [0], m = 0, nums2 = [1], n = 1 输出:[1] 解释:需要合并的数组是 [] 和 [1] 。 合并结果是 [1] 。 注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。

提示:

  • nums1.length == m + n

  • nums2.length == n

  • 0 <= m, n <= 200

  • 1 <= m + n <= 200

  • -10(9) <= nums1[i], nums2[j] <= 10(9)

进阶:你可以设计实现一个时间复杂度为 O(m + n) 的算法解决此问题吗?

 
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
    int pos = m-- + n-- - 1;
    while (m >= 0 && n >= 0) {
        nums1[pos--] = nums1[m] > nums2[n] ? nums1[m--] : nums2[n--];
    }
    while (n >= 0) {
        nums1[pos--] = nums2[n--];
    }
 }

这段代码实现的功能是合并两个已排序的数组nums1nums2nums1的大小足以包含nums1nums2合并后的所有元素,其中mn分别是nums1nums2中初始化元素的数量。

int pos = m-- + n-- - 1;这行代码初始化了一个变量pos,它表示合并后数组的最后一个元素的位置。由于数组的索引是从0开始的,所以需要m + n - 1来计算pos的位置。接着,mn分别减1,为接下来的操作做准备(因为在C++中,数组的索引是从0开始的,而mn是元素的数量)。此时mn分别指向原nums1最后一个元素和nums2最后一个元素。

while (m >= 0 && n >= 0) {这个while循环继续执行,直到nums1nums2中的元素被完全遍历。条件m >= 0 && n >= 0确保了不会访问任何数组的负索引。

nums1[pos--] = nums1[m] > nums2[n] ? nums1[m--] : nums2[n--];在循环体内,通过比较nums1[m]nums2[n]的大小,决定nums1[pos]的值。这里使用了三元运算符:如果nums1[m]大于nums2[n],则将nums1[m]赋值给nums1[pos],否则将nums2[n]赋值给nums1[pos]。无论哪种情况,pos都会递减,指向下一个待填充的位置。同时,被选中的那个数组的索引也会递减,以便于下一次比较。

while (n >= 0) {这个while循环是为了处理nums2中剩余的元素。如果nums1中的所有元素都已经被正确放置,但nums2中还有剩余的元素,这个循环会将它们复制到nums1的相应位置。

nums1[pos--] = nums2[n--];在循环体内,将nums2中剩余的元素依次复制到nums1的正确位置上,同时更新posn的值。

没有包含处理nums1剩余元素的循环,因为nums1的剩余元素已经在合适的位置上,不需要移动。

时间复杂度分析

这个算法的时间复杂度为O(m + n),其中m是nums1中初始化元素的数量,n是nums2中元素的数量。每个元素只被访问和比较一次。

空间复杂度分析

空间复杂度为O(1),因为合并是就地进行的,没有使用额外的存储空间。

142. 环形链表 II

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos-1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

示例 1:

输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

输入:head = [1,2], pos = 0 输出:返回索引为 0 的链表节点 解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

输入:head = [1], pos = -1 输出:返回 null 解释:链表中没有环。

提示:

  • 链表中节点的数目范围在范围 [0, 10(4)]

  • -10(5) <= Node.val <= 10(5)

  • pos 的值为 -1 或者链表中的一个有效索引

进阶:你是否可以使用 O(1) 空间解决此题?

 
ListNode *detectCycle(ListNode *head) {
    ListNode *slow = head, *fast = head;
// 判断是否存在环路
    do {
        if (!fast || !fast->next) return nullptr;
        fast = fast->next->next;
        slow = slow->next;
    } while (fast != slow);
// 如果存在,查找环路节点
    fast = head;
    while (fast != slow) {
        slow = slow->next;
        fast = fast->next;
    }
    return fast;
 }

这段代码是用于检测链表中是否存在环,如果存在,返回环的入口节点。这个算法使用了快慢指针的技巧,分为两个主要部分:检测环的存在和找到环的入口。

ListNode *slow = head, *fast = head;首先,声明两个指针slowfast,都初始化为链表的头节点headslow每次向前移动一个节点,而fast每次向前移动两个节点。

do { if (!fast || !fast->next) return nullptr;do-while循环中,首先检查fast指针或者fast->next是否为nullptr。如果是,说明链表没有环,因为在链表有环的情况下,fast指针不会遇到nullptr

fast = fast->next->next; slow = slow->next; } while (fast != slow);如果链表中没有出现nullptr,则fast指针每次移动两步,slow指针每次移动一步。这个过程一直持续到fast指针和slow指针相遇,相遇说明链表中存在环。

fast = head;当检测到链表中有环后,将fast指针重新指向链表头节点head

while (fast != slow) { slow = slow->next; fast = fast->next; }然后,让fast指针和slow指针都每次向前移动一个节点,当它们再次相遇时,相遇点就是环的入口节点。

时间复杂度分析

第一部分(检测环的存在)的时间复杂度为O(N),其中N是链表中节点的数量。在最坏的情况下,快慢指针会在环的入口相遇,此时快指针已经遍历了整个链表。

第二部分(找到环的入口)的时间复杂度也为O(N),但实际上这部分的运行次数不会超过链表的长度,因此在实际中这部分的时间消耗通常小于第一部分。

因此,总体时间复杂度为O(N)。

空间复杂度分析

空间复杂度为O(1),因为这个算法只使用了固定的几个指针,没有使用额外的存储空间。

76. 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。

  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC" 输出:"BANC" 解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。

示例 2:

输入:s = "a", t = "a" 输出:"a" 解释:整个字符串 s 是最小覆盖子串。

示例 3:

输入: s = "a", t = "aa" 输出: "" 解释: t 中两个字符 'a' 均应包含在 s 的子串中, 因此没有符合条件的子字符串,返回空字符串。

提示:

  • (m == s.length)

  • (n == t.length)

  • 1 <= m, n <= 10(5)

  • st 由英文字母组成

进阶:你能设计一个在 o(m+n) 时间内解决此问题的算法吗?

 
string minWindow(string S, string T) {
    vector<int> chars(128, 0);
    vector<bool> flag(128, false);
// 先统计T中的字符情况
    for (int i = 0; i < T.size(); ++i) {
        flag[T[i]] = true;
        ++chars[T[i]];
    }
// 移动滑动窗口,不断更改统计数据
    int cnt = 0, l = 0, min_l = 0, min_size = S.size() + 1;
    for (int r = 0; r < S.size(); ++r) {
        if (flag[S[r]]) {
            if (--chars[S[r]] >= 0) {
                ++cnt;
            }
// 若目前滑动窗口已包含T中全部字符,
 // 则尝试将l右移,在不影响结果的情况下获得最短子字符串
            while (cnt == T.size()) {
                if (r - l + 1 < min_size) {
                    min_l = l;
                    min_size = r - l + 1;
                }
                if (flag[S[l]] && ++chars[S[l]] > 0) {
                    --cnt;
                }
                ++l;
            }
        }
    }
    return min_size > S.size() ? "" : S.substr(min_l, min_size);
 }

vector<int> chars(128, 0); vector<bool> flag(128, false);这两行代码初始化了两个大小为128的数组charsflagchars用于存储字符串T中各字符出现的次数,而flag标记字符串T中是否包含某个字符(ASCII码范围为0-127)。

for (int i = 0; i < T.size(); ++i) { flag[T[i]] = true; ++chars[T[i]]; }这个循环遍历字符串T,更新T中字符的出现频率,同时标记这些字符在flag数组中。

int cnt = 0, l = 0, min_l = 0, min_size = S.size() + 1;声明和初始化四个变量:cnt用于记录当前窗口中已匹配的T中字符数量,l是窗口的左边界,min_l记录最小窗口的起始位置,min_size记录最小窗口的长度(初始值设为S.size() + 1,保证一开始任何窗口长度都小于min_size)。

for (int r = 0; r < S.size(); ++r) {这个循环通过右指针r遍历字符串S,用来扩展窗口的右边界。

if (flag[S[r]]) {如果当前字符S[r]T中出现过(通过flag检查),则处理字符。

if (--chars[S[r]] >= 0) { ++cnt; }字符S[r]对应的chars数组减1,如果减1后仍大于等于0,意味着当前字符是T中需要的字符之一,因此cnt加1。

while (cnt == T.size()) {cnt等于T的长度时,意味着当前窗口已包含T中所有字符。

if (r - l + 1 < min_size) { min_l = l; min_size = r - l + 1; }如果当前窗口的长度小于已知的最小长度min_size,则更新最小窗口的起始位置min_l和长度min_size

if (flag[S[l]] && ++chars[S[l]] > 0) { --cnt; } ++l;尝试缩小窗口的左边界,即右移左指针l。如果移除的字符是T中的字符(flag[S[l]]true),且chars[S[l]]在增加1后大于0,意味着移除了一个必需的字符,因此cnt减1。

return min_size > S.size() ? "" : S.substr(min_l, min_size);如果min_size大于S的长度,说明没有找到符合条件的窗口,返回空字符串;否则,返回最小窗口子串。

时间复杂度分析

时间复杂度为O(N),其中N是字符串S的长度。虽然有一个嵌套的while循环,但每个字符在S中最多被访问两次(一次由右指针扩展窗口,一次由左指针缩小窗口),因此总的时间复杂度仍为O(N)。

空间复杂度分析

空间复杂度为O(1),因为charsflag数组的大小固定为128,与输入字符串的大小无关。

结尾

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

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

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

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

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

相关文章

Java项目源码基于springboot的家政服务平台的设计与实现

大家好我是程序员阿存&#xff0c;在java圈的辛苦码农。辛辛苦苦板砖&#xff0c;今天要和大家聊的是一款Java项目源码基于springboot的家政服务平台的设计与实现&#xff0c;项目源码以及部署相关请联系存哥&#xff0c;文末附上联系信息 。 项目源码&#xff1a;Java基于spr…

[计算机效率] 便笺的使用

2.4 便笺 便笺程序是一种方便用户记录、查看和编辑便签的简单应用程序。在Windows系统中&#xff0c;便笺通常作为系统自带的实用工具之一&#xff0c;可以帮助用户快速创建、编辑和组织便签&#xff0c;以便随时记录重要的信息、任务或提醒事项。 便笺程序通常具有以下特点&a…

岩土工程监测中振弦采集仪的选型指南与市场概况

岩土工程监测中振弦采集仪的选型指南与市场概况 振弦采集仪是岩土工程监测中常用的一种设备&#xff0c;用于测量土体的振动特性。它的选型指南和市场概况如下&#xff1a; 选型指南&#xff1a; 1. 测量参数&#xff1a;振弦采集仪可用于测量土体的振动振幅、频率、相位等参数…

美团2025春招第一次笔试题

第四题 题目描述 塔子哥拿到了一个大小为的数组&#xff0c;她希望删除一个区间后&#xff0c;使得剩余所有元素的乘积未尾至少有k个0。塔子哥想知道&#xff0c;一共有多少种不同的删除方案? 输入描述 第一行输入两个正整数 n,k 第二行输入n个正整数 a_i&#xff0c;代表…

OpenHarmony开发—购物示例应用

介绍 本示例展示在进场时加载进场动画&#xff0c;整体使用Tabs容器设计应用框架&#xff0c;通过TabContent组件设置分页面&#xff0c;在子页面中绘制界面。通过Navigation完成页面之间的切换。在详情页中通过 Video组件加载视频资源&#xff0c;使用CustomDialogController…

力扣刷题日记——L66.加一

1. 前言&#xff1a; 从今天开始打卡力扣&#xff0c;每天一道力扣题&#xff0c;然后将解题思路分享出来&#xff0c;纯原创。 2. 题目描述 给定一个由 整数 ****组成的 ****非空 数组所表示的非负整数&#xff0c;在该数的基础上加一。 最高位数字存放在数组的首位&#…

使用API有效率地管理Dynadot域名,使用API设置域名隐私保护

关于Dynadot Dynadot是通过ICANN认证的域名注册商&#xff0c;自2002年成立以来&#xff0c;服务于全球108个国家和地区的客户&#xff0c;为数以万计的客户提供简洁&#xff0c;优惠&#xff0c;安全的域名注册以及管理服务。 Dynadot平台操作教程索引&#xff08;包括域名邮…

SiT技术报告阅读

论文链接&#xff1a;SiT: Exploring Flow and Diffusion-based Generative Models with Scalable Interpolant Transformers 报告链接&#xff1a;https://scalable-interpolant.github.io/ 文章目录 IntroFlow and DiffusionDiffusion-Based ModelsStochastic Interpolant an…

GPT出现Too many requests in 1 hour. Try again later.

换节点 这个就不用多说了&#xff0c;你都可以上GPT帐号了&#xff0c;哈…… 清除cooki 然后退出账号&#xff0c;重新登录即可

应用工程中获取Shapefile文件的图形信息并显示

本文用纯前端获取shp文件以及前后端交互的方式获取Shapefile文件中的图形信息 1.案例说明 在日常的WebGIS开发中&#xff0c;我们往往会面对&#xff0c;需要用户选择矢量数据&#xff0c;通过矢量数据中的空间范围信息&#xff0c;显示在界面上&#xff0c;并给用户的下一步…

wave库基本操作

wave 常见的语音信号处理python库有librosa, scipy, soundfile等等。wave库是python的标准库&#xff0c;对于python来说相对底层&#xff0c;wave不支持压缩/解压&#xff0c;但支持单声道/立体声语音的读取。 读取音频 import wavefile_path D:/ba.wav #文件路径 f wave…

数据库应用

约束 概念&#xff1a; 约束是一种限制&#xff0c;它通过对表的行或列的数据做出限制&#xff0c;来确保表的数据的正确性、完整性、有效性、唯一性。 分类&#xff1a; primary key&#xff1a;主键约束&#xff0c;指定某列的数据不能重复、唯一、非空。 not null&#…

QT----计算器

目录 1 搭建标准界面2、 逻辑编写2.1 初始化 github链接&#xff1a;基于qt的计算器 1 搭建标准界面 按照下图搭设界面 修改样式让这计算器看起来更像一点&#xff0c;同时对按钮分组进行样式编辑&#xff0c;添加字符串name,为number&#xff0c;其他按键为other。之前的文章…

Linux操作系统-07-Linux安装应用

一、使用rpm安装应用&#xff08;不推荐&#xff09; 先下载到本地&#xff0c;以.rpm文件名结尾&#xff0c;下载完成后&#xff0c;再安装 rpm -qa | grep mysql #查询当前系统是否有下载过mysql包 先上传mysql的rpm安装包到linux的opt目录 安装 rpm -ivh …

CVE-2024-27199 JetBrains TeamCity 身份验证绕过漏洞2

漏洞简介 TeamCity Web 服务器中发现了第二个身份验证绕过漏洞。这种身份验证旁路允许在没有身份验证的情况下访问有限数量的经过身份验证的端点。未经身份验证的攻击者可以利用此漏洞修改服务器上有限数量的系统设置&#xff0c;并泄露服务器上有限数量的敏感信息。 项目官网…

3D模型优化10个最佳实践

对于许多在建模、渲染和动画方面经验丰富的 3D 建模者来说&#xff0c;3D 优化可能是一个令人畏惧的过程 - 特别是当你正在优化实时应用程序的 3D 模型时&#xff01; 在 Google 上快速搜索“如何优化 3D 文件”将会出现一些建议&#xff0c;例如减少多边形数和消除多余的顶点。…

【MATLAB 】 EMD信号分解+FFT傅里叶频谱变换组合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~ 展示出图效果 1 EMD信号分解算法 EMD 分解又叫经验模态分解&#xff0c;英文全称为 Empirical Mode Decomposition。 EMD 是一种信号分解方法&#xff0c;它将一个信号分解成有限个本质模态函数 (EMD) 的和&#xff0c…

马斯克宣布本周开源AI助手Grok;Gemini 1.5:多模态理解

&#x1f989; AI新闻 &#x1f680; 马斯克宣布本周开源AI助手Grok 摘要&#xff1a;马斯克通过X平台宣布&#xff0c;其人工智能公司xAI计划本周开源人工智能助手Grok。此前&#xff0c;马斯克因OpenAI及其CEO阿尔特曼违反了公司成立协议—推动AI技术为人类福祉而非利润而起…

Linux 多进程开发(上)

第二章 Linux 多进程开发 2.1 进程概述2.2 进程状态转换2.3 进程创建2.4 exec 函数族2.5 进程控制 网络编程系列文章&#xff1a; 第1章 Linux系统编程入门&#xff08;上&#xff09; 第1章 Linux系统编程入门&#xff08;下&#xff09; 第2章 Linux多进程开发&#xff08;…

PCL 约束Delaunay三角网(版本二)

目录 一、算法概述二、代码实现三、结果展示四、测试数据本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。 一、算法概述 PCL 点云Delaunay三角剖分一文给出了PCL中Delaunay三角网算法的基础用法。本文在基础用法的基…