双指针算法(超详细带8道例题及算法解析) —— 包含力扣题目有283移动零、1089复写零、202快乐数、11盛水最多的容器、611有效三角形的个数、179双数之和、15三数之和、18四数之和

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 这篇文章详细介绍了双指针算法的概念、应用场景和八道力扣题目的解题思路及Java代码实现。

双指针算法解析

双指针是一种思想,而不是说真的就是定义了两个指针,它和语言没有关系,比如C++,Java,Python等都可以使用双指针算法解题,而且是一种非常常见的算法

本篇博客适合所有语言学者阅读,因为算法是思想,每个题目除超详细的算法解析外后面还附赠了Java代码来供参考

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

左右指针

  • 一般用于顺序结构中,也称对撞指针
  • 左右指针从两端向中间移动。一个指针从最左端开始,另一个从最右端开始,然后逐渐往中间逼近
  • 左右指针的终止条件一般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循环),也就是:
  • left == right (两个指针指向同一个位置)
  • left > right (两个指针错开)

快慢指针

  • 又称为龟兔赛跑算法,其基本思想就是使用两个移动速度不同的指针在数组或链表等序列结构上移动
  • 这种方法对于处理环形链表或数组非常有用
  • 其实不单单是环形链表或者数组,如果我们要研究的问题出现循环往复的情况时,均可考虑使用快慢指针的思想
  • 快慢指针的实现方式有很多种,最常用的一种就是:
  • 在一次循环中,每次让慢的指针向后移动一位,而快的指针往后移动两位,实现一快一慢

1、力扣283.移动零

题目链接:283.移动零

题目描述:

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例 1:

**输入:** nums = `[0,1,0,3,12]`
**输出:** `[1,3,12,0,0]`

示例 2:

**输入:** nums = `[0]`
**输出:** `[0]`

提示:

  • 1 <= nums.length <= 104
  • -231 <= nums[i] <= 231 - 1

算法思路:

快排的思想:数组划分区间 - 数组分两块

在本题中,我们可以用一个 cur 指针来扫描整个数组,另一个 dest 指针用来记录非零数序列的最后一个位置。

根据 cur 在扫描的过程中,遇到的不同情况,分类处理,实现数组的划分。

在cur遍历期间,使 [0, dest] 的元素全部都是非零元素, [dest + 1, cur - 1] 的元素全是零, 这两部分属于处理过的区间,存放处理过的元素,单论处理过的区间的话,是符合题中结果的,而 [cur, n-1] 是待处理的区间,里面存放还未处理的元素

算法流程:

Java算法代码:

class Solution
{
    public void moveZeroes(int[] nums)
    {
        for(int cur = 0, dest = -1; cur < nums.length; cur++)
            if(nums[cur] != 0) // 仅需处理⾮零元素
            {
                dest++; // dest 先向后移动⼀位
                // 交换
                int tmp = nums[cur];
                nums[cur] = nums[dest];
                nums[dest] = tmp;
            }
    }
}

2、力扣1089复写零

题目链接:力扣1089复写零

题目描述:

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

算法思路:

原地复写 + 双指针

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

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

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

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

值得注意如何找最后一个复写的数,咱可以这样想,就先按另外开辟数组的方法,一个指针原数组用,另一个指针新开辟的数组用,把新开辟的数组完成复写后,原数组的指向不就是咱需要的最后一个复写的数么,当然,此题不能另外开辟空间,咱可以用这种思想,不复写只移动来找到那个复写的数

算法流程:

Java算法代码:

class Solution
{
    public void duplicateZeros(int[] arr)
    {
        int cur = 0, dest = -1, n = arr.length;
        // 1. 先找到最后⼀个需要复写的数
        while(cur < n)
        {
            if(arr[cur] == 0) dest += 2;
            else dest += 1;
            if(dest >= n - 1) break;
            cur++;
        }
        // 2. 处理⼀下边界情况
        if(dest == n)
        {
            arr[n - 1] = 0;
            cur--; dest -= 2;
        }
        // 3. 从后向前完成复写操作
        while(cur >= 0)
        {
            if(arr[cur] != 0) arr[dest--] = arr[cur--];
            else
            {
                arr[dest--] = 0;
                arr[dest--] = 0;
                cur--;
            }
        }
    }
}

3、力扣202快乐数

题目链接:力扣202快乐数

题目描述:

编写一个算法来判断一个数 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

题目分析:

简单证明:

算法思路:

补充知识:如何求一个数n每个位置上的数字的平方和

当然,用递归求也行

Java算法代码:

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

4、力扣11盛水最多的容器

题目链接:力扣11盛水最多的容器

题目描述:

定一个长度为 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

算法思路:

Java算法代码:

class Solution
{
    public int maxArea(int[] height)
    {
        int left = 0, right = height.length - 1, ret = 0;
        while(left < right)
        {
            int v = Math.min(height[left], height[right]) * (right - left);
            ret = Math.max(ret, v);
            if(height[left] < height[right]) left++;
            else right--;
        }
        return ret;
    }
}

5、力扣611有效三角形的个数

题目链接:力扣611有效三角形的个数

题目描述:

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

算法思路:

排序+双指针

Java算法代码:

class Solution
{
    public int triangleNumber(int[] nums)
    {
        // 1. 优化:排序
        Arrays.sort(nums);
        // 2. 利⽤双指针解决问题
        int ret = 0, n = nums.length;
        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;
    }
}

6、力扣179 查找总价为目标值的两个商品/ 原剑指Offer和为s的两个数字

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

题目描述:

购物车内的商品价格按照升序记录于数组 price。请在购物车中找到两个商品的价格总和刚好是 target。若存在多种情况,返回任一结果即可。

示例 1:

**输入:**price = \[3, 9, 12, 15\], target = 18
**输出:**\[3,15\] 或者 \[15,3\]

示例 2:

**输入:**price = \[8, 21, 27, 34, 52, 66\], target = 61
**输出:**\[27,34\] 或者 \[34,27\]

提示:

  • 1 <= price.length <= 10^5
  • 1 <= price[i] <= 10^6
  • 1 <= target <= 2*10^6

算法思路:

注意到本题是升序的数组,因此可以⽤「对撞指针」优化时间复杂度。

算法流程:

Java算法代码:

class Solution
{
    public int[] twoSum(int[] nums, int target)
    {
        int left = 0, right = nums.length - 1;
        while(left < right)
        {
            int sum = nums[left] + nums[right];
            if(sum > target) right--;
            else if(sum < target) left++;
            else return new int[] {nums[left], nums[right]};
        }
        // 照顾编译器
        return new int[]{0};
    }
}

7、力扣15三数之和

题目链接:力扣15三数之和

题目描述:

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

**输入:**nums = \[-1,0,1,2,-1,-4\]
**输出:**\[\[-1,-1,2\],\[-1,0,1\]\]
**解释:**
nums\[0\] + nums\[1\] + nums\[2\] = (-1) + 0 + 1 = 0 。
nums\[1\] + nums\[2\] + nums\[4\] = 0 + 1 + (-1) = 0 。
nums\[0\] + nums\[3\] + nums\[4\] = (-1) + 2 + (-1) = 0 。
不同的三元组是 \[-1,0,1\] 和 \[-1,-1,2\] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

**输入:**nums = \[0,1,1\]
**输出:**\[\]
**解释:**唯一可能的三元组和不为 0 。

示例 3:

**输入:**nums = \[0,0,0\]
**输出:**\[\[0,0,0\]\]
**解释:**唯一可能的三元组和为 0 。

提示:

  • 3 <= nums.length <= 3000
  • -105 <= nums[i] <= 105

算法思路:

每次固定一个位置,其余位置用双指针求双数之和的方法求,注意边界情况复杂

Java算法代码:

class Solution
{
    public List<List<Integer>> threeSum(int[] nums)
    {
        List<List<Integer>> ret = new ArrayList<>();
        // 1. 排序
        Arrays.sort(nums);
        // 2. 利⽤双指针解决问题
        int n = nums.length;
        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
                {
                    // nums[i] nums[left] num[right]
                    ret.add(new ArrayList<Integer>(Arrays.asList(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++;
            while(i < n && nums[i] == nums[i - 1]) i++;
        }
        return ret;
    }
}

8、力扣18四数之和

题目链接:力扣18四数之和

题目描述:

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abcd 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

示例 1:

**输入:**nums = \[1,0,-1,0,-2,2\], target = 0
**输出:**\[\[-2,-1,1,2\],\[-2,0,0,2\],\[-1,0,0,1\]\]

示例 2:

**输入:**nums = \[2,2,2,2,2\], target = 8
**输出:**\[\[2,2,2,2\]\]

提示:

  • 1 <= nums.length <= 200
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109

算法思路:

排序 + 双指针

Java算法代码:

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;
        for(int i = 0; i < n; ) // 固定数 a
        {
            // 三数之和
            for(int j = i + 1; j < n; ) // 固定数 b
            {
                // 双指针
                int left = j + 1, right = n - 1;
                long aim = (long)target - nums[i] - nums[j];
                while(left < right)
                {
                    int sum = nums[left] + nums[right];
                    if(sum > aim) right--;
                    else if(sum < aim) left++;
                    else
                    {
                        ret.add(Arrays.asList(nums[i], nums[j], nums[left++],
                                nums[right--]));
                        // 去重⼀
                        while(left < right && nums[left] == nums[left - 1])
                            left++;
                        while(left < right && nums[right] == nums[right + 1])
                            right--;
                    }
                }
                // 去重⼆
                j++;
                while(j < n && nums[j] == nums[j - 1]) j++;
            }
            // 去重三
            i++;
            while(i < n && nums[i] == nums[i - 1]) i++;
        }
        return ret;
    }
}

🧸前路漫漫,愿星光与您相伴!

目录
相关文章
|
2月前
|
算法 前端开发 数据处理
小白学python-深入解析一位字符判定算法
小白学python-深入解析一位字符判定算法
53 0
|
2月前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
56 3
|
18天前
|
机器学习/深度学习 人工智能 算法
深入解析图神经网络:Graph Transformer的算法基础与工程实践
Graph Transformer是一种结合了Transformer自注意力机制与图神经网络(GNNs)特点的神经网络模型,专为处理图结构数据而设计。它通过改进的数据表示方法、自注意力机制、拉普拉斯位置编码、消息传递与聚合机制等核心技术,实现了对图中节点间关系信息的高效处理及长程依赖关系的捕捉,显著提升了图相关任务的性能。本文详细解析了Graph Transformer的技术原理、实现细节及应用场景,并通过图书推荐系统的实例,展示了其在实际问题解决中的强大能力。
113 30
|
22天前
|
存储 算法
深入解析PID控制算法:从理论到实践的完整指南
前言 大家好,今天我们介绍一下经典控制理论中的PID控制算法,并着重讲解该算法的编码实现,为实现后续的倒立摆样例内容做准备。 众所周知,掌握了 PID ,就相当于进入了控制工程的大门,也能为更高阶的控制理论学习打下基础。 在很多的自动化控制领域。都会遇到PID控制算法,这种算法具有很好的控制模式,可以让系统具有很好的鲁棒性。 基本介绍 PID 深入理解 (1)闭环控制系统:讲解 PID 之前,我们先解释什么是闭环控制系统。简单说就是一个有输入有输出的系统,输入能影响输出。一般情况下,人们也称输出为反馈,因此也叫闭环反馈控制系统。比如恒温水池,输入就是加热功率,输出就是水温度;比如冷库,
169 15
|
2月前
|
搜索推荐 算法
插入排序算法的平均时间复杂度解析
【10月更文挑战第12天】 插入排序是一种简单直观的排序算法,通过不断将未排序元素插入到已排序部分的合适位置来完成排序。其平均时间复杂度为$O(n^2)$,适用于小规模或部分有序的数据。尽管效率不高,但在特定场景下仍具优势。
|
24天前
|
负载均衡 网络协议 算法
Docker容器环境中服务发现与负载均衡的技术与方法,涵盖环境变量、DNS、集中式服务发现系统等方式
本文探讨了Docker容器环境中服务发现与负载均衡的技术与方法,涵盖环境变量、DNS、集中式服务发现系统等方式,以及软件负载均衡器、云服务负载均衡、容器编排工具等实现手段,强调两者结合的重要性及面临挑战的应对措施。
55 3
|
26天前
|
安全 持续交付 Docker
深入理解并实践容器化技术——Docker 深度解析
深入理解并实践容器化技术——Docker 深度解析
50 2
|
1月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
1月前
|
运维 持续交付 虚拟化
深入解析Docker容器化技术的核心原理
深入解析Docker容器化技术的核心原理
46 1
|
1月前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
71 4

推荐镜像

更多