LeetCode【算法思想篇】【双指针】

216 阅读6分钟

两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9

所以返回 [0, 1]

解题思路:

  1. 暴力解法
  • 两层for循环,数组的第一个值与第二,三,四...个值相加判断是否等于目标值,由于每个值只能使用一次,所以内存for起始值为 i + 1
var twoSum = function (nums: number[], target: number): number[] {
    if (nums.length < 2) return []
    
    if (nums.length === 2){
        if (nums[0] + nums[1] === target) return [0, 1]
        return []
    }

    for (var i = 0; i < nums.length; i++) {
        for (var j = i + 1; j < nums.length; j++) {
            if (nums[i] + nums[j] === target) {
                return [i, j]
            }
        }
    }
    return []
};

 console.log(twoSum([2, 7], 9))
  1. 哈希表解法
  • 利用对象存储已经循环过的值,比较目标值和当前正在循环的值的差值,如果差值正好在对象中,则找到了两个值
var towSumHash = function (nums: number[], target: number): number[] {
    if (nums.length < 2) return []

    if (nums.length === 2) {
        if (nums[0] + nums[1] === target) return [0, 1]
        return []
    }

    var map = {} // key: 数字  value:index
    var loop = 0 // 循环次数
    var dis = 0 // 差值
    var len = nums.length

    while (loop < len) {
        dis = target - nums[loop]

        if (map[dis] != undefined) {
            return [map[dis], loop]
        }
        map[nums[loop]] = loop
        loop++
    }

    return []
}

console.log(towSumHash([2, 7, 122, 123, 23], 129))

两数平方和

给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a^2 + b^2 = c。

示例:

输入: 5
输出: True
解释: 1 * 1 + 2 * 2 = 5

输入: 3
输出: False

难易度:简单

解题思路:双指针

为什么这道题可以用双指针来做呢?

因为要找到两个数的平方加起来等于target,其实就是相当于在0-target这个数组中找两个数,使得他们的平方相加等于target

所以这里用双指针的方法,左指针代表较小的数,右指针代表较大的数

因为是平方,所以较大的数right,最大可以到right^2 = target
var judgeSquareSum = function(c: number): boolean {
    if(c < 0) return false

    let left  = 0, right = Math.floor(Math.sqrt(c))

    while (left <= right) { // 此题可以取两个相同的数
        const res = left * left + right * right

        if (res == c) {
            return true
        } else if (res < c) {
            left++
        } else {
            right--
        }
    }
    return  false
};

反转字符串的元音字符

编写一个函数,以字符串作为输入,反转该字符串中的元音字母。

示例

输入: "hello"
输出: "holle"

输入: "leetcode"
输出: "leotcede"

难易度:简单

解题方案

本题就是很典型的双指针问题,两个指针向中心移动,如果两个指针都遇到元音字母,则交换位置

如果哪个指针没有遇到元音字母则往下一个目标移动

因为字符串不能直接操作,改变字符,新开一个数组存储结果数组

var reverseVowels = function(s: string): string {
    if (s.length < 2) return s

    const strSet = new Set<String>(['a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'])

    let left = 0, right = s.length - 1
    const res = new Array<string>(s.length)
    while(left <= right) {
        const sLeft = s[left]
        const sRight = s[right]
        if (strSet.has(sLeft) && strSet.has(sRight)) { // 遇到了两个元音字母
            res[left] = sRight
            res[right] = sLeft
            left++
            right--
        } else if (!strSet.has(sLeft)){
            res[left] = sLeft
            left++
        } else{
            res[right] = sRight
            right--
        }
    }
    return res.join('')
};

验证回文字符串

给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。

示例 1:

输入: "aba"
输出: True

输入: "abca"
输出: True
解释: 你可以删除c字符。

难易度:容易

解题思路

回文串可以很简单的用双指针去检测得出结果,只需要首尾两个指针向中间移动,两个指针指向的字符如果不同,那就不是回文串,如果都相同,那么就是回文字符串,这里两个指针不可以相等

但是这道题的问题是 可以删除一个字符

也就是说当遍历到两个指针指向的字符不相等时,可以选择删除一个字符,左边的或者右边的,然后再继续比较(只能删除一次)

// 删除一个字符串之后继续比较
const deleteStr = (str: string, left: number, right: number): boolean => {
    while (left < right) {
        if (str[right] != str[left]) return false
        left++
        right--
    }
    return true
}

var validPalindrome = function (s: string): boolean {
    if (!s) return false
    for (let i = 0, j = s.length - 1; i < j; i++, j--) {
        if (s[i] != s[j]) {
            // 在左边删除一个字符,或者右边删除一个字符继续比较
            return deleteStr(s, i, j - 1) || deleteStr(s, i + 1, j);
        }
    }
    return true;
};

合并两个有序数组

给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。

说明:

  • 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
  • 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

示例:

输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6],       n = 3

输出: [1,2,2,3,5,6]

难易度: 容易

解题思路

既然两个数组是有序数组,那么就利用两个指针,分别指向两个数组,比较两个指针对应的数值

这里采用指针指向最后一个数字的方法,因为顺序遍历的话不好处理nums1的指针变化

比较的结果就是把两个指针指向的更大的数 往 nums1 数组的后面填充

var merge = function(nums1: number[], m:number, nums2: number[], n:number): number[] {
    let i = m - 1, j = n - 1
    let maxIdx = m + n - 1
    while(i >= 0 || j >= 0) {
        if(nums1[i] < nums2[j]) {
            nums1[maxIdx--] = nums2[j--]
        } else if (nums1[i] > nums2[j])  {
           nums1[maxIdx--] = nums1[i--]
           // 两个数组长度不同的情况,有可能已经遍历完一个数组
        } else if (i < 0) {  // nums2 更长
            nums1[maxIdx--] = nums2[j--]
        } else { // num1 更长
            nums1[maxIdx--] = nums1[i--]
        }
    }
    return nums1
};

最长子序列

给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。

如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。

示例

输入: s = "abpcplea", d = ["ale","apple","monkey","plea"]
输出: "apple"

说明:

所有输入的字符串只包含小写字母。
字典的大小不会超过 1000。
所有输入的字符串长度不会超过 1000。

难易度:中等

解题思路

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

function isSubstr(target: string, origin: string): boolean {
    let i = 0, j = 0, len1 = target.length, len2 = origin.length
    // 使用双指针来查询子序列
    while (i < len1 && j < len2) {
        if (target[i] == origin[j]) {
            i++ // 如果两个字符相等,目标字符串的指针才需要后移
        }
        j++
    }
    return i == len1 // 如果目标字符串遍历完成  才算目标字符串是 源字符串的子序列
}

var findLongestWord = function (s: string, d: string[]): string {
    let longestStr = ''
    for (let i = 0, len = d.length; i < len; i++) {
        const len1 = longestStr.length, len2 = d[i].length

        if (len1 > len2 || (len1 == len2 && longestStr.charCodeAt() < d[i].charCodeAt())) {
            // 如果现有的最长字符串长于d[i] 不需要比较
            // 如果两个字符串长度相等  并且现有最长字符串 的 ASCII码 小于 需要比较的字符串(字典序最小)  不需要比较
            continue
        }
        if (isSubstr(d[i], s)) {
            longestStr = d[i]
        }
    }
    return longestStr
};