当前位置: 首页 > news >正文

双指针---(部分地更新)

双指针

在这里插入图片描述

复写零

给你一个长度固定的整数数组 arr ,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。

注意:请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上述修改,不要从函数返回任何东西。

示例 1:

输入:arr = [1,0,2,3,0,4,5,0]
输出:[1,0,0,2,3,0,0,4]
解释:调用函数后,输入的数组将被修改为:[1,0,0,2,3,0,0,4]

示例 2:

输入:arr = [1,2,3]
输出:[1,2,3]
解释:调用函数后,输入的数组将被修改为:[1,2,3]

提示:

  • 1 <= arr.length <= 104
  • 0 <= arr[i] <= 9

题目解析:

特别需要注意的是就地

算法原理:
在这里插入图片描述

代码如下:

class Solution 
{
public:void duplicateZeros(vector<int>& arr) {//1.先找到最后一个数int cur=0,dest=-1,n=arr.size();while(cur<n){if(arr[cur]) dest++;else dest+=2;if(dest>=n-1) break;cur++;}//2.处理边界情况if(dest==n){arr[n-1]=0;cur--;dest-=2;}//3.从后往前进行复写操作while(cur>=0){if(arr[cur]) arr[dest--]=arr[cur--];else{arr[dest--]=0;arr[dest--]=0;cur--;}}}
};

写这类题目的时候,不要在脑袋里面空想,要动手动笔去思考,像下面这样:

更详细点来说:
如果「从前向后」进⾏原地复写操作的话,由于 0 的出现会复写两次,导致没有复写的数「被覆 盖掉」。因此我们选择「从后往前」的复写策略。

但是「从后向前」复写的时候,我们需要找到「最后⼀个复写的数」,因此我们的⼤体流程分两 步:

i. 先找到最后⼀个复写的数;

ii. 然后从后向前进⾏复写操作。

算法流程:

a. 初始化两个指针 cur = 0 , dest = -1 ;

b. 找到最后⼀个复写的数:

i. 当 cur < n 的时候,⼀直执⾏下⾯循环:

• 判断 cur 位置的元素:

◦ 如果是 0 的话, dest 往后移动两位;

◦ 否则, dest 往后移动⼀位。

• 判断 dest 时候已经到结束位置,如果结束就终⽌循环;

• 如果没有结束, cur++ ,继续判断。

c. 判断 dest 是否越界到 n 的位置:

i. 如果越界,执⾏下⾯三步:

  1. n - 1 位置的值修改成 0 ;
  2. cur 向移动⼀步;
  3. dest 向前移动两步。

d. 从 cur 位置开始往前遍历原数组,依次还原出复写后的结果数组:

i. 判断 cur 位置的值:

  1. 如果是 0 : dest 以及 dest - 1 位置修改成 0 , dest -= 2 ;
  2. 如果⾮零: dest 位置修改成 0 , dest -= 1 ;

ii. cur-- ,复写下⼀个位置。

快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n快乐数 就返回 true ;不是,则返回 false

示例 1:

输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

示例 2:

输入:n = 2
输出:false

提示:

  • 1 <= n <= 231 - 1

题目解析:
在这里插入图片描述

这个题目让我们想到了带环链表

就可以考虑快慢指针来解决

算法原理:
在这里插入图片描述

这个题目不需要考虑是否遇不到1,或者是循环,我们可以想到鸽巢原理

在这里插入图片描述

联系到这个题目,我们可以知道如果哪个数回不到1,那么它一定会落到哪个环里

代码如下:

class Solution 
{int Add(int n){int sum=0;while(n){int t=n%10;sum+=t*t;n/=10;}return sum;}
public:bool isHappy(int n) {int slow =n,fast=Add(n);while(slow!=fast){slow=Add(slow);fast=Add(Add(fast));}return slow==1;}
};

更进一步来说:

题⽬分析:

为了⽅便叙述,将「对于⼀个正整数,每⼀次将该数替换为它每个位置上的数字的平⽅和」这⼀个 操作记为 x 操作;

题⽬告诉我们,当我们不断重复 x 操作的时候,计算⼀定会「死循环」,死的⽅式有两种:

▪ 情况⼀:⼀直在 1 中死循环,即 1 -> 1 -> 1 -> 1…

▪ 情况⼆:在历史的数据中死循环,但始终变不到 1

由于上述两种情况只会出现⼀种,因此,只要我们能确定循环是在「情况⼀」中进⾏,还是在「情 况⼆」中进⾏,就能得到结果。

简单证明:

a. 经过⼀次变化之后的最⼤值 9^2 * 10 = 810 ( 2^31-1=2147483647 。选⼀个更⼤的最 ⼤ 9999999999 ),也就是变化的区间在 [1, 810] 之间;

b. 根据「鸽巢原理」,⼀个数变化 811 次之后,必然会形成⼀个循环;

c. 因此,变化的过程最终会⾛到⼀个圈⾥⾯,因此可以⽤「快慢指针」来解决。

解法(快慢指针):

算法思路:

根据上述的题⽬分析,我们可以知道,当重复执⾏ x 的时候,数据会陷⼊到⼀个「循环」之中。

⽽「快慢指针」有⼀个特性,就是在⼀个圆圈中,快指针总是会追上慢指针的,也就是说他们总会 相遇在⼀个位置上。如果相遇位置的值是 1 ,那么这个数⼀定是快乐数;如果相遇位置不是 1 的话,那么就不是快乐数。

补充知识:如何求⼀个数 n 每个位置上的数字的平⽅和。

a. 把数 n 每⼀位的数提取出来:

循环迭代下⾯步骤:

i. int t = n % 10 提取个位;

ii. n /= 10 ⼲掉个位;

直到 n 的值变为 0 ;

b. 提取每⼀位的时候,⽤⼀个变量 sum 记录这⼀位的平⽅与之前提取位数的平⽅和

▪ sum = sum + t * t

盛水最多的容器

给定一个长度为 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 <= 105
  • 0 <= height[i] <= 104

算法原理
在这里插入图片描述

代码如下:

class Solution 
{
public:int maxArea(vector<int>& height) {int left=0,right=height.size()-1,ret=0;while(left<right){int v=min(height[left],height[right])*(right-left);ret=max(ret,v);//移动指针if(height[left]<height[right]) left++;else right--;}return ret;}
};

进一步来说:

解法⼀(暴⼒求解)(会超时):

算法思路:

枚举出能构成的所有容器,找出其中容积最⼤的值。

◦ 容器容积的计算⽅式:

设两指针 i , j ,分别指向⽔槽板的最左端以及最右端,此时容器的宽度为 j - i 。由于 容器的⾼度由两板中的短板决定,因此可得容积公式 : v = (j - i) * min( height[i], height[j])

算法代码:

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++) {// 计算容积,找出最⼤的那⼀个ret = max(ret, min(height[i], height[j]) * (j - i));}}
return ret;}
};

解法⼆(对撞指针):

算法思路:

设两个指针 left , right 分别指向容器的左右两个端点,此时容器的容积 :

v = (right - left) * min( height[right], height[left])

容器的左边界为 height[left] ,右边界为 height[right] 。

为了⽅便叙述,我们假设「左边边界」⼩于「右边边界」。

如果此时我们固定⼀个边界,改变另⼀个边界,⽔的容积会有如下变化形式:

◦ 容器的宽度⼀定变⼩。

◦ 由于左边界较⼩,决定了⽔的⾼度。如果改变左边界,新的⽔⾯⾼度不确定,但是⼀定不会超 过右边的柱⼦⾼度,因此容器的容积可能会增⼤。

◦ 如果改变右边界,⽆论右边界移动到哪⾥,新的⽔⾯的⾼度⼀定不会超过左边界,也就是不会 超过现在的⽔⾯⾼度,但是由于容器的宽度减⼩,因此容器的容积⼀定会变⼩的。

由此可⻅,左边界和其余边界的组合情况都可以舍去。所以我们可以 left++ 跳过这个边界,继 续去判断下⼀个左右边界。

当我们不断重复上述过程,每次都可以舍去⼤量不必要的枚举过程,直到 left 与 right 相 遇。期间产⽣的所有的容积⾥⾯的最⼤值,就是最终答案

有效三角形的个数

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

这道题也是同理,不能够用脑袋空想,需要动手去梳理逻辑关系

如下图:
在这里插入图片描述

题目解析

在这里插入图片描述

这里需要注意的是不同顺序但是相同的数字也被算作是一种

算法原理

在这里插入图片描述

代码如下:

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

进一步来说:
解法⼀(暴⼒求解)(会超时)

算法思路:

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

解法⼆(排序 + 双指针):

算法思路:

先将数组排序。

根据「解法⼀」中的优化思想,我们可以固定⼀个「最⻓边」,然后在⽐这条边⼩的有序数组中找 出⼀个⼆元组,使这个⼆元组之和⼤于这个最⻓边。由于数组是有序的,我们可以利⽤「对撞指 针」来优化。

设最⻓边枚举到 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++ 进⼊下轮循环

和为s的两个数字

题⽬描述

输⼊⼀个递增排序的数组和⼀个数字 s ,在数组中查找两个数,使得它们的和正好是 s 。如果有多 对数字的和等于 s ,则输出任意⼀对即可。

⽰例 1:

输⼊: nums = [2,7,11,15], target = 9

输出: [2,7] 或者 [7,2]
找 出⼀个⼆元组,使这个⼆元组之和⼤于这个最⻓边。由于数组是有序的,我们可以利⽤「对撞指 针」来优化。

设最⻓边枚举到 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++ 进⼊下轮循环

相关文章:

  • 基于JAVA springboot+mybatis智慧生活分享平台设计和实现
  • CS 工作笔记:SmartEdit 里创建的是 CMS Component
  • 调用飞书接口导入供应商bug
  • Vue3 工具函数(总结)
  • 大数据处理从零开始————8.基于Java构建WordCount项目
  • ①EtherCAT转Modbus485RTU网关多路同步高速采集无需编程串口服务器
  • [Linux]进程管理指令
  • 第十一届蓝桥杯嵌入式省赛程序设计题解析(基于HAL库)(大学组)
  • 第五章 linux实战-挖矿 二
  • Azure OpenAI检索增强微调:使用 GPT-4o 对 GPT-4o mini 进行微调,以适应特定领域的应用
  • 【回眸】Tessy 单元测试软件使用指南(四)常见报错及解决方案与批量初始化的经验
  • 分布式难题-三座大山NPC
  • 微信卖茶叶套路
  • 华为源NAT技术与目的NAT技术
  • debian linux 只安装mysql client
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • [译]CSS 居中(Center)方法大合集
  • 【个人向】《HTTP图解》阅后小结
  • Docker: 容器互访的三种方式
  • ES6核心特性
  • js中的正则表达式入门
  • log4j2输出到kafka
  • magento2项目上线注意事项
  • MD5加密原理解析及OC版原理实现
  • MYSQL 的 IF 函数
  • niucms就是以城市为分割单位,在上面 小区/乡村/同城论坛+58+团购
  • PHP 小技巧
  • Python_网络编程
  • RedisSerializer之JdkSerializationRedisSerializer分析
  • VUE es6技巧写法(持续更新中~~~)
  • vuex 笔记整理
  • 从0搭建SpringBoot的HelloWorld -- Java版本
  • 类orAPI - 收藏集 - 掘金
  • 区块链共识机制优缺点对比都是什么
  • 区块链技术特点之去中心化特性
  • 深入体验bash on windows,在windows上搭建原生的linux开发环境,酷!
  • 手写双向链表LinkedList的几个常用功能
  • 腾讯优测优分享 | 你是否体验过Android手机插入耳机后仍外放的尴尬?
  • elasticsearch-head插件安装
  • 我们雇佣了一只大猴子...
  • ​探讨元宇宙和VR虚拟现实之间的区别​
  • (24)(24.1) FPV和仿真的机载OSD(三)
  • (3)选择元素——(14)接触DOM元素(Accessing DOM elements)
  • (Mac上)使用Python进行matplotlib 画图时,中文显示不出来
  • (Pytorch框架)神经网络输出维度调试,做出我们自己的网络来!!(详细教程~)
  • (二)Kafka离线安装 - Zookeeper下载及安装
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (蓝桥杯每日一题)love
  • (十六)一篇文章学会Java的常用API
  • (转)scrum常见工具列表
  • (自适应手机端)响应式服装服饰外贸企业网站模板
  • ***原理与防范
  • .NET COER+CONSUL微服务项目在CENTOS环境下的部署实践
  • .net 设置默认首页
  • .net/c# memcached 获取所有缓存键(keys)