两个双指针 的 “他“和“ 她“会相遇么? —— “双指针“算法 (Java版)

两个双指针 的 “他“和“ 她“会相遇么? —— “双指针“算法 (Java版)

码农世界 2024-05-22 前端 60 次浏览 0个评论

本篇会加入个人的所谓鱼式疯言

❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言

而是理解过并总结出来通俗易懂的大白话,

小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.

🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人能接受我们这个概念 !!!

前言

因为小编最近在学习算法, 每次学习新知识,都会有新的体会和心得,就会和小伙伴们一起分享, 今天 带来我们 算法 首篇 ————“双指针” 算法 💥💥💥

这时就会有小伙伴问了,我们的"双指针"算法 不是在 “题海寻offer” 专题中讲解过么 ? 为啥还要重复学习呢 🤔 🤔 🤔

是的, 但这次小编带来的是更重点更详细并且更系统以算法的角度一步一步讲解我们的 “双指针” 算法 💖 💖 💖

目录

  1. 双指针算法初识

  2. 双指针算法的运用

  3. 双指针算法的总结

一. 双指针初识

1. 双指针算法的认识

首先小编在这里得区别一个概念

这里我们提及的 双“指针” 的指针 并不是我们学过 C语言 的 定义 int * p = &a 那个指针 ,

这里我们用的指针其实就是一个 变量

我们通过 变量 来指向一个元素 然后不断移动来影响数据的一个单纯的 变量

而 双指针 就是定义两个变量来 影响数据

2. 双指针算法的种类

常见的双指针有两种形式:

一种是 对撞指针,一种是 左右指针。

对撞指针

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

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

◦ left == right (两个指针指向同一个位置)

◦ left > right (两个指针错开)

快慢指针

快慢指针:又称为 龟兔赛跑算法,其基本思想就是使用两个移动 速度不同 的指针在数组或链表等序列结构上移动。

这种方法对于处理 环形链表 或 数组 非常有用。

其实不单单是环形链表或者是数组,如果我们要研究的问题出现 循环往复 的情况时,均可考虑使用 快慢指针 的思想。

快慢指针的实现方式有很多种,最常用的一种就是:

在一次循环中,每次让慢的指针向后移动 一位 ,而快的指针往后移动 两位,实现一快一慢。

鱼式疯言

总结三句话

  1. 指针本质上是 变量, 只是形式上我们俗称 “指针”

  2. 对撞指针 是 从起点和终点两个 同时 相向 走,会相遇 的两个指针

  3. 快慢指针 是 同向不相遇的两个走的跨度不一样的指针

二. 双指针的算法运用

1. 复写零

1089.复写零题目链接

<1> . 题目描述

给你一个长度固定的整数数组 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]

题目的 含义

在这个数组中,我们 从左往右 ,每出现一个0 就在右边位置再 写一个 0 ,然后数据一直 挪动 , 原有数据就要一直往右 挪动

<2>. 讲解算法原理

大体思路 分为 :

如果 「从前向后」 进行原地复写操作的话,由于 0 的出现会 复写两次 ,导致没有复写的数 「被覆

盖掉」 。因此我们选择 「从后往前」 的复写策略。

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

步:

算法流程

  • 先找到最后一个复写的数
    1. 先定义一个 cur=0 和 dest= -1 指针来 通向移动
    1. 先让 cur 向前走一步 , 当 cur 位置遇到 值 为 0 时就 dest 就走 两 步

    3,否则让 dest 走一步 ,直到 dest 到底 数组下标 >= n- 1 的位置就停止

    • 然后从后向前进行复写操作。
      1. cur 遇到 0 时 ,就让 dest 跳跃两次 并 把这两次的值都赋值为 0 , cur 走一步
      1. cur 不是 0 时 ,就把 cur 的值赋给 dest , 接着让 dest 和 cur 都走一步

      <3>. 编写代码

      class Solution {
             public static void duplicateZeros(int[] arr) {
              int cur=0;
              int dest=-1;
              int n=arr.length;
             while (cur < n) {
                 if (arr[cur]==0) {
                     dest +=2;
                 } else {
                      dest++;
                 }
                 if (dest >= n-1) {
                     break;
                 }
                 cur++;
             }
             if (dest > n-1) {
                 arr[n-1]=0;
                 cur--;
                 dest-=2;
             }
             while (cur >= 0) {
                 if (arr[cur]==0) {
                     arr[dest--]=0;
                     arr[dest--]=0;
                     cur--;
                 } else {
                     arr[dest--]=arr[cur--];
                 }
             }
          }
      }
      

      鱼式疯言

      说两句小编关于本题自己的理解和体会

      1. 这里我们用到了 前后指针(快慢指针) , 一个 快指针 模拟 复写零 后的数组的位置 , 而 慢指针 用于 模拟 == 未复写零后的数组的最后一个元素的位置
      1. 细节:
         if (dest > n-1) {
             arr[n-1]=0;
             cur--;
             dest-=2;
         }
      

      当 == dest== 的位置跳到 n 的位置时, 说明 0 的位置不够放 , 我们就需要 把最后一个位置 赋值为 0 ,

      并让 dest 想左走两步, cur 走一步即可

      2. 快乐数

      202. 快乐数题目链接

      <1>. 题目描述

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

      「快乐数」 定义为:

      ◦ 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。

      ◦ 然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1 。

      ◦ 如果这个过程 结果为 1 ,那么这个数就是快乐数。

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

      示例 1:

      输入: n = 19

      输出: true

      解释:

      19 -> 1 * 1 + 9 * 9 = 82

      82 -> 8 * 8 + 2 * 2 = 68

      68 -> 6 * 6 + 8 * 8 = 100

      100 -> 1 * 1 + 0 * 0 + 0 * 0 = 1

      示例 2:

      输入: n = 2

      输出: false

      解释:(这里省去计算过程,只列出转换后的数)

      2 -> 4 -> 16 -> 37 -> 58 -> 89 -> 145 -> 42 -> 20 -> 4 -> 16

      往后就不必再计算了,因为出现了重复的数字,最后结果肯定不会是1

      题目含义:

      让一个数一直取出每个位置上的数字的 平方算出他们的之和 ,得到这个数后 判断是否为 1

      如果为 1 说明是快乐数 返回 true

      如果继续循环的按照上面的方法计算知道 得到看是否 最终结果为 1= ,若不是就返回 false

      <2>. 讲解算法思想

      我们先简单的分析一下

      为了方便叙述,将「对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和」这一个

      操作记为 x 操作;

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

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

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

      由于上述两种情况只会出现一种

      因此,只要我们能确定循环是在 「情况一」 中进行

      还是在 「情况二 ] 中进行,就能得到结果。

      大体的算法思想

      先用一个方法来 获取到每一位上数字的 平方之和

      由于我们在循环内查找这个数 (相当于检查一个链表是否成环) , 所以我们第一步考虑的应该是 快慢指针 的思维,

      我们进行利用 定义 一个 left 的单位为 1 走一步, 再定义一个 right 单位为 2 走两步 ,因为是环形所以 最终是一定会相遇的

      最后一步 我们只需要在相遇的位置判断是否是 等于 1 即可

      <3>. 编写代码

      class Solution {
          public boolean isHappy(int n) {
              int slow=n;
              int fast=happyFunc(n);
              // 一定会成环的
              while(slow != fast) {
                      slow = happyFunc(slow);
                      fast = happyFunc(happyFunc(fast));
              }
              // 最终相遇时只要为 1 就为快乐数
              if(slow==1) {
                  return true;
              }
              return false;
          }
          private int happyFunc(int n) {
              int sum=0;
              while(n != 0) {
                  int t = n % 10;
                  sum += (t*t);
                  n /= 10; 
              }
              return sum;
          }
      }
      

      鱼式疯言

      先说几点体会吧

      1. 当我们碰到 一个成环或者循环 的一种结构时, 快慢指针的思路是很有必要去尝试的
      1. 本题 的 特点 最终是会成为一个 特定的值 , 我们只需要循环判断即可
      1. 本题细节 :
      int slow=n;
          int fast=happyFunc(n);
      

      初始化的时候

      不可以让 fast 和 slow 同时为 n ,否则就进入不了 循环 .

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

      179. 查找总价值为目标值的两个商品题目链接

      <1>. 题目描述

      输入一个 递增 排序的数组和一个数字s ,在数组中查找两个数,使得它们的和正好是s 。如果有多

      对数字的和等于s ,则输出 任意一对 即可。

      示例 1:

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

      输出: [2,7] 或者 [7,2]

      题目含义 就是找两个和为 s 的数字

      <2>. 讲解算法思想

      题目分析

      如果我们要求两个数的 和 ,只需要先 固定 一个数,然后再 寻找其他数 判断即可寻找到

      暴力枚举

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

      双指针 的思路

      当我们看到 一个有序数组的时候, 我们头脑中就得建立两个思路 :

      一个 二分查找 算法

      一个 双指针 算法

      而在本题中更适合用我们的双指针来解决 两数和 的问题

      1. 先定义一个指针为 left = 0 , 另一个指针为 right = n-1
      1. left 和 right 的 数值之和 > target 时 ,我们就让 较大值 right --

      left 和 right 的数组之和 < target 时, 我们就让较小值 left ++ ;

      直到最终相遇或者寻找到目标值 target 为止 ,

      <3>. 编写代码

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

      鱼式疯言

      记住一点

      有序就要想到用 二分查找算法 或者 双指针算法

      4 有效三角形个数

      611.有效三角形个数题目链接

      <1>. 题目描述

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

      题目含义 :

      找出所有能满足 组合 三角形三条边 的个数(包含相同数字的边)

      <2>. 讲解算法思想

      题目分析

      如果我们要找三条边, 先 固定两个数 的基础上

      再找另外一个数来判断是否满足 三角形 的 三边条件

      算法过程

      解法一 : 暴力枚举

      三个 for= 来查找 是否存在该数据

      解法二 : 双指针算法

      先将 数组排序

      根据「解法一」中的优化思想,我们可以固定一个「最长边」,然后在比这条边小的有序数组中找

      出一个二元组,使这个二元组之和大于这个最长边。由于数组是有序的,我们可以利用「对撞指

      针」来优化。

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

      <3>. 编写代码

      class Solution {
          public int triangleNumber(int[] nums) {
                Arrays.sort(nums);
                int sz = nums.length;
                int count=0;
                for(int i=sz-1; i >= 2 ; i--) {
                  int left=0,right=i-1;
                  while(left < right) {
                      if(nums[left] + nums[right] > nums[i]) {
                          
                          count += (right-left);
                          right--;
                      } else {
                          left++;
                      }
                  }
                }
                return count;  
          }
      }
      

      鱼式疯言

      讲两点

      1. 必须 先排序 ,利用本身的 单调性 来进行 两个较小值和最大值进行比较来判断

      所以我们得出以下结论

      有序 》 单调性 》 双指针

      三. 双指针算法的总结

      在 复写零 和 快乐数 时, 用到了(快慢指针).特别在快乐数的时候,我们在循环的特点下

      我们建立了 快慢指针的思路

      而在 查找两个数之和和 有效三角形个数 时, 我们用到了 双指针(对撞指针)

      而在这里我们巧妙的利用 单调性 的方法来建立 对撞指针 的模型

      如果觉得小编写的还不错的咱可支持 三连 下 (定有回访哦) , 不妥当的咱请评论区 指正

      希望我的文章能给各位宝子们带来哪怕一点点的收获就是 小编创作 的最大 动力 💖 💖 💖

转载请注明来自码农世界,本文标题:《两个双指针 的 “他“和“ 她“会相遇么? —— “双指针“算法 (Java版)》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,60人围观)参与讨论

还没有评论,来说两句吧...

Top