面试专题训练之“双指针”

Posted hdujackyan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试专题训练之“双指针”相关的知识,希望对你有一定的参考价值。

一、需要思考的问题包括以下几点:

双指针是什么,什么时候需要用到双指针

通用的模板是什么

实现过程中需要注意的细节有哪些

常见的双指针题型有哪些

 

 

二、模板整理

 

 

三、专题训练

1.Leetcode283

技术图片
 1 class Solution {
 2 public:
 3     void moveZeroes(vector<int>& nums) {
 4         int n = nums.size();
 5         int j = 0;
 6         for (int i = 0; i < n; i++) {
 7             if (nums[i] != 0) nums[j++] = nums[i];
 8         }
 9         while (j < n) {
10             nums[j++] = 0;
11         }
12     }
13 };
leetcode283

题意:给定一个数组,把 0 移动到末尾的位置,剩下非 0 的数的相对位置保持不变。

题解:相当于有两个指针,都是从头开始。一个指向为 0 的值,一个指向非 0 的值。然后进行交换。

 

2.Leetcode1248

技术图片
class Solution {
public:
    int numberOfSubarrays(vector<int>& nums, int k) {
        vector<int> ind;
        int n = nums.size(), ans = 0;
        ind.push_back(-1);
        for (int i = 0; i < n; i++) {
            if (nums[i] % 2 == 1) ind.push_back(i);
        }
        ind.push_back(n);
        int m = ind.size();
        for (int i = 1; i+k-1 < m-1; i++) {
            int l = ind[i];
            int r = ind[i+k-1];
            int leftGap = l - ind[i-1];
            int rightGap = ind[i+k] - r;
            ans += leftGap * rightGap;
        }
        return ans;
    }
};
leetcode1248(解法1)
技术图片
 1 class Solution {
 2 public:
 3     int numberOfSubarrays(vector<int>& nums, int k) {
 4         return atMost(nums, k) - atMost(nums, k-1);
 5     }
 6     
 7     int atMost(vector<int>& nums, int k) {
 8         int ans = 0, l = 0, n = nums.size();
 9         for (int r = 0; r < n; r++) {
10             k -= (nums[r] & 1);
11             while (k < 0) {
12                 k += (nums[l++] & 1);
13             }
14             ans += (r - l + 1);
15         }
16         return ans;
17     }
18 };
leetcode1248(解法2)

题意:给定一个长度为 n 的数组,求有多少个子数组满足其中包含 k 个奇数。

题解:

解法1:将所有奇数的下标保存起来,目的在于能够快速找到恰好包含 k 个奇数的子数组(/窗口),然后将该窗口向左右延申,但是不能破坏之前所满足的条件,在延申的过程中的窗口都是答案。

解法2:添加函数 atMost 表示对于给定的数组,最多包含 k 个奇数的子数组有多少。这样原问题的答案就变成了 atMost(k) - atMost(k-1)。在统计子数组个数时,设置两个指针,右指针一直往右延申,每次延申一格,然后看当前的子数组 [l, r] 是否满足条件,如果不满足条件,则右移左指针直至符合条件,然后更新答案。

 

3.Leetcode350

技术图片
 1 class Solution {
 2 public:
 3     vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
 4         unordered_map<int, int> mp;
 5         for (int num : nums1) mp[num]++;
 6         vector<int> ans;
 7         int n = nums2.size();
 8         for (int i = 0; i < n; i++) {
 9             int num = nums2[i];
10             if (mp[num] > 0) {
11                 ans.push_back(num);
12                 mp[num]--;
13             }
14         }
15         return ans;
16     }
17 };
leetcode350

题意:给定两个数组,求两个数组的交集

题解:

方法1:用 unordered_map 存一个数组的数据,然后遍历另外一个数组去判断 map 中是否存在那个数。

方法2:先对两个数组进行排序,然后对于每个数组设置指针指向头部,比较并后移。

 

4.Leetcode16

技术图片
 1 class Solution {
 2 public:
 3     int threeSumClosest(vector<int>& nums, int target) {
 4         sort(nums.begin(), nums.end());
 5         int n = nums.size();
 6         int ans = nums[0] + nums[1] + nums[2];
 7         for (int i = 0; i < n-2; i++) {
 8             int j = i+1, k = n-1;
 9             while (j < k) {
10                 int sum = nums[i] + nums[j] + nums[k];
11                 if (abs(target-sum) < abs(target-ans)) ans = sum;
12                 if (sum < target) j++;
13                 else if (sum == target) return sum;
14                 else k--;
15             }
16         }
17         return ans;
18     }
19 };
leetcode16

题意:给定一个数组 nums,从中取出三个数,使得取出的三个数的和最接近给定的 target 值,问该值是多少。

题解:“三指针”,用 for i = [0 ~ n-2] 先确定第一个指针的位置,然后剩下两个指针,分别设定为 l = i+1, r = n-1。在根据其nums[i]+nums[l]+nums[r] 的结果更新答案。

 

5.Leetcode75

技术图片
 1 class Solution {
 2 public:
 3     void sortColors(vector<int>& nums) {
 4         int n = nums.size();
 5         int one, two, three;
 6         one = two = three = 0;
 7         for (int i = 0; i < n; i++) {
 8             if (nums[i] == 0) {
 9                 nums[three++] = 2;
10                 nums[two++] = 1;
11                 nums[one++] = 0;
12             } else if (nums[i] == 1) {
13                 nums[three++] = 2;
14                 nums[two++] = 1;
15             } else {
16                 nums[three++] = 2;
17             }
18         }
19     }
20 };
leetcode75(解法1)
技术图片
 1 class Solution {
 2 public:
 3     void sortColors(vector<int>& nums) {
 4         int n = nums.size();
 5         int i, j, k;
 6         i = j = 0, k = n - 1;
 7         while (j <= k) {
 8             if (nums[j] == 0) swap(nums[i++], nums[j++]);
 9             else if (nums[j] == 1) j++;
10             else swap(nums[j], nums[k--]);
11         }
12     }
13 };
leetcode75(解法2)

题意:给定一个只包含数字 1 2 3 的数组 nums,只遍历一遍实现原地更新。

题解:

1.记值为 0 的数字个数为 a,值为 1 的数字个数为 b,值为 2 的数字个数为 c. 这样可以保证 [0, a) 的位置的值全为 0,[a, a+b) 的位置的值全为 1,[a+b, a+b+c) 的位置的值全为 2。我们用变量 one 代表值 <= 0 的数的个数(/位置),用变量 two 代表值 <= 1 的数的个数,用变量 three 代表值 <= 2 的数的个数。这样每次访问到 nums[i] == 0 时,那么我们需要按照 three -> two -> one 的顺序依次更新各个值。 每次访问到 nums[i] == 1 时,那么我们需要按照 three -> two 的顺序依次更新各个值。 每次访问到 nums[i] == 2 时,那么我们需要按照 three  的顺序依次更新各个值。 这样可以保证先前暂时被写到前面位置的大值被覆盖。

2.“三指针”,用 i 代表值为 0 的数的位置,j 代表值为 1 的数的位置,k 代表值为 2 的数的位置。初始时 i = j = 0, k = n-1. 在整个过程中移动 j 的值,遇到 0,就交换 nums[i++] 和 nums[j++] 确保值 0 一定可以放到前面。遇到 1,则 j++。遇到 2,就交换 nums[j] 和 nums[k--],确保值 2 一定放在最后。即 0 是从开头往后增长,2 是从最后往前增长,而 1 是根据 0 和 2 的位置调整变动的。

 

以上是关于面试专题训练之“双指针”的主要内容,如果未能解决你的问题,请参考以下文章

算法专题(02)双指针(01) 简单LeetCode 977

双指针算法:同向双指针模板以及相关面试题

leetcode双指针专题

《寒假算法集训》(专题十一)双指针

leetcode双指针专题

LeetCode日记——算法双指针专题