基础算法(1)——双指针

1. 概念

常见的双指针有两种形式,一种是对撞指针,一种是快慢指针

1.1 对撞指针

一般用于顺序结构中,也称为左右指针

对撞指针从两端向中间移动,一个指针从最左端开始,另一个从最右端开始,逐渐往中间逼近

对撞指针的终止条件一般是两个指针相遇或错开(也可能在循环内部找到结果直接跳出循环)

1.2 快慢指针

又称为龟兔赛跑算法,其基本思想就是使用两个移动速度不同的指针在数组或链表等序列结构上移动,这种方法对于处理环形链表或数组很有用

快慢指针的实现方式有很多种,最常用的一种就是:在一次循环中,每次让慢的指针向后移动一位,快的指针向后移动两位,实现一快一慢

2. 移动零

属于 “数组划分、数组分块” 类型的题,该题是数组分为两块,根据一种划分方式,将数组的内容分成左右两部分,这种类型的题一般就是使用 “双指针” 完成

题目描述:

难点:同时保持非零元素的相对位置

解法思路:

利用数组下标来充当指针,两个指针分别的作用:

cur :从左向右扫描数组,遍历数组;dest:已处理的区间内,非零元素的最后一个位置。如下图:

代码思路:

cur 从前往后遍历的过程中

1. 遇到 0 元素:cur++

2. 遇到非零元素:swap(dest + 1, cur); dest++,cur++;

代码实现:

class Solution {public void moveZeroes(int[] nums) {int dest = 0;int cur = 0;int n = nums.length;while (cur < nums.length) {if (nums[cur] != 0) {int tmp = nums[cur];nums[cur] = nums[dest];nums[dest] = tmp;dest++;}cur++;}}
}

tip:该方法思路也是快排里面最核心的一步,如下快排示意图:

3. 复写零

题目描述:

难点:对输入数组就地进行上述修改

解法思路:

如果 从前向后 进行原地复写操作的话,由于 0 的出现会复写两次,导致没有复写的数被覆盖掉,因此应选择 从后往前 的复写策略

先根据 “异地” 操作,然后优化成双指针下的 “就地” 操作:

从而得到 “就地” 处理的思路:

1) 先找到最后一个 复写 的元素下标(双指针法)

cur 和 dest 从 0 下标开始遍历数组

        a) 判断 cur 位置的值

        b) 若为 0,则 dest 后移 2 位,反之后移 1 位

        c) 判断 dest 是否 >= 数组长度,若有,则跳出循环

         d) 若没有,cur++

2) 处理边界情况,这里就分为了两种情况

        a) 如例 [1,0,2,3,0,4,5,0] 这个数组,最后一位复写的数字不为 0,此时第一步结束之后,dest 等于数组长度,只需处理 dest 等于数组长度 - 1 即可

        b) 如例 [1,0,2,3,0,4] 这个数组,最后一位复写的数字为 0,此时第一步结束之后,dest 大于数组长度,这时若在让 dest 等于数组长度 - 1的话,就会导致复写第二次 0 的时候发生数组越界异常,因此这种最后一位复写数字为 0 的情况,直接处理为:

        arr[n - 1] = 0;

        dest = n - 2;

        cur--;

3) 从后向前完成复写操作

代码实现:

class Solution {public void duplicateZeros(int[] arr) {int cur = 0;int dest = 0;int n = arr.length;//1.找到最后一个复写的元素下标while (cur < n) {if (arr[cur] == 0) {dest += 2;} else {dest++;}if (dest >= n) {break;}cur++;}//2.处理边界情况(当第 1 步走完后,dest 肯定 >= nif (dest == n) {dest--;} else { //特殊情况:当数组为[1,0,2,3,0,4]这样,最后一位需要复写的数字为 0 时arr[n - 1] = 0;dest = n - 2;cur--;}//3.从后向前复写while (cur >= 0) {if (arr[cur] == 0) {arr[dest--] = 0;arr[dest--] = 0;} else {arr[dest--] = arr[cur];}cur--;}}
}

4. 快乐数

题目描述:

题目解析:

为方便叙述,将 【对于一个正整数,每次将该数替换为它每个位置上的数字的平方和】 这个操作记为 x 操作

题目中说,当我们不断重复 x 操作时,计算一定会【死循环】,死循环的方式有两种:

情况一:一直在 1 中死循环,如下图:

情况二:在历史的数据中【死循环】,但始终变不到 1,如下图:

由于上述两种情况只会出现一种,因此我们只需确定循环是在【情况一】还是【情况二】中进行就能解决该题

扩展:为什么不会出现【不是死循环,而是一直计算下去】的情况

【鸽巢原理】(也称为抽屉原理):现有 n 个巢,n + 1 个鸽子,得出结论【至少有一个巢里面的鸽子数大于 1】

在该题中,根据题目给出提示,n 的取值范围为 [12^{31}-1](也就是最大能取到 2147483647),现在我们假设 n 能取到 9999999999,该数字进行 x 操作 = 9^2 *10 = 820,也就是说变化的区间在 [1,810] 之间

根据【鸽巢原理】,取极限情况,一个数在进行 x 操作 810 次后都没有进入循环,但是一旦到 811 次之后,必然会形成一个循环

因此,变化的过程最终肯定会走到一个圈里面,所以可以用【快慢指针】来解决该问题

解法思路:

根据题目分析可知,当重复执行 x 操作时,数据最终会死循环,而快慢指针有一个特性,就是在一个圆圈中,快指针总是会追上慢指针的,也就是说他们总会相遇到一个位置上,此时就可以判断,若相遇位置为 1,则 n 为快乐数;若相遇位置不为 1,则 n 不是快乐数

代码实现:

class Solution {//返回 n 这个数每一位的平方和public int bitSum(int n) {int sum = 0;while (n != 0) {int val = n % 10;sum += val * val;n /= 10;}return sum;}public boolean isHappy(int n) {int fast = bitSum(n);int slow = n;while (fast != slow) {fast = bitSum(bitSum(fast));slow = bitSum(slow);}return fast == 1;}
}

5. 盛水最多的容器

题目描述:

解法一:暴力求解(会超时)

算法思路:枚举出能构成的所有容器,找出其中最大值

两个 for 循环,时间复杂度 O(n^2)

代码实现:

class Solution {public int maxArea(int[] height) {int area = 0;for (int i = 0; i < height.length; i++) {for (int j = i + 1; j < height.length; j++) {area = Math.max(area, (j - i) * Math.min(height[i], height[j]));}}return area;}
}

解法二:对撞指针

算法思路:

先找到单调性的规律:

重复上述过程,每次都可以舍去大量不必要的枚举过程,直到 left 和 right 相遇

代码实现:

//方法二:利用单调性的规律,通过双指针解决问题
class Solution {public int maxArea(int[] height) {int area = 0;int left = 0;int right = height.length - 1;while (left < right) {area = Math.max(area, (right - left) * Math.min(height[left], height[right]));if (height[left] < height[right]) {left++;} else {right--;}}return area;}
}

6. 有效三角形的个数

题目描述:

解法一:暴力求解

算法思路:

三层 for 循环枚举出所有的三元组,并判断是否能构成三角形

若是直接进行判断,利用 a+b>c, a+c>b, b+c>a 这三个条件进行判断的话,会超时(时间复杂度3N^3

所以进行优化:将数组从小到大排序,这样只需比较 较小的两个数相加是否大于较大的数 即可(时间复杂度N\log N + N^3,远小于3N^3)

代码实现:

class Solution {public int triangleNumber(int[] nums) {Arrays.sort(nums);int n = nums.length;int sum = 0;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]) {sum ++;}}}}return sum;}
}

解法二:排序 + 双指针

算法思路

代码实现:

class Solution {public int triangleNumber(int[] nums) {//1.将数组排序Arrays.sort(nums);//2.根据单调性,利用双指针解决问题int n = nums.length - 1; //通过 n 来固定最大值int sum = 0;while (n >= 2) {//通过双指针快速统计处符合要求的三元组个数int left = 0;int right = n - 1;while (left < right) {if (nums[left] + nums[right] > nums[n]) {sum += right - left;right--;} else {left++;}}n--;}return sum;}
}

7. 查找总价格为目标值的两个商品

题目描述:

解法一:暴力解法(会超时)

算法思路:两层 for 循环列出所有两个数字的组合,判断是否等于目标值

注:在第二层 for 循环时,挑选数字不从第一个数开始选,因为前面的数字已经判断过了,直接从第一层循环挑选数字的下一个位置开始

代码实现:

class Solution {public int[] twoSum(int[] price, int target) {int n = price.length;for (int i = 0; i < n; i++) { //第一层循环从前往后列举第一个数for (int j = i + 1; j < n; j ++) { //第二层循环从 i 位置之后列举第二个数if (price[i] + price[j] == target) {return new int[] {price[i], price[j]};}}}return null;}
}

解法二:对撞指针

算法思路:

本题为升序的数组,因此可以用 对撞指针 优化时间负责度

具体实现流程,相当于上一个算法题的简化,不展开

代码实现:

class Solution {public int[] twoSum(int[] price, int target) {int[] arr = new int[2];int left = 0;int right = price.length - 1;while (left < right) {if (price[left] + price[right] < target) {left++;} else if (price[left] + price[right] > target) {right--;} else {arr[0] = price[left];arr[1] = price[right];return arr;}}return null;}
}

8. 三数之和

题目描述:

解法一:排序 + 暴力枚举 + 利用 HashSet 去重(时间复杂度 O(N^3)

三层 for 循环

解法二:排序 + 双指针

本题和两数之和类似,稍微不同的是题目要求找到所有【不重复】的三元组,具体步骤如下:

1) 排序

2) 固定一个数 a(此处固定最大值或最小值都可以,只是遍历方式不同)

3) 若是固定最小值,则在此数后面的区间内使用【双指针算法】快速找到两个数之和等于 -a 即可;若是固定最大值,则在此数前面的区间进行操作

本题需要注意的是:需要有去重操作

a) 找到一个结果后,left 和 right 指针要【跳过重复】的元素

b) 当使用完一次双指针算法之后,固定的 a 也要【跳过重复】的元素

代码实现:

1. 固定最小值:

class Solution {public List<List<Integer>> threeSum(int[] nums) {//1.排序Arrays.sort(nums);List<List<Integer>> ret = new ArrayList<>();int n = nums.length - 1;//2.固定值 afor (int i = 0; i < n;) { //注:此处为了去重,将 i++ 操作放到 for 循环尾部int left = i + 1;int right = n;while (left < right) {if (nums[left] + nums[right] < -nums[i]) {left++;} else if (nums[left] + nums[right] > -nums[i]) {right--;} else {ret.add(new ArrayList<Integer>(Arrays.asList(nums[i], nums[left], nums[right])));left++;right--;//去重while (left < right && nums[left] == nums[left - 1]) {left++;}while (left < right && nums[right] == nums[right + 1]) {right--;}}}i++;//去重while (i < n && nums[i] == nums[i - 1]) {i++;}}return ret;}
}

2. 固定最大值:

class Solution {public List<List<Integer>> threeSum(int[] nums) {//1.排序Arrays.sort(nums);//2.固定最大值(数组最后一位)int n = nums.length - 1;if (n < 0) { //若数组中最大值都小于 0 直接返回 nullreturn null;}List<List<Integer>> ret = new ArrayList<>();while (n > 0) {int left = 0;int right = n - 1;//3.遍历数组剩余元素,将符合条件的添加到 retwhile (left < right) {if (nums[left] + nums[right] + nums[n] > 0) {right--;} else if (nums[left] + nums[right] + nums[n] < 0) {left++;} else {//将相加等于 0 的三个数创建为一个 ArrayList,并添加到 ret 中ret.add(new ArrayList<Integer>(Arrays.asList(nums[left], nums[right], nums[n])));left++;right--;//去重操作while (left < right && nums[right] == nums[right + 1]) {right--;}while (left < right && nums[left] == nums[left-1]) {left++;}}}n--;//去重操作while (n >= 2 && nums[n] == nums[n + 1]) {n--;}}return ret;}
}

9. 四数之和

题目描述:

解法一:排序 + 暴力枚举(四个 for 循环) + 利用HashSet 去重(会超时)

解法二:排序 + 双指针

代码实现:

class Solution {public List<List<Integer>> fourSum(int[] nums, int target) {List<List<Integer>> ret = new ArrayList<>();//1.排序Arrays.sort(nums);//2.固定两个数int n = nums.length;int a = 0;while (a < n) {int b = a + 1;while (b < n) {int left = b + 1;int right = n - 1;long aim = (long)target - nums[a] - nums[b];while (left < right) {int sum = nums[left] + nums[right];if (sum < aim) {left++;} else if (sum > aim) {right--;} else {ret.add(Arrays.asList(nums[left], nums[right], nums[b], nums[a]));left++;right--;while (left < right && nums[left] == nums[left - 1]) {left++;}while (left < right && nums[right] == nums[right + 1]) {right--;}}}b++;while (b < n && nums[b] == nums[b - 1]) {b++;}}a++;while (a < n && nums[a] == nums[a - 1]) {a++;}}return ret;}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.net dataexcel winform控件 更新 日志

增加 列宽度调整时动态显示列象素大小 更改列的宽度可以使用 column.Width属性进行修改

【持续更新】Mχ Plaayer Pro 1.86.0安卓知名播放器最新免费高级修改版

Mχ Plaayer Pro MOD 版本免费 APK&#xff0c;专为安卓手机和平板打造。这是一款功能强大的视频播放器&#xff0c;具备先进的硬件加速技术和字幕支持功能。 • 硬件加速 - 新增 HW 解码器帮助更多视频格式实现硬件加速。 • 多核心解码 - Mχ Plaayer 是首款支持多核心解码的…

基于STM32的RFID高速收费系统(论文+源码+实物)

1系统方案设计 本文基于STM32的RFID高速收费系统&#xff0c;其可以实现小车和货车两种车型收费&#xff0c;当车辆超过了规定的重量后&#xff0c;出现声光报警提示&#xff0c;并且启动杆不会抬起&#xff0c;只有当车辆重量低于设置值时&#xff0c;启动杆才会自动抬起&…

【Linux】在 bash shell 环境下,当一命令正在执行时,按下 control-Z 会?

目录 题目分析答案 题目 分析 ctrl-c&#xff1a; 发送 SIGINT 信号给前台进程组中的所有进程。常用于终止正在运行的程序&#xff1b;ctrl-z&#xff1a; 发送 SIGTSTP信号给前台进程组中的所有进程&#xff0c;常用于挂起一个进程&#xff1b;ctrl-d&#xff1a; 不是发送信…

揭秘排行榜系统:如何在高并发场景下实现高效更新!

大家好,我是你们的技术分享伙伴小米!今天我们来聊聊一个非常有趣的话题——如何设计一个排行榜。在这个互联网时代,无论是游戏、学习平台,还是各种社交应用,排行榜都是用户互动和竞争的核心功能之一。而如何设计一个高效、实时更新的排行榜,是一个充满挑战性的问题。今天…

win11,vscode上用docker环境跑项目

1.首先用dockerfile创建docker镜像 以下是dockerfile文件的内容&#xff1a; FROM pytorch/pytorch:1.11.0-cuda11.3-cudnn8-devel LABEL Service"SparseInstanceActivation"ENV TZEurope/Moscow ENV DETECTRON_TAGv0.6 ARG DEBIAN_FRONTENDnoninteractiveRUN apt-…

vim常用快捷键问答

vim的光标位置操作快捷键有哪些&#xff1f;怎样记忆它们&#xff1f; 在 Vim 中&#xff0c;光标位置的操作快捷键非常重要&#xff0c;可以帮助你更高效地编辑文本。下面是一些常用的光标位置操作快捷键&#xff1a; 基本移动 h&#xff1a;光标左移一个字符j&#xff1a;光…

使用安信可Ai-WB2-12F开启wifi与手机通信TCP-IP(AT指令)

当时在做两个单片机之间无线通信&#xff0c;或者单片机与手机无线通信&#xff0c;就像找一个蓝牙和wifi双模的无线模块&#xff0c;一开始看ESP8684&#xff08;ESP32-C2&#xff09;这个芯片模组是有wifi和蓝牙的&#xff0c;买回来后才发现他不可以在程序运行中更换蓝牙或者…

主流AI绘画工具-StableDiffusion本地部署方法(mac电脑版本)

Stable Diffusion是一款强大的AI生成图像模型&#xff0c;它可以基于文本描述生成高质量的图像。对于想要在本地运行此模型的用户来说&#xff0c;使用Mac电脑部署Stable Diffusion是一个非常吸引人的选择&#xff0c;特别是对于M1或M2芯片的用户。本文将详细介绍如何在Mac上本…

视频化时代,用好AIGC产品赋能企业培训打造增效降本“最佳实践”

根据IBM的数据&#xff0c;85%的中国企业正在加速投资AI领域&#xff0c;其中超过63%的企业已积极采用生成式AI。德勤的调研进一步显示&#xff0c;近80%的全球受访企业高管认为&#xff0c;生成式AI的兴起与发展将在3年内推动组织和行业发生实质性变革&#xff0c;这也就意味着…

探秘DevSecOps黄金管道,安全与效率的完美融合

软件应用的安全性已成为企业和用户关注的焦点&#xff0c;DevSecOps作为一种将安全融入开发和运维全过程的理念和实践&#xff0c;旨在消除传统开发模式中安全被后置处理的弊端。DevSecOps黄金管道&#xff08;Golden Pipeline&#xff09;是实现这一理念的核心框架&#xff0c…

C++领进门(第三讲)

目录 7.内联函数 7.1 概念 7.2 特征 8. auto关键字(C11) 8.1 auto简介 8.2 auto的使用细则 8.3 auto不能推导的场景 9. 基于范围的for循环(语法糖)(C11) 9.1 范围for的语法 9.2 范围for的使用条件 10. 指针空值nullptr(C11) 7.内联函数 7.1 概念 以inline修饰的函数…

ctfshow之web55~web57(无字母的rce)

目录 web55 思路一&#xff1a; 思路二&#xff1a; web56 web57 本系列主要针对无字母rce或无字母无数字rce 声明&#xff1a;本章内容是引荐几位师傅的博客&#xff0c;然后根据自己的理解编写而成。 web55 if(isset($_GET[c])){$c$_GET[c];if(!preg_match("/\…

模糊视频一键变清晰,从此告别模糊不清的画质

话不多说&#xff0c;咱们直入主题。你是不是有比较模糊的视频&#xff0c;比如老视频&#xff0c;老电影和监控视频&#xff0c;对了&#xff0c;还有日本土特产&#xff08;懂的都懂&#xff09;&#xff0c;模糊的视频看起是不是很不舒服&#xff0c;长期久了还会影响视力影…

弹窗相关操作

弹窗使用 文章目录 弹窗使用弹窗-新增表单修改弹窗 弹窗-新增表单 拖拽弹出层组件&#xff0c;补充表单信息 2.点击表单&#xff0c;绑定数据库模型&#xff0c;绑定字段 3.新增弹窗按钮绑定打开或关闭弹出层事件 4.弹窗保存按钮依次绑定 保存表单&#xff0c;打开或关闭弹…

Docker笔记-Docker Hello World

Docker笔记-Docker Hello World 1、输出Hello World Docker 允许你在容器内运行应用程序&#xff0c;使用 docker run 命令来在容器内运行一个应用程序&#xff1a; $ docker run ubuntu:15.10 /bin/echo "Hello world"各个参数解析&#xff1a; docker&#xff1…

使用极狐GitLab进行K3S集群的维护与控制

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门面向中国程序员和企业提供企业级一体化 DevOps 平台&#xff0c;用来帮助用户实现需求管理、源代码托管、CI/CD、安全合规&#xff0c;而且所有的操作都是在一个平台上进行&#xff0c;省事省心省钱。可以一键安装极狐GitL…

查看网址是否失效

检查指令 可能是IPve6无法使用问题 检查网址 Is it down? Check at Down for Everyone or Just Me 欧克挂掉了 补充&#xff1a; Downdetector &#xff08;下检测器&#xff09; 网站监控服务 — 可用性和性能 |平度 (pingdom.com)

[Algorithm][综合训练][合并k个已排序的链表][dd爱旋转][小红取数]详细讲解

目录 1.合并k个已排序的链表1.题目链接2.算法原理讲解 && 代码实现 2.dd爱旋转1.题目链接2.算法原理详解 && 代码详解 3.小红取数1.题目链接2.算法原理详解 && 代码实现 1.合并k个已排序的链表 1.题目链接 合并k个已排序的链表 2.算法原理讲解 &…

centos换源安装升级gcc

使用devtools升级安装的时候&#xff0c;由于此库已经停止更新 了&#xff0c;因此需要切换阿里源 SCLDevtoolset 安装与使用笔记-腾讯云开发者社区-腾讯云 (tencent.com)https://cloud.tencent.com/developer/article/1889181 1 yum 安装 yum install centos-release-scl c…