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

简介: 这篇文章详细介绍了双指针算法的概念、应用场景和八道力扣题目的解题思路及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;
    }
}

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

目录
相关文章
|
1月前
|
存储 算法
算法入门:专题二---滑动窗口(长度最小的子数组)类型题目攻克!
给定一个正整数数组和目标值target,找出总和大于等于target的最短连续子数组长度。利用滑动窗口(双指针)优化,维护窗口内元素和,通过单调性避免重复枚举,时间复杂度O(n)。当窗口和满足条件时收缩左边界,更新最小长度,最终返回结果。
|
2月前
|
机器学习/深度学习 存储 算法
动态规划算法深度解析:0-1背包问题
0-1背包问题是经典的组合优化问题,目标是在给定物品重量和价值及背包容量限制下,选取物品使得总价值最大化且每个物品仅能被选一次。该问题通常采用动态规划方法解决,通过构建二维状态表dp[i][j]记录前i个物品在容量j时的最大价值,利用状态转移方程避免重复计算子问题,从而高效求解最优解。
444 1
|
2月前
|
机器学习/深度学习 人工智能 搜索推荐
从零构建短视频推荐系统:双塔算法架构解析与代码实现
短视频推荐看似“读心”,实则依赖双塔推荐系统:用户塔与物品塔分别将行为与内容编码为向量,通过相似度匹配实现精准推送。本文解析其架构原理、技术实现与工程挑战,揭秘抖音等平台如何用AI抓住你的注意力。
577 7
从零构建短视频推荐系统:双塔算法架构解析与代码实现
|
1月前
|
存储 算法
算法入门:专题一:双指针(有效三角形的个数)
给定一个数组,找出能组成三角形的三元组个数。利用“两边之和大于第三边”的性质,先排序,再用双指针优化。固定最大边,左右指针从区间两端向内移动,若两短边之和大于最长边,则中间所有组合均有效,时间复杂度由暴力的O(n³)降至O(n²)。
|
1月前
|
存储 算法 编译器
算法入门:剑指offer改编题目:查找总价格为目标值的两个商品
给定递增数组和目标值target,找出两数之和等于target的两个数字。利用双指针法,left从头、right从尾向中间逼近,根据和与target的大小关系调整指针,时间复杂度O(n),空间复杂度O(1)。找不到时返回{-1,-1}。
|
2月前
|
算法 搜索推荐 Java
贪心算法:部分背包问题深度解析
该Java代码基于贪心算法求解分数背包问题,通过按单位价值降序排序,优先装入高价值物品,并支持部分装入。核心包括冒泡排序优化、分阶段装入策略及精度控制,体现贪心选择性质,适用于可分割资源的最优化场景。
269 1
贪心算法:部分背包问题深度解析
|
1月前
|
存储 人工智能 算法
从零掌握贪心算法Java版:LeetCode 10题实战解析(上)
在算法世界里,有一种思想如同生活中的"见好就收"——每次做出当前看来最优的选择,寄希望于通过局部最优达成全局最优。这种思想就是贪心算法,它以其简洁高效的特点,成为解决最优问题的利器。今天我们就来系统学习贪心算法的核心思想,并通过10道LeetCode经典题目实战演练,带你掌握这种"步步为营"的解题思维。
|
2月前
|
机器学习/深度学习 资源调度 算法
遗传算法模型深度解析与实战应用
摘要 遗传算法(GA)作为一种受生物进化启发的优化算法,在复杂问题求解中展现出独特优势。本文系统介绍了GA的核心理论、实现细节和应用经验。算法通过模拟自然选择机制,利用选择、交叉、变异三大操作在解空间中进行全局搜索。与梯度下降等传统方法相比,GA不依赖目标函数的连续性或可微性,特别适合处理离散优化、多目标优化等复杂问题。文中详细阐述了染色体编码、适应度函数设计、遗传操作实现等关键技术,并提供了Python代码实现示例。实践表明,GA的成功应用关键在于平衡探索与开发,通过精心调参维持种群多样性同时确保收敛效率
|
2月前
|
机器学习/深度学习 边缘计算 人工智能
粒子群算法模型深度解析与实战应用
蒋星熠Jaxonic是一位深耕智能优化算法领域多年的技术探索者,专注于粒子群优化(PSO)算法的研究与应用。他深入剖析了PSO的数学模型、核心公式及实现方法,并通过大量实践验证了其在神经网络优化、工程设计等复杂问题上的卓越性能。本文全面展示了PSO的理论基础、改进策略与前沿发展方向,为读者提供了一份详尽的技术指南。
粒子群算法模型深度解析与实战应用
|
3月前
|
Kubernetes Docker Python
Docker 与 Kubernetes 容器化部署核心技术及企业级应用实践全方案解析
本文详解Docker与Kubernetes容器化技术,涵盖概念原理、环境搭建、镜像构建、应用部署及监控扩展,助你掌握企业级容器化方案,提升应用开发与运维效率。
784 108

热门文章

最新文章

推荐镜像

更多
  • DNS
  • 下一篇
    oss云网关配置