再探双指针——玄兵利器

175 阅读5分钟

引言

由于在某站看见一位前辈用双指针也写了一下这道题 于是乎又拿出来看看

题目

两数之和

给定一个整数数组 nums 和一个整数目标值 target, 请你在该数组中找出和为目标值target的那两个整数,并返回它们的数组下标。

相信各位都不陌生,力扣的入门题,暴力、哈希表相信各位都随手拈来了 今天看见前辈的双指针 边学习了一番,在此记录分享

var twoSum = function(nums, target) {
const indexArray = [];
for (let i = 0; i < nums.length; i++) {
    indexArray.push(i);
}

// 根据原数组 nums 的值对 indexArray 进行排序
indexArray.sort((a, b) => nums[a] - nums[b]);

// 使用双指针解决问题
let left = 0;
let right = nums.length - 1;
while (left < right) {
    const sum = nums[indexArray[left]] + nums[indexArray[right]];
    if (sum === target) {
        return [indexArray[left], indexArray[right]];
    } else if (sum < target) {
        left++;
    } else {
        right--;
    }
}

// 如果没有找到满足条件的两个数,返回空数组
return [];
};

首先创建一个新的数组 indexArray,用于存储原始数组 nums 的下标。然后,根据原数组 nums 的值对 indexArray 进行排序。接下来,使用双指针 leftright 来遍历排序后的 indexArray,在每一步迭代中计算指针指向的两个数之和,并与目标值 target 进行比较。最后返回满足条件的两个数的下标。

看来双指针是个好用法子,于是我又找了两道题试试。嘿嘿~

三数之和

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

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

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

思考

  • 非常显然,这题目正符合我们的胃口,双指针的思路很快就出现在了脑子里,不过双指针的前提是有序队列,这样才更有意义,因此我们首先要对数组进行排序
  • 题目说返回不重复的三元组,所以可能有相同的元素,我们要找三个数,因此在外循环中,我们需要选定一个数,暂时不动,再用双指针寻找。
  • 这里有个小细节,如果前多个元素是一样的,毫无疑问我们要跳过的,那么是要第一个还是最后一个呢?虽然不是很难,但鄙人不才,疏忽了。(应该是要第一个,否则可能会遗漏)
  • 那么选定了一个,后面的nums.length-1个就交给双指针去寻找吧

所以 我们也就有了结果

var threeSum = function(nums) {
    var result = []; // 存储结果的数组
    
    // 先对数组进行排序
    nums.sort((a, b) => a - b); // 使用快速排序算法对数组进行升序排序
    
    // 遍历数组,查找三个数之和等于0的组合
    for (var i = 0; i < nums.length - 2; i++) {
        // 跳过重复的元素,确保不重复处理相同的数
        if (i > 0 && nums[i] === nums[i - 1]) continue; 
        
        var target = -nums[i]; // 将问题转化为在剩余数组中找两数之和等于target
        var left = i + 1; // 左指针指向当前元素的下一个位置
        var right = nums.length - 1; // 右指针指向数组末尾
        
        // 使用双指针法寻找两数之和等于target的组合
        while (left < right) {
            var sum = nums[left] + nums[right]; // 计算两数之和
            
            if (sum === target) {
                // 找到满足条件的组合,加入到结果数组中
                result.push([nums[i], nums[left], nums[right]]);
                
                // 跳过重复的元素,确保不重复处理相同的数
                while (left < right && nums[left] == nums[left + 1]) left++;
                while (left < right && nums[right] == nums[right - 1]) right--;
                
                left++; // 左指针右移
                right--; // 右指针左移
            } else if (sum < target) {
                left++; // 和小于target,左指针右移
            } else {
                right--; // 和大于target,右指针左移
            }
        }
    }
    
    return result; // 返回结果数组
};

盛水最多的容器

题目

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。

这道题也是思路清晰多了 我们直接开始

在这个问题中,我们可以使用两个指针 left 和 right,分别指向数组的起始和结束位置。 然后,我们计算由当前指针所指的线段构成的容器的容量,并记录最大容量。 接着,我们将指向较短线段的指针向中间移动一步(因为移动较长线段的指针不会使容器的容量增加,而只会减少容器的宽度), 直到两个指针相遇为止。

var maxArea = function(height) {
    // 初始化最大面积为 0
    let maxArea = 0;
    // 左指针初始位置
    let left = 0;
    // 右指针初始位置
    let right = height.length - 1;

    // 当左指针小于右指针时,进行循环
    while (left < right) {
        // 计算当前左右指针所指的线段的高度的最小值
        const minHeight = Math.min(height[left], height[right]);
        // 计算当前容器的宽度
        const width = right - left;
        // 计算当前容器的面积
        const area = minHeight * width;
        // 更新最大面积
        maxArea = Math.max(maxArea, area);

        // 移动较短的线段指针,以寻找更高的线段
        if (height[left] < height[right]) {
            left++; // 如果左边的线段较短,则将左指针向右移动
        } else {
            right--; // 如果右边的线段较短或相等,则将右指针向左移动
        }
    }
    // 返回最大面积
    return maxArea;
};

总结

双指针法常用于以下情况:

  • 有序数组/链表:在有序数组或链表中查找特定元素或满足特定条件的组合。
  • 求和问题:如两数之和、三数之和等。
  • 反转问题:如反转字符串、反转链表等。

是一种简单且高效的解决问题的方法,适用于很多类型的问题,包括数组和链表。掌握了双指针法的基本思想和应用场景,能够帮助我们更快地解决问题,并写出高效的算法。