双指针

230 阅读3分钟

1. 有序数组的 Two Sum

LeetCode 167

Input: numbers 有序数组,target 需要求和的值 Output: index1,index2 两个值的下标,注意从1开始

题目描述:在有序数组中寻找两个数,使之和为 target

使用双指针,一个指针指向较小的元素,一个指针指向较大的元素。小指针从头开始往后遍历,大指针从尾开始往前遍历。

  • 如果两个指针指向的元素和为 target 则得到了要求的结果
  • 如果 sum > target 说明结果大了,大指针需要前移
  • 如果 sum < target 说明结果小了,小指针需要后移
/**
 * @param {number[]} numbers
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function (numbers, target) {
  let p1 = 0, p2 = numbers.length - 1
  while (p1 < p2) {
    if (numbers[p1] + numbers[p2] === target) return [p1 + 1, p2 + 1]
    else if (numbers[p1] + numbers[p2] < target) p1++
    else p2--
  }
};

2. 两数平方和

LeetCode 633

Input: 5 Output: True

题目描述:判断一个数是否是两个数的平方和

var judgeSquareSum = function (c) {
  let p1 = 0, p2 = parseInt(Math.sqrt(c))
  while (p1 <= p2) {
    if (p1 * p1 + p2 * p2 === c) return true
    else if (p1 * p1 + p2 * p2 > c) p2--
    else p1++
  }
  return false
};

3. 反转字符串中的元音字母

LeetCode 345

Input: "LeetCode" Output: "LeoCede"

解题思路:使用双指针指向待反转的两个元音字母,一个指针从头遍历,一个指针从尾遍历。

var reverseVowels = function (s) {
  const VOWELS = { 'a': 1, 'e': 1, 'i': 1, 'o': 1, 'u': 1, 'A': 1, 'E': 1, 'I': 1, 'O': 1, 'U': 1 }
  const arr = s.split('')
  let i = 0, j = arr.length - 1
  while (i < j) {
    if (VOWELS[arr[i]] && VOWELS[arr[j]]) {
      [arr[i], arr[j]] = [arr[j], arr[i]]
      i++
      j--
    } else if (VOWELS[arr[i]]) j--
    else i++
  }
  return arr.join('')
};

4. 回文字符串

LeetCode 680

Input: "abca" Output: true

题目描述:可以删除一个字符,判断是否可以构成回文串

var validPalindrome = function (s) {
  let i = 0, j = s.length-1
  while (i < j) {
    if (s[i] !== s[j]) {
      return judge(s, i + 1, j) || judge(s, i, j - 1)
    } else {
      i++
      j--
    }
  }
  return true
};

const judge = (s, i, j) => {
  while (i < j) {
    if (s[i] !== s[j]) {
      return false
    }
    i++
    j--
  }
  return true
}

5. 归并两个有序数组

LeetCode 88

Input: nums1 = [1,2,3,0,0,0], m = 3 nums2 = [4,5,6], n = 3

Output: [1,2,3,4,5,6]

题目描述:把结果归并在第一个数组上

需要从尾部开始遍历,不然结果会覆盖未进行归并的值。

var merge = function (nums1, m, nums2, n) {
  let p1 = m - 1, p2 = n - 1, mergeIndex = m + n - 1
  while (p1 >= 0 || p2 >= 0) {
    if (p1 < 0) {
      nums1[mergeIndex--] = nums2[p2--]
    } else if (p2 < 0) {
      nums1[mergeIndex--] = nums1[p1--]
    } else if (nums2[p2] >= nums1[p1]) {
      nums1[mergeIndex--] = nums2[p2--]
    } else {
      nums1[mergeIndex--] = nums1[p1--]
    }
  }
};

6. 判断链表是否存在环

LeetCode 141

使用双指针,一个指针一次移动一个节点,一个一次移动两个节点,如果存在环,则两个指针一定会相遇。 PS: 起始时不能使两个指针指向同一个节点,一前一后不会出问题。

var hasCycle = function (head) {
  if (head === null) return false
  let p1 = head, p2 = head.next
  while (p1 !== null && p2 !== null && p2.next !== null) {
    if (p1 === p2) return true
    p1 = p1.next
    p2 = p2.next.next
  }
  return false
};

7. 最长子序列

LeetCode 524

Input: s = "abpcplea", d = ["ale","apple","monkey","plea"]

Output: "apple"

题目描述:删除 s 中的一些字符,使之能构成 d 列表中的一个字符串,找出能构成的最长字符串,如果字符串长度相同,则取字典序最小的。

通过删除 s 中的一些字符获得字符串 t,可以认为 t 是 s 的子串序列,我们可以用双指针判断一个字符串是否是另一个字符串的子序列

var findLongestWord = function (s, d) {
  let longestCode = ''

  for (let cur of d) {
    const l1 = longestCode.length, l2 = cur.length
    if (l1 > l2 || (l1 === l2 && cur > longestCode))
      continue
    if (isSubStr(s, cur)) {
      longestCode = cur
    }
  }
  return longestCode
};

const isSubStr = (s, target) => {
  let i = 0, j = 0
  while (i < s.length && j < target.length) {
    if (s[i] === target[j]) {
      j++
    }
    i++
  }
  return j === target.length
}