js算法-双指针

1,039 阅读5分钟

一. 什么是双指针

双指针指的是在遍历对象的过程时不是普通的使用单个指针进行遍历,而是使用两个相同方向或者对向方向的指针进行遍历,在遍历过程中根据某种限制条件进行筛选,从而提升代码处理速度。

二. 用法

  1. 对向指针

对向指针是指在有序数组中,将指向最左侧的索引定义为左指针(left),最右侧的定义为右指针(right),然后从两头向中间进行数组遍历。

对向指针适用于有序数组,也就是说当你遇到题目给定有序数组时,应该第一时间想到用对向指针解题。

1)对向指针

模板:

// 双指针(双向)
var list = [];
var i = 0, j = list.length;
while (i < j) {
  if (/*条件01成立*/) {
    i++;
  } else if (/*条件02成立*/) {
    j++;
  } else {
    // 输出第一张成立
    i++, j++;
  }
}

Eg. 求解数组中和为target的索引(index)

var list_data = [2, 4, 8, 11, 23, 65, 120, 234, 565, 1288, 2866];
var target = 1292;
var left = 0, right = list_data.length;
var res_data = [];
while (left < right) {
  if (list_data[left] + list_data[right] > target) {
    right--;
  } else if (list_data[left] + list_data[right] < target) {
    left++;
  } else {
    res_data.push({ left, right });
    left++;
  }
}
console.log("res_data", res_data);

2)同向指针

// 双指针(同向)
var list = [];
var a = 0;
for (var i = 0; i < list.length; i++) {
  if (/*条件成立*/) {
    for (var j = a; j < i; j++) {
      // 具体实现
    }
    // 将指针左端点向右移动
    a = i + 1;// 这里条件非法死循环
  }
}

Eg. JavaScript库函数Split()封装

String.prototype.split_test = function (ts) {
  var res = [];
  var a = 0;
  for (var i = 0; i < this.length; i++) {
    if (this[i] == ts) {
      var temp = "";
      for (var j = a; j < i; j++) {
        temp = temp + this[j];
      }
      res.push(temp);
      a = i + 1;
    }
  }
  return res;
}

三. 题目讲解

  1. 两数之和

题目描述:

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

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

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

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

常规解法:

解法思路:

  1. 从数组第一位开始遍历 i
  2. 从下一位开始遍历 j
  3. 判断 i+j是否等于target,等于则返回 索引,跳出循环

双指针解法:

解法思路:

  1. 首先对数组从小到大进行排序
  2. 从数组首尾位置定义两个指针
  3. 如果首指针 !==尾指针 则循环遍历,取对应位置值
  4. 判断值相加是否等于target,是则跳出循环返回结果,否则继续
  5. 相加大于目标值则 右指针减,小于则左指针加

由于本题确定有唯一答案,不考虑去重,得到匹配结果就可以跳出循环

function twoSum(nums: number[], target: number): number[] {
  const nextNums = nums.map((val, idx) => ({
    val,
    idx,
  }));
  nextNums.sort((a, b) => {
    return a.val - b.val;
  });
  const n = nums.length;
  let [left, right] = [0, n - 1];

  while (left < right) {
    const tmp = nextNums[left].val + nextNums[right].val;

    if (tmp > target) {
      right--;
    } else if (tmp < target) {
      left++;
    } else {
      break;
    }
  }
  return [nextNums[left].idx, nextNums[right].idx];
}
  1. 移动0到数组末尾

题目描述:

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

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

示例 1:

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

示例 2:

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

常规解法:

解法思路:

  1. 遍历数组,遇到0则 push 到数组末尾
  2. splice截取当前操作的元素
var moveZeroes = function(nums) {
  const length = nums.length
  if(length === 0) return
  
  let zeroLength = 0
  
  for(let i = 0; i < length - zeroLength; i++){
   if(nums[i] === 0){
      nums.push(0)
      nums.splice(i,1) //本身就是O(n)
      i-- //截取了一个元素,i要递减,否则连续 0 就会有错
      zeroLength++ //累加 0 的长度
   }
  }
};

结果:

双指针解法:

解法思路:

  • 定义 j 指向第一个0,i指向j后面的第一个非0
  • 交换 i 和 j 的值,继续向后移动
var moveZeroes = function(nums) {
 const len = nums.length
 if(len === 0) return
 let i //指向j后面第一个非0
 let j = -1 //指向第一个0
 for(i = 0; i < len; i++){
  if(nums[i] === 0){
    //第一个0
    if(j < 0){
      j = i
    }
  }
  if(nums[i] !== 0 && j >= 0){
    //交换
    const n = nums[i]
    nums[i] = nums[j]
    nums[j] = n
    j++
  }
 }
};

结果:

两种解法对比明显能看出,双指针的解法运行速度比常规遍历的解法要快4倍左右

  1. 三数之和(中等难度,建议双指针解法)

题目描述:

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != 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 。

解题思路:

  1. 将数组从小到大sort一下排序
  2. 令i从第一位开始固定,从i往后设置两个指针(头指针与尾指针),使这两个指针的和相加恰好为i所对应的相反数

注意:如果i为正数的话直接跳出循环,因为正数相加什么都不可能为0

  1. 操作i指针时对i进行查重处理,即如果第i与第i-1个对应的数字相等,则continue
  2. left与right指针开始操作,当三者相加为0时,将三个数拍成数组的形式,添加到一个空数组中(结果数组)
  3. 继续向下操作,查重,再次遇到相同的就需要left++或者right--
  4. 重复操作

指向下一个数字重新开始相加比较是否为0,往返操作。 当三者相加大于0时,则左指针向前移动一位(以减小sum的总值)。当三者相加小于0时,则右指针向后移动一位(以增加sum的总值)。最后返回我们的结果数组

双指针解法代码:

var threeSum = function (nums) {
    var arr = [] // 结果数组
    nums.sort((a, b) => a - b) // 数组从小到大排序
    for (let i = 0; i < nums.length; i++) {
        // 头指针大于0直接结束循环,因为从大到小排列数组,头指针大于0的话 左右指针也大于0,相加不可能为0
        if (nums[i] > 0) break;
        // 去重 头指针与前一位相同则跳过
        if (i > 0 && nums[i] == nums[i - 1]) continue;
        // 定义左右指针 ,左指针为头指针下一位,右指针从数组尾部倒序
        var left = i + 1, right = nums.length - 1
        while (left < right) {
            const sum = nums[i] + nums[left] + nums[right]
            if (sum == 0) {
                arr.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 < 0) left++;
            else if (sum > 0) right--;
        }
    }
    return arr;
};

暴力解法:(大家自行研究,看看如果不通过双指针方式的话,能不能解出这个题目)

四. 相关文档

1. ## leetcode相关题目推荐