Skip to content

双指针

loop detection

  • Method 1: hash table

    unordered_set<node*> visited;
    
  • Method 2: fast-slow pointer

    O(1) Space.

    fast一次走两格,slow一次走一格;两指针相遇则说明有环。

    若要找到环头,则相遇之后再从起点跑一个指针,此指针与slow指针相遇时即到达环头。

有序数组中的搜索

两数之和

给定一个数组,求其中所有不重复的两个数,使其和为特定值。

// O(n), lr pointer
vector<vector<int>> twoSumTarget(vector<int>& nums, int target) {
    sort(nums.begin(), nums.end());

    vector<vector<int>> res;

    int l = 0, r = nums.size() - 1;
    while (l < r) {
        int sum = nums[l] + nums[r];
        int left = nums[l], right = nums[r];
        if (sum < target) {
            while (l < r && nums[l] == left) l++;
        } else if (sum > target) {
            while (l < r && nums[r] == right) r--;
        } else {
            res.push_back({left, right});
            while (l < r && nums[l] == left) l++;
            while (l < r && nums[r] == right) r--;
        }
    }

    return res;
}


// 如果只需要求一组这样的数据(的下标),也可以使用hash
// 但hash法求所有时不好去重复,故不推荐。
// O(n), hash
vector<int> twoSum(vector<int>& nums, int target) {
    unordered_map<int, int> v;
    for (int i = 0; i < nums.size(); i++) {
        int x = nums[i];
        if (v.count(target - x)) return vector<int>{i, v[target - x]};
        else v[x] = i;
    }
    return vector<int>{-1, -1}; // never reach here
}
三数之和

给定一个数组,求其中所有不重复的三个数,使其和为0。

// O(n^2), lr pointer
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n = nums.size();
        sort(nums.begin(), nums.end());
        vector<vector<int>> ans;
        // 枚举 a
        for (int first = 0; first < n; ++first) {
            // 需要和上一次枚举的数不相同
            if (first > 0 && nums[first] == nums[first - 1]) {
                continue;
            }
            // c 对应的指针初始指向数组的最右端
            int third = n - 1;
            int target = -nums[first];
            // 枚举 b
            for (int second = first + 1; second < n; ++second) {
                // 需要和上一次枚举的数不相同
                if (second > first + 1 && nums[second] == nums[second - 1]) {
                    continue;
                }
                // 需要保证 b 的指针在 c 的指针的左侧
                while (second < third && nums[second] + nums[third] > target) {
                    --third;
                }
                // 如果指针重合,随着 b 后续的增加
                // 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
                if (second == third) {
                    break;
                }
                if (nums[second] + nums[third] == target) {
                    ans.push_back({nums[first], nums[second], nums[third]});
                }
            }
        }
        return ans;
    }
};

合法三角形个数

给定一个边长数组,求其中可以组成合法三角形的三元组个数。

暴力枚举三条边\(O(N^3)\);排序后枚举两条边,二分第三条边\(O(N^2\log N)\)

排序后枚举一条边,双指针处理另外两条边\(O(N^2)\)

class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int ans = 0;
        for (int i = 0; i < nums.size(); i++) {
            // double pointer (j, k)
            for (int j = i + 1, k = i + 1; j < nums.size(); j++) {
                // j and k both only takes at most N ++step
                while (k + 1 < nums.size() && nums[k + 1] < nums[i] + nums[j]) k++;
                ans += max(k - j, 0);
            }
        }
        return ans;
    }
};

链表相交点

双指针换家。证明很简单,但是方法想不到啊。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (headA == nullptr || headB == nullptr) return nullptr;
        ListNode *a = headA, *b = headB;
        while (a != b) {
            if (a) a = a->next; else a = headB;
            if (b) b = b->next; else b = headA;
        }
        return a;
    }
};

救生艇的最少数量

并不是动规,问的是袋子的最少数量。

应该用贪心法做,每次尽可能填满袋子(先塞大的,然后用小的开始填)。

进一步可以用双指针实现这个过程。

每艘船载人量上限为2:

class Solution {
public:
    int numRescueBoats(vector<int>& people, int limit) {
        // greedy, double pointer
        sort(people.begin(), people.end());
        int l = 0, r = people.size() - 1;
        int ans = 0;
        while (l <= r) {
            int rem = limit - people[r];
            if (l < r && rem >= people[l]) {
                rem -= people[l];
                l++;
            }
            ans++;
            r--;
        }
        return ans;
    }
};

每艘船载人量任意的情况:

class Solution {
public:
    int numRescueBoats(vector<int>& people, int limit) {
        // greedy, double pointer
        sort(people.begin(), people.end());
        int l = 0, r = people.size() - 1;
        int ans = 0;
        while (l <= r) {
            int rem = limit - people[r];
            // change to while !
            while (l < r && rem >= people[l]) {
                rem -= people[l];
                l++;
            }
            ans++;
            r--;
        }
        return ans;
    }
};

和为s的连续正整数序列

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

两个指针都从首部开始。

class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
        int l = 1, r = 2;
        vector<vector<int>> ans;
        while (l < r && r <= (target + 1) / 2) {
            int s = (l + r) * (r - l + 1) / 2;
            if (s == target) {
                vector<int> tmp;
                for (int i = l; i <= r; i++) tmp.push_back(i);
                ans.push_back(tmp);
                l++; // don't forget this.
            }
            else if (s < target) r++;
            else l++;
        }
        return ans;
    }
};