双指针详解

665 阅读4分钟

双指针是一种技巧, 不等同于C,C++中的指针, 其是一种算法思想, 没有具体的算法, 通常用在二分查找中。

常见题型

一般分以下三种类型

  • 快慢指针(两个指针步长不同, 多用于滑动窗口)
  • 左右端点指针(两个指针分别指向头尾, 并向中间移动, 多用以二分查找)
  • 固定间距指针(两个指针步长不同, 间距相同, 多用于滑动窗口)

伪代码框架

快慢指针

l = 0
r = 0
while 没有遍历完
  if 一定条件
    l += 1
  r += 1
return 

左右端点指针

l = 0
r = n - 1
while l < r
  if 找到了
    return 找到的值
  if 一定条件1
    l ++
  else if  一定条件2
    r --
return 没找到

固定间距指针

l = 0
r = k
while 没有遍历完
  自定义逻辑
  l += 1
  r += 1
return 合适的值

快慢指针常见题--快慢指针

环形链表

给定一个链表,判断链表中是否有环。

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

来源: leetcode-cn.com/problems/li…

var hasCycle = function(head) {
    if(!head || !head.next) return false;
    let slow = head, fast = head.next;
    while(slow != fast) {
        if(!fast || !fast.next) {
            return false;
        }
        slow = slow.next;
        fast = fast.next.next;
    }
    return true;
};

环形链表 II

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

来源: leetcode-cn.com/problems/li…

var detectCycle = function(head) {
    let slow = head, fast = head;
    while(fast && fast.next) {
        slow = slow.next;
        fast = fast.next.next;
        if(slow === fast) {
            fast = head;
            while(slow != fast) {
                slow = slow.next;
                fast = fast.next;
            }
            return slow;
        }
    }
    return null;
};

最大连续1的个数 III

给定一个由若干 01 组成的数组 A,我们最多可以将 K 个值从 0 变成 1 。返回仅包含 1 的最长(连续)子数组的长度。

输入:A = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释: 
[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6

来源: leetcode-cn.com/problems/ma…

var longestOnes = function(A, K) {
    let left = K, i = 0, j = 0, res = 0;
    while(j < A.length) {
        if(A[j] === 0) {
            if(left === 0) {
                if(A[i] === 0) {
                    left++;
                }
                i++;
            } else {
                left--;
                j++;
            }
        } else {
            j++;
        }
        res = Math.max(res, j - i)
    }
    return res;
};

和相同的二元子数组

在由若干 01 组成的数组 A 中,有多少个和为 S非空子数组。

输入:A = [1,0,1,0,1], S = 2
输出:4
解释:
如下面黑体所示,有 4 个满足题目要求的子数组:
[1,0,1,0,1]
[1,0,1,0,1]
[1,0,1,0,1]
[1,0,1,0,1]

来源: leetcode-cn.com/problems/bi…

var numSubarraysWithSum = function(A, S) {
    let l = r = 0, sum = 0, count = 0;
    while(r < A.length) {
        let c = A[r];
        sum += c;
        while(sum > S) {
            let d = A[l];
            sum -= d;
            l++;
        }
        if(sum === S) {
            let tmp = sum, j = l;
            while(j <= r && tmp === S) {
                count++;
                tmp -= A[j];
                j++
            }
        }
        r++;
    }
    return count;
};

 两个数组的交集

给定两个数组,编写一个函数来计算它们的交集。

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]

來源: leetcode-cn.com/problems/in…

var intersection = function(nums1, nums2) {    nums1.sort((a, b) => a - b);    nums2.sort((a, b) => a - b);    let i = 0, j = 0;    let res = new Set();    while(i < nums1.length && j < nums2.length) {        if(nums1[i] < nums2[j]) {            i++;        } else if(nums1[i] > nums2[j]) {            j++;        } else {            res.add(nums1[i]);            i++;            j++;        }    }    return [...res];};

两个数组的交集 II

给定两个数组,编写一个函数来计算它们的交集。

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]

來源: leetcode-cn.com/problems/in…

var intersect = function(nums1, nums2) {
    nums1 = nums1.sort((a, b) => a-b);
    nums2 = nums2.sort((a, b) => a - b);
    let i = 0, j = 0;
    let res = [];
    while(i < nums1.length && j < nums2.length) {
        if(nums1[i] < nums2[j]) {
            i++;
        } else if(nums1[i] > nums2[j]) {
            j++;
        } else {
            res.push(nums1[i]);
            i++;
            j++;
        }
    }
    return res;
};

数组中的 k-diff 数对

给定一个整数数组和一个整数 **k**,你需要在数组里找到不同的 k-diff 数对,并返回不同的 k-diff 数对 的数目。

输入:nums = [3, 1, 4, 1, 5], k = 2
输出:2
解释:数组中有两个 2-diff 数对, (1, 3) 和 (3, 5)。
尽管数组中有两个1,但我们只应返回不同的数对的数量。

來源: leetcode-cn.com/problems/k-…

var findPairs = function(nums, k) {
    let count = 0;
    nums = nums.sort((a, b) => a-b);
    let left = 0, right = 1, len = nums.length;
    while(right < len) {
        let diff = nums[right] - nums[left];
        if(diff == k) {
            count++;
            while(nums[right + 1] == nums[right]) {
                right++;
            }
            while(nums[left + 1] == nums[left]) {
                left++;
            }
            left++;
            right = left + 1
        } else if(diff > k) {
            left++;
            if(left == right) {
                right = left + 1
            }
        } else {
            right++;
        }
    }
    return count;
};

快慢指针常见题--左右端点指针

左右断点指针多用于二分查找, 详情请见我的另一篇文章- 二分查找详解juejin.cn/post/688746…

三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。 注意:答案中不可以包含重复的三元组。 

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

来源: leetcode-cn.com/problems/3s…

var threeSum = function(nums) {
    if(nums.length < 3) return [];
    const list = [];
    nums.sort((a, b) => a - b);
    for (let i = 0; i < nums.length; i++) {
        if(nums[i] > 0) break;
        if(i > 0 && nums[i] === nums[i - 1]) continue;
        let left = i + 1, right = nums.length - 1;
        while(left < right) {
            if(nums[left] + nums[right] + nums[i] === 0) {
                list.push([nums[left], nums[right], nums[i]])
                while(nums[left] === nums[left + 1]) {
                    left++;
                }
                left++;
                while(nums[right] === nums[right - 1]) {
                    right--;
                }
                right--;
            } else if(nums[left] + nums[right] + nums[i] > 0) {
                right--;
            } else {
                left++;
            }
        }
    }
    return list;
};

三数之和的多种可能

给定一个整数数组 A,以及一个整数 target 作为目标值,返回满足 i < j < k 且 A[i] + A[j] + A[k] == target 的元组 i, j, k 的数量。 由于结果会非常大,请返回 结果除以 10^9 + 7 的余数。  

输入:A = [1,1,2,2,3,3,4,4,5,5], target = 8
输出:20
解释:
按值枚举(A[i]A[j]A[k]):
(1, 2, 5) 出现 8 次;
(1, 3, 4) 出现 8 次;
(2, 2, 4) 出现 2 次;
(2, 3, 3) 出现 2 次。

来源: leetcode-cn.com/problems/3s…

var threeSumMulti = function(A, target) {
    A.sort((a, b) => a - b)
    let res = 0;
    let len = A.length;
    for (let i = 0; i < len - 2; i++) {
        let l = i + 1, r = len - 1;
        while(l < r) {
            let total = A[i] + A[l] + A[r];
            if(total === target) {
                if(A[l] === A[r]) {
                    res += Math.floor((r - l + 1) * (r - l) / 2);
                    break;
                } else {
                    let tmp = 1;
                    while(A[l] === A[++l]) tmp++;
                    res += tmp;
                    while(A[r] === A[--r]) res += tmp;
                }
            } else if(total > target) {
                r--;
            } else {
                l++;
            }
        }
    }
    return res % (1e9 + 7)
};

最接近的三数之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

来源: leetcode-cn.com/problems/3s…

var threeSumClosest = function(nums, target) {
    nums.sort((a, b) => a - b);
    let ans = nums[0] + nums[1] + nums[2];
    for (let i = 0; i < nums.length; i++) {
        let l = i + 1, r = nums.length - 1;
        while(l < r) {
            let sum = nums[i] + nums[l] + nums[r];
            if(Math.abs(target - sum) < Math.abs(target - ans)) {
                ans = sum;
            }
            if(sum > target) {
                r--;
            } else if(sum < target)  {
                l++;
            } else {
                return ans;
            }
        }
    }
    return ans;
};

四数之和

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。 注意: 答案中不可以包含重复的四元组。  

给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。

满足要求的四元组集合为:
[
  [-1,  0, 0, 1],
  [-2, -1, 1, 2],
  [-2,  0, 0, 2]
]

来源: leetcode-cn.com/problems/4s…

var fourSum = function(nums, target) {
    let res = [];
    if(nums.length < 4) return res;
    nums.sort((a, b) => a - b);
    for (let i = 0; i < nums.length; i++) {
        if(i > 0 && nums[i] === nums[i - 1]) {
            continue;
        }
        for (let j = i + 1; j < nums.length; j++) {
            if(j > i + 1 && nums[j] === nums[j - 1]) {
                continue;
            }
            let l = j + 1, r = nums.length - 1;
            while(l < r) {
                let sum = nums[i] + nums[j] + nums[l] + nums[r];
                if(sum === target) {
                    res.push([nums[i], nums[j], nums[l], nums[r]]);
                    while(l < r && nums[l] == nums[l + 1]) {
                        l++;
                    }
                    while(l < r && nums[r] === nums[r - 1]) {
                        r--;
                    }
                    l++;
                    r--;
                } else if (sum < target) {
                    l++;
                } else {
                    r--;
                }
            }
        }
    }
    return res;
};

有序数组的平方

给定一个按非递减顺序排序的整数数组 A,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。

输入:[-4,-1,0,3,10]
输出:[0,1,9,16,100]

来源: leetcode-cn.com/problems/sq…

var sortedSquares = function(A) {
    let i = 0, arr = [], j = A.length - 1, z = A.length - 1;
    while(z >= 0) {
        let l = A[i] * A[i];
        let r = A[j] * A[j];
        if(l > r) {
            arr[z] = l;
            i++;
        } else {
            arr[z] = r;
            j--;
        }
        z--;
    }
    return arr;
};

救生艇

i 个人的体重为 people[i],每艘船可以承载的最大重量为 limit。每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit。返回载到每一个人所需的最小船数。(保证每个人都能被船载)。

输入:people = [1,2], limit = 3
输出:1
解释:1 艘船载 (1, 2)

输入:people = [3,5,3,4], limit = 5
输出:4
解释:4 艘船分别载 (3), (3), (4), (5)

来源: leetcode-cn.com/problems/bo…

var numRescueBoats = function(people, limit) {
    let res = 0;
    let right = people.length - 1, left = 0;
    people.sort((a, b) => a - b)
    while(left <= right) {
        if(left === right) {
            res++; // 只剩下最后一个, 直接一个走,结束
            break;
        } if(people[left] + people[right] > limit) {
            res++;
            right--; // 先载最重的,而且最小的也无法一起,那么最重的要单独走
        } else {
            res++;
            right--;  // 最重的和最轻的一起走
            left++;
        }
    }
    return res;
};

验证回文串

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

输入: "A man, a plan, a canal: Panama"
输出: true

来源: leetcode-cn.com/problems/va…

var isPalindrome = function(s) {
    s = s.replace(/[^0-9a-zA-Z]/g, '').toLowerCase();
    let left = 0, right = s.length - 1;
    while(left < right) {
        if(s[left] !== s[right]) {
            return false;
        }
        left++;
        right--;
    }
    return true;
};

接雨水

给定n个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6

来源: leetcode-cn.com/problems/tr…

var trap = function(height) {
    let n = height.length, l = 0, r = n -1;
    let ans = 0;
    let l_max = height[0], r_max = height[n - 1];
    while(l <= r) {
        l_max = Math.max(l_max, height[l]);
        r_max = Math.max(r_max, height[r]);
        if(l_max < r_max) {
            ans += l_max - height[l];
            l++;
        } else {
            ans += r_max - height[r];
            r--;
        }
    }
    return ans;
};

反转字符串中的元音字母

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

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

来源: leetcode-cn.com/problems/re…

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

 盛最多水的容器

给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

输入:[1,8,6,2,5,4,8,3,7]
输出:49

 來源: leetcode-cn.com/problems/co…

var maxArea = function(height) {
    let i = 0, j = height.length - 1, max = 0, tmp;
    while(i < j) {
        if(height[i] < height[j]) {
            tmp = (j - i) *  height[i];
            i++;
            max = Math.max(max, tmp);
        } else {
            tmp = (j - i) *  height[j];
            j--;
            max = Math.max(max, tmp);
        }
    }
    return max;
};

二维数组中的查找

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数  

[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]
给定 target = 5,返回 true

來源: leetcode-cn.com/problems/er…

var findNumberIn2DArray = function(matrix, target) {
    if(matrix.length === 0) return false;
    let row = matrix.length, col = matrix[0].length;
    for (let i = 0, j = col - 1; i >= 0 && i < row && j >= 0 && j < col;) {
        if(matrix[i][j] > target) {
            j--;
        } else if(matrix[i][j] < target) {
            i++;
        } else {
            return true;
        }
    }
    return false;
};

颜色分类

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。 此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。  

输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

来源:leetcode-cn.com/problems/so…

var sortColors = function(nums) {
    let i = 0, l = 0, r = nums.length - 1;
    while(i <= r) {
        if(nums[i] === 0) {
            swap(nums, i++, l++)
        } else if (nums[i] === 1) {
            i++;
        } else {
            swap(nums, i, r--);
        }
    }

    function swap(nums, i, j) {
        let tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
};

部分排序

给定一个整数数组,编写一个函数,找出索引m和n,只要将索引区间[m,n]的元素排好序,整个数组就是有序的。注意:n-m尽量最小,也就是说,找出符合条件的最短序列。函数返回值为[m,n],若不存在这样的m和n(例如整个数组是有序的),请返回[-1,-1]。  

输入: [1,2,4,7,10,11,7,12,6,7,16,18,19]
输出: [3,9]

来源:leetcode-cn.com/problems/su…

var subSort = function(array) {
    if(array.length === 0) return [-1, -1];
    let max = array[0];
    let r = -1;
    for (let i = 1; i < array.length; i++) {
        if(array[i] >= max) {
            max = array[i]
        } else {
            r = i;
        }
    }
    if(r === -1) return [-1, -1];
    let min = array[array.length - 1];
    let l = -1;
    for (let i = array.length - 2; i >= 0; i--) {
        if(array[i] <= min) {
            min = array[i]
        } else {
            l = i;
        }
    }
    return [l, r]
};

快慢指针常见题--固定间距指针

定长子串中元音的最大数目

给你字符串 s 和整数 k 。 请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。 英文中的 元音字母 为(a, e, i, o, u)。 

输入:s = "abciiidef", k = 3
输出:3
解释:子字符串 "iii" 包含 3 个元音字母。

来源: leetcode-cn.com/problems/ma…

var maxVowels = function(s, k) {
    const strs = new Set(['a', 'e', 'i', 'o', 'u'])
    let count = 0,
        l = 0,
        r = 0
    while (r < k) {
        strs.has(s[r]) && count++
        r++
    }
    let max = count
    while (r < s.length) {
        strs.has(s[r]) && count++
        strs.has(s[l]) && count--
        l++
        r++
        max = Math.max(max, count)
    }
    return max;
};

 乘积小于K的子数组

给定一个正整数数组 nums。找出该数组内乘积小于 k 的连续的子数组的个数。

输入: nums = [10,5,2,6], k = 100
输出: 8
解释: 8个乘积小于100的子数组分别为: [10], [5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于100的子数组。

来源: leetcode-cn.com/problems/su…

var numSubarrayProductLessThanK = function(nums, k) {
    if(k <= 1) return 0;
    let prod = 1, ans = left = right = 0;
    while(right < nums.length) {
        prod *= nums[right];
        while(prod >= k) {
            prod = prod / nums[left++]
        }
        ans += right - left + 1;
        right++;
    }
    return ans
};

最小差

给定两个整数数组ab,计算具有最小差绝对值的一对数值(每个数组中取一个值),并返回该对数值的差

输入:{1, 3, 15, 11, 2}, {23, 127, 235, 19, 8}
输出: 3,即数值对(11, 8)

来源: leetcode-cn.com/problems/sm…

var smallestDifference = function(a, b) {
    a = a.sort((a, b) => a-b);
    b = b.sort((a, b) => a-b);
    let m = a.length, n = b.length;
    let i = 0, j = 0;
    let ans = Infinity;
    while(i < m && j < n) {
        if(a[i] === b[j]) {
            return 0;
        } else if(a[i] < b[j]) {
            ans = Math.min(ans, b[j] - a[i]);
            i++;
        } else {
            ans = Math.min(ans, a[i] - b[j]);
            j++;
        }
    }
    return ans;
};