算法—双指针

双指针算法可以帮忙把时间复杂度降低一个维度,即原本O(n2)降为O(n);将O(n)降为O(1)

移动零

移动零

题目解析

  1. 将所有0移动到末尾
  2. 保持非0元素相对顺序
  3. 对数组进行原地操作(不开辟额外空间)

image.png

算法原理

数组划分/数组分块——划分为非0元素和0元素两个区间
解决方法:双指针算法(利用数组下标充当指针)
原理: ⽤⼀个 cur 指针来扫描整个数组,另⼀个 dest 指针⽤来记录⾮零数序列 的最后⼀个位置。根据 cur 在扫描的过程中,遇到的不同情况,分类处理,实现数组的划分。 在 cur 遍历期间,使 [0, dest] 的元素全部都是⾮零元素, [dest + 1, cur - 1] 的 元素全是零。

流程:

  • ** **a. 初始化 cur = 0 (⽤来遍历数组), dest = -1 (指向⾮零元素序列的最后⼀个位置。 因为刚开始我们不知道最后⼀个⾮零元素在什么位置,因此初始化为 -1 )

  • b. cur 依次往后遍历每个元素,遍历到的元素会有下⾯两种情况:

      i. 遇到的元素是 0 , cur 直接 ++ 。因为我们的⽬标是让 [dest + 1, cur - 1] 内 的元素全都是零,因此当 cur 遇到 0 的时候,直接 ++ ,就可以让 0 在 cur - 1 的位置上,从⽽在 [dest + 1, cur - 1] 内; <br />ii. 遇到的元素不是 0 , dest++ ,并且交换 cur 位置和 dest 位置的元素,之后让 cur++ ,扫描下⼀个元素。<br /> • 因为 dest 指向的位置是⾮零元素区间的最后⼀个位置,如果扫描到⼀个新的⾮零元 素,那么它的位置应该在 dest + 1 的位置上,因此 dest 先⾃增 1 ; <br />• dest++ 之后,指向的元素就是 0 元素(因为⾮零元素区间末尾的后⼀个元素就是 0 ),因此可以交换到 cur 所处的位置上,实现 [0, dest] 的元素全部都是⾮零 元素, [dest + 1, cur - 1] 的元素全是零。  <br />![](https://img-blog.csdnimg.cn/img_convert/b7395e9bf3e416bdc29fd71ea0ac02a4.jpeg)
    

代码实现

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
      for (int cur = 0, dest = -1; cur < nums.size(); cur++)

        {
            if(nums[cur]) //处理非0元素
                swap(nums[++dest],nums[cur]);
        }
    }
};

复写零

复写零

题目解析

  1. 不要超过数组长度
  2. 不要开辟额外空间

算法原理

解法:依旧是双指针算法
思路:先根据"异地"操作,然后优化成双指针下的"就地"操作image.png

流程:

  1. 先找到最后一个复写的数
  2. 处理边界情况(eg:1,0,2,3,0,4这样dest最终会造成越界访问)
  3. 从后向前完成复写操作

注意:这里不能从前向后进行复写,否则会覆盖掉后面的数字

a. 初始化两个指针 cur = 0 , dest = 0 ;
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-- ,复写下⼀个位置

代码实现

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

            }    
        }
    }
};

快乐数

快乐数

题目解析

  • 题目定义快乐数的第一种情况:这个数会变成1,是快乐数

image.png

  • 题目定义快乐数的第二种情况:会陷入某一个环里开始循环,永远变不为1所以不是快乐数

image.png

算法原理

  • 第一种情况相当于也是一个环,只不过环中所有的数都是1
  • 第二种情况所有环中的数都不是1

所以以上两种情况抽象成一种,即总会进入一个环里开始循环。故只需要判断链表里是否有1就能确定是否有快乐数。(ps:类似判断列表是否有环——快慢指针)

快慢指针:慢指针每次向后移动一步,快指针每次向后移动两步;判断相遇时候的值即可

题目中告诉我们最终出现的两种情况——1.变成1一直循环下去 2.在循环里永远变不为1。若题目不给出这句话,我们也可以证明出来

鸽巢原理(抽屉原理)
n个鸽巢,n+1只鸽子——至少会有一个巢穴有大于1的鸽子数
题目中数据范围时int的最大值231-1(2.1*109) 让这个数再大一点,方便我们确定范围
image.png
因为最大的数只能到810,每次变化时都会落在[1,810]这个区间里,所以当我们变化次数超过810时,他一定会有重复的数字落入这个范围里,即一定会进入环里。

代码实现

class Solution {
public:
// 返回 n 这个数每⼀位上的平⽅和
    int bitSum(int n) 
    {
    int sum = 0;
        while(n)
        {
            int t = n % 10;
            sum += t * t;
            n /= 10;
        }
    return sum;
        }
        
    bool isHappy(int n)
    {
        int slow = n, fast = bitSum(n);
        while(slow != fast)
        {
            slow = bitSum(slow);
            fast = bitSum(bitSum(fast));
        }
    return slow == 1;
     }
};

盛水最多的容器

盛水最多的容器

题目解析

选两条线,取较小的(木桶原理)与x轴进行相乘算出容积

算法原理

  1. 暴力枚举:先固定最左边的线,依次乘,把所有情况全算出来找出最大值(两层for循环)时间复杂度O(n^2)image.png

  2. 利用单调性,使用双指针,即对撞指针(时间复杂度O(n),空间复杂度O(1))

  • 先寻找规律:即随便取两个数,研究一个小区间,然后拿两个中较小的数向内枚举。如图可以看出,要么h会减小,要么w宽度会减小,所以直接把4这个数字pass掉,不需要进行枚举。image.png

  • 扩大规律,直接拿最左边的数和最右边的数,计算出V1,然后向内枚举时可以舍弃掉1(较小的数),研究下一段区间。当两个指针相遇时,我们计算出所有的V取最大值即可。image.png

代码实现

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

有效三角形个数

有效三角形个数

题目解析

emm三角形两边之和大于第三边(hhhh)

算法原理

  • 暴力枚举——令a+b>c a+c>b b+c>a三个条件同时成立即可,但这个方法要判断三次

判断三⻆形的优化

  • 如果能构成三⻆形,需要满⾜任意两边之和要⼤于第三边。但是实际上只需让较⼩的两条边之和⼤于第三边即可。
  • 因此我们可以先将原数组排序,然后从⼩到⼤枚举三元组,⼀⽅⾯省去枚举的数量,另⼀⽅⾯⽅便判断是否能构成三⻆形
//超时
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;
}
};

image.png

  • 单调性,双指针

先将数组排序。根据「解法⼀」中的优化思想,我们可以固定⼀个「最⻓边」,然后在⽐这条边⼩的有序数组中找出⼀个⼆元组,使这个⼆元组之和⼤于这个最⻓边。由于数组是有序的,我们可以利⽤「对撞指针」来优化。
设最⻓边枚举到 i 位置,区间[left, right] 是 i 位置左边的区间(也就是⽐它⼩的区间):

  1. 如果 nums[left] + nums[right] > nums[i] :
  2. 说明 [left, right - 1] 区间上的所有元素均可以与 nums[right] 构成⽐nums[i] ⼤的⼆元组
  3. 满⾜条件的有 right - left 种情况(下标相减)
  4. 此时 right 位置的元素的所有情况相当于全部考虑完毕, right-- ,进⼊下⼀轮判断
  5. 如果 nums[left] + nums[right] <= nums[i] :
    1. 说明 left 位置的元素是不可能与 [left + 1, right] 位置上的元素构成满⾜条件的⼆元组
    2. left 位置的元素可以舍去, left++ 进⼊下轮循环 (即换一个固定的数,重复上述过程)

image.png

代码实现

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

和为s的两个数字

和为s的两个数字

题目解析

  • 数组是有序的
  • 最终结果返回数字,如果有多对,只需返回一对即可

算法原理

  1. 暴力解法

仅需两个for循环,先固定一个数,挨个相加。但没有利用数组有序的特性image.png

  1. 双指针算法——对撞指针
    1. 当left+right > target;那么right所指的数舍去,right–
    2. 当left+right < target;那么[left+1,right] 之间的数都不可能满足,所以left++
    3. 当left+right = target; 返回

image.png

代码实现

class Solution {
public:
    vector<int> twoSum(vector<int>& price, int target) {
        int left = 0,right = price.size()-1;
        while(left < right)
        {
            int sum = price[left] + price[right];
            if (sum < target) left++;
            else if(sum > target) right--;
            else return{price[left],price[right]}; //C++语法,初始化列表(隐形转换,会自动转换成vector<int>)
        }
       
       //照顾编译器,让所有路径有返回值
       return{-4221,-1};
    }
};
//当返回值是vector,并且只需要返回两个变量时,可以用初始化列表写法

三数之和

三数之和

题目解析

  1. 要求选三个不同位置的数
  2. 注意中的不允许重复是指即使三个数位置不同,也都满足结果,但因为元素一样,所以只选择一组即可image.png

算法原理

  • 解法一:排序+暴力枚举+set去重

去重操作我们是使用容器进行,是常数级别,可以忽略不计,在面试时,往往要求我们不使用现成的容器进行去重image.png

  • 解法二:排序+双指针

(有点类似上道题的两数之和) 这里去重我们尝试不使用容器set
注意:

  1. 「去重」操作:
    1. 找到⼀个结果之后, left 和 right 指针要「跳过重复」的元素;
    2. 当使⽤完⼀次双指针算法之后,固定的 a 也要「跳过重复」的元素 ;(-4 -4)
    3. 去重操作移动指针时,要避免越界(【0,0,0,0,】)
  2. 不漏操作
    1. 找到一种结果后,双指针不要听继续缩小空间寻找
  3. (常数级别的小优化)固定数字a时,只需要固定a<0的数,因为数组经过排序后,后面的正数无论怎么组合也不会出现负数的情况,所以只需要固定a<0的数


image.png

代码实现

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
                    {
						//大括号会直接形成vector<int>数组,存储到ret里
                        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也不能越界(这里先让i移动下一个位置,然后去之前的值比较
			若相等,继续移动。我们这里去重操作在for循环最后,所以为避免多++一个位置
			删除for循环里的++ */
            i++;
            while(i < n && nums[i] == nums[i - 1]) i++;
            }
                return ret;
     }
};

四数之和

四数之和

题目解析

与三数之和类似

算法原理

  • 解法一:排序+暴力枚举+set去重

  • 解法二:排序+双指针

先依次固定一个数记为a,剩余后面的数利用**“ 三数之和“**的思想,找到三个数,使其和为target-a。
依次固定一个数b,在b后面的区间,利用双指针找到使其和为target-a-b.image.png

同样需要处理细节问题:不重、不漏

  1. 不重(3个地方)
    1. 当left和right找到结果时,要跳过相同的数
    2. 当利用完双指针寻找完之后,b也要跳过相同的数
    3. 当找出三数之和后,a也要跳过相同的数
  2. 不漏:在我们利用双指针寻找结果为target-a-b时,当找到一个结果时,不要停继续缩小区间寻找结果

代码实现

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]; //目标值target-a-b   //这里提交leetcode会提示数据溢出的错误,换成longlong
                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--;

                    }
                }
                //去重二b
                j++;
                while(j<n && nums[j]==nums[j-1]) j++;
            }
            //去重a
            i++;
            while(i<n && nums[i] == nums[i-1]) i++;
        }
    return ret;
    }
};

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

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

相关文章

互联网洗鞋店小程序怎么做,流程有哪些?

洗鞋店小程序让洗鞋更便捷高效&#xff0c;用户只需通过手机预约&#xff0c;即可享受上门取送服务&#xff0c;省时省力&#xff0c;让鞋子焕然一新。下面我们详细介绍这个小程序的功能&#xff1a; 1. 轻松预约&#xff1a;用户可以随时随地通过洗鞋店小程序预约洗鞋服务&…

SDN、SD-WAN、CDN、SDH分别是什么,有什么关联?

SDN代表“软件定义网络”&#xff0c;是一种网络架构&#xff0c;它将网络控制和数据转发分离。SDWAN代表“软件定义广域网”&#xff0c;是SDN的一种实现&#xff0c;在广域网中使用虚拟化技术来连接分支机构和数据中心。 CDN代表“内容分发网络”&#xff0c;是一种通过在全球…

Spring框架体系及Spring IOC思想

目录 Spring简介Spring体系结构SpringIOC控制反转思想自定义对象容器Spring实现IOCSpring容器类型容器接口容器实现类对象的创建方式使用构造方法使用工厂类的方法使用工厂类的静态方法对象的创建策略对象的销毁时机生命周期方法获取Bean对象的方式通过id/name获取通过类型获取…

GitHub----使用记录

一、上传文件到仓库 1、首先新建一个github仓库 然后先记住这一句指令 2、下载git工具 https://git-scm.com/downloads 下载工具安装不用运行 3、使用git工具上传文件并推送 找到你想上传的文件的位置&#xff0c;右击git Bush here git init &#xff1a;初始化这个仓…

CSP认证2023-03:田地丈量、垦田计划、LDAP,python满分解答代码

CSP认证2023-03&#xff1a;田地丈量、垦田计划、LDAP&#xff0c;python满分解答代码 目录 一、田地丈量 问题描述 输入输出 思路 代码和结果 二、垦田计划 问题描述 输入和输出 思路 代码和结果 三、LDAP 问题描述 思路 代码和结果 一、田地丈量 问题描…

React 签字手写签名组件 react-signature

安装依赖包 npm install uiw/react-signature示例代码 import React, { useRef } from "react"; import Signature from uiw/react-signature;export default function App() {const $svg useRef(null);const handle (evn) > $svg.current?.clear();return (…

重写equals为什么要重写hashCode

答&#xff1a;因为两个相等的对象的 hashCode值必须是相等。也就是说如果equals方法判断两个对象是相等的&#xff0c;那这两个对象的hashCode值也要相等。 如果重写equals()时没有重写 hashCode()方法的话就可能会导致equals方法判断是相等的两个对象&#xff0c;hashcode值…

LabVIEW通过编程将图形类控件的X轴显示为时间戳

LabVIEW通过编程将图形类控件的X轴显示为时间戳 每个版本的LabVIEW中都有属性节点&#xff0c;可以以编程方式调整X轴和Y轴格式。对于不同版本的LabVIEW&#xff0c;这些属性节点无法在同一个位置找到。请参阅以下部分&#xff0c;了解特定版本LabVIEW的相关属性节点的位置。 …

供应商关系管理软件:如何使用它来改善供应商关系?

从最基本的角度来说&#xff0c;企业需要供应商为其生产和销售的产品或服务提供原材料&#xff0c;或者为其提供资源和服务来经营自己的业务。 建立稳定而健康的供应商关系的最大优势之一&#xff0c;就是可以为企业带来更高的价值。企业对供应商了解越多&#xff0c;供应商对…

强基固本,红海云数字化重塑提升国企干部管理能力

国有企业的干部管理体系建设具有重要的战略意义&#xff0c;对于构建高素质专业化的干部队伍&#xff0c;推动企业高质量发展至关重要。特别是在党的二十大以后&#xff0c;建设中国特色现代企业制度&#xff0c;在完善公司治理中加强党的领导&#xff0c;加强党管干部党管人才…

全球市场:跨境电商消费者权益维权的趋势

随着数字技术的迅猛发展&#xff0c;跨境电商逐渐成为全球贸易的引领者&#xff0c;但与此同时&#xff0c;消费者权益维权问题也备受关注。本文将深入探讨跨境电商消费者权益维权的趋势&#xff0c;剖析全球市场中涌现的挑战和解决方案&#xff0c;以期为构建更加公平、透明的…

GoWeb学习-第二天

文章目录 从零开始学Go web——第二天一、安装Go语言二、建立web目录2.1 创建GO语言包目录2.2 创建Go web文件 三、编译并运行Go web应用3.1 编译并运行3.2 查看结果 从零开始学Go web——第二天 ​ 第一天我们了解了与web息息相关的HTTP协议&#xff0c;聊了聊Go与web的关系等…

struts2 代码执行 (CVE-2020-17530)

struts2 代码执行 &#xff08;CVE-2020-17530&#xff09; 使用struts2框架的网站 URL上会有.action结尾这个特征 名称: struts2 代码执行 &#xff08;CVE-2020-17530&#xff09; 描述: Struts2是一个基于MVC设计模式的Web应用框架&#xff0c;它本质上相当于一个servlet…

PM2 在线和离线部署uvicorn和fastapi项目过程

PM2介绍 PM2 is a daemon process manager that will help you manage and keep your application online 24/7 PM2是一个后台进程管理工具&#xff0c;能帮助管理应用和维持应用7*24小时运行。 PM2在线安装 npm install pm2 -gPM2离线安装(适用于内网) 参见 如何离线安装pm2…

算法通关村第二关—手写链表反转(青铜)

链表反转的三种方式 一、建立虚拟头结点辅助反转 为了方便反转&#xff0c;可以创建一个ans结点&#xff0c;让ans.next head,然后后面的结点一次插入到ans.next 在下图中&#xff0c;对&#xff08;1->2->3->4->5&#xff09;进行反转&#xff0c;可以创建ans&…

马骑顿部署实在RPA,所产价值超该项目投入6倍

“数字化最大的挑战在于我们增长太快了&#xff0c;对于全域经营的DTC品牌来说&#xff0c;让一个部门去管理所有平台数据&#xff0c;形成品牌全域生意的协同实非易事。”马骑顿运营部负责人表示。2022年&#xff0c;马骑顿搭建企业数据中台&#xff0c;统一管理线上数据&…

wsj0数据集原始文件.wv1.wv2转换成wav文件

文章目录 准备一、获取WSJO数据集二、安装sph2pipe三、转换代码四、结果展示 ​ 最近做语音分离实验需要用到wsj0-2mix数据集&#xff0c;但是从李宏毅语音分离教程里面获取的wsj0-2mix只有一部分。从网上获取到了完整的WSJO数据集后&#xff0c;由于原始的语音文件后缀是wv1或…

网关路由器双栈配置中的IPv6相关选项解析

1、引言 讲知识往往是枯燥无味的&#xff0c;我们先从问题入手。家庭网关&#xff08;光猫&#xff09;、路由器是我们每个人或多或少都有所接触的2种设备。现在一般都是光纤入户&#xff0c;通常每个家庭配备一个光猫和一台家用路由器。 目前有许多网络服务已经提供了IPv6支…

距离“全自动”漏洞挖掘又近了一步!腾讯安全大数据实验室论文入选ACM CCS 2023

计算机领域国际权威学术顶会ACM CCS 2023于11月26日在丹麦哥本哈根开幕。腾讯安全大数据实验室团队论文《Hopper: Interpretative Fuzzing for Libraries》被大会收录&#xff0c;昨天&#xff0c;实验室研究员谢雨轩受邀出席大会进行主题分享。 该论文提出了解释性模糊测试&a…

前端笔试遇到的坑-100题

1.闭包 let 形成闭包 var全局变量 function test() {for (var i 0; i < 6; i) {console.log(i); //1 2 3 4 5 6// setTimeout(() > {// console.log(i);// }, 0); 6 6 6 6 6 6 6} } test();var array []; for (var i 0; i < 3; i) {array.push(() > i);…
最新文章