双指针法(快慢指针和对撞指针)总结

LeetCode题目

  • 27.移除元素
  • 26.删除有序数组中的重复项
  • 283.移动零
  • 844.比较含退格的字符串
  • 977.有序数组的平方
  • 344.反转字符串
  • 剑指 Offer 05. 替换空格
  • 151.反转字符串中的单词
  • 206.反转链表
  • 19.删除链表的倒数第 N 个结点
  • 面试题 02.07. 链表相交
  • 142.环形链表 II
  • 15.三数之和
  • 18.四数之和

简介

  • 双指针法的题目非常多,主要分为快慢指针和对撞指针,通常能够将 O ( n 2 ) O(n^2) O(n2)的时间复杂度,变为 O ( n ) O(n) O(n)的时间复杂度,且空间复杂度为 O ( 1 ) O(1) O(1),因此面试考察概率大
  • 上述链表相关的题目,206、19、02.07、142,可参考链表LeetCode总结

快慢指针

  • 先讲快慢指针。快慢指针要点
    • 快慢指针起点必须一致
    • 让快指针向前遍历,寻找符合条件的值,如果不符合则继续向前遍历
    • 如果符合条件,则赋值到慢指针所在的位置,再让快慢指针向前一步
    • 快指针到尾部,则结束
    • 结束时,慢指针位于符合条件的最后一个值的后一个位置
  • 题目讲解:除链表外,快慢指针可用于上述27、26、283、Offer 05、151,挑有代表性的讲
    • 283.移动零
    class Solution {
    public:
        void moveZeroes(vector<int>& nums)
        {
            auto size = nums.size();
            if (size < 2)
                return;
    
            auto slow = size - size, fast = slow;
            // 快指针到尾部,则结束
            while (fast < size) {
                // 快指针寻找符合条件的值
                if (0 == nums[fast])
                    // 不符合则继续向前遍历
                    ++fast;
                else {
                	// 符合条件,则赋值到慢指针所在的位置,再让快慢指针向前一步
                    nums[slow] = nums[fast];
                    ++slow;
                    ++fast;
                }
            }
            // 结束时,慢指针位于符合条件的最后一个值的后一个位置
            // 用于补零
            while (slow < size) {
                nums[slow] = 0;
                ++slow;
            }
    
            return;
        }
    };
    
    • 剑指 Offer 05. 替换空格
    class Solution {
    public:
        string& replaceSpace(string& s)
        {
            auto size = s.size();
            if (0 == size)
                return s;
    
            auto space_count = size - size;
            for (auto& ch : s) {
                if (' ' == ch)
                    ++space_count;
            }
            s.resize(size + (space_count << 1));
    		
    		// 这题要先扩充空间,然后使用反向快慢指针
    		// 注意!由于指针递减会小于0,所以下面只能用有符号整型
    		// 如果用auto会得到uint64_t,小于0会报overflow错误
            int64_t slow = s.size() - 1, fast = size - 1;
            while (fast >= 0) {
                if (' ' == s[fast]) {
                    s[slow] = '0';
                    s[slow - 1] = '2';
                    s[slow - 2] = '%';
                    slow -= 3;
                    --fast;
                } else {
                    s[slow] = s[fast];
                    --slow;
                    --fast;
                }
            }
    
            return s;
        }
    };
    
    • 151.反转字符串中的单词
    class Solution {
    private:
    	// 对撞指针实现字符串反转
        void reverse(uint64_t left, uint64_t right, string& s)
        {
            while (left < right) {
                swap(s[left], s[right]);
                ++left;
                --right;
            }
            return;
        }
    
    public:
        string& reverseWords(string& s)
        {
            auto size = s.size();
            auto slow = size - size, fast = slow;
            // fast必须先跳过前导空格,否则会导致在前面多补一个空格
            while (' ' == s[fast])
                ++fast;
            // 补空格标志位
            auto has_space = false;
            while (fast < size) {
                if (' ' == s[fast]) {
                    has_space = true;
                    ++fast;
                } else {
                    if (has_space) {
                        s[slow] = ' ';
                        has_space = false;
                        s[slow + 1] = s[fast];
                        slow += 2;
                        ++fast;
                    } else {
                        s[slow] = s[fast];
                        ++slow;
                        ++fast;
                    }
                }
            }
            // 去除多余尾部
            s.resize(slow);
            size = s.size();
    
            // 整体reverse,再逐个单词reverse,即可达成单词反转
            reverse(0, size - 1, s);
            slow = fast = 0;
            while (fast < size) {
                if (' ' != s[fast])
                    ++fast;
                else {
                    reverse(slow, fast - 1, s);
                    ++fast;
                    slow = fast;
                }
            }
            // 需要把最后一个单词也reverse了
            reverse(slow, fast - 1, s);
    
            return s;
        }
    };
    

对撞指针

  • 对撞指针要点
    • 左指针在头,右指针在尾部前一个
    • 判断左右指针是否符合条件,视具体情况,赋值并更新指针
    • 当左右指针“对撞”时,则结束
  • 题目讲解:除链表外,对撞指针可用于上述977、344、15、18,挑有代表性的讲
    • 977.有序数组的平方
    class Solution {
    public:
        vector<int> sortedSquares(vector<int>& _nums)
        {
            // 本题必须拷贝,因为原始值会被覆盖
            vector<int> nums = _nums;
            auto size = nums.size();
            if (0 == size)
                return {};
            if (1 == size)
                return { nums[0] * nums[0] };
    
            auto left = size - size, right = size - 1, target = right;
            auto num = nums[left], num2 = nums[right];
            // 当左右指针“对撞”时,则结束
            while (left < right) {
                // 符合条件的指针,则赋值给结果,然后更新该指针
                if (abs(num2) >= abs(num)) {
                    _nums[target] = num2;
                    _nums[target] *= _nums[target];
                    --target;
    
                    --right;
                    num2 = nums[right];
                } else {
                    _nums[target] = num;
                    _nums[target] *= _nums[target];
                    --target;
    
                    ++left;
                    num = nums[left];
                }
            }
            _nums[target] = num;
            _nums[target] *= _nums[target];
    
            return _nums;
        }
    };
    
    • 18.四数之和
    class Solution {
    private:
        int target;
    
        // int64_t防止两个int相加超过int
        int helper(int a, int b, int c, int d)
        {
            int64_t sum = a;
            sum += b;
            int64_t sum2 = -c;
            sum2 -= d;
    
            sum -= target;
            if (sum > sum2)
                return 2;
            else if (sum < sum2)
                return 1;
            else
                return 0;
        }
    
    public:
        vector<vector<int>> fourSum(vector<int>& nums, int _target)
        {
            target = _target;
            auto size = nums.size();
            if (size < 4)
                return {};
            if (4 == size) {
                if (0 == helper(nums[0], nums[1], nums[2], nums[3]))
                    return { { nums[0], nums[1], nums[2], nums[3] } };
                else
                    return {};
            }
    
            // 本题要采用对撞指针,必须先排序
            sort(nums.begin(), nums.end());
            vector<vector<int>> ans;
            for (auto i = size - size; i < (size - 3); ++i) {
                // 如果当前最小值比target还大,则无论如何也不会有符合条件的结果,break
                if (2 == helper(nums[i], nums[i + 1], nums[i + 2], nums[i + 3]))
                    break;
                // 如果当前最大值比target还小,则进入下一轮才可能有符合条件的结果,continue
                if (1
                    == helper(
                        nums[i], nums[size - 3], nums[size - 2], nums[size - 1]))
                    continue;
                // 去重
                if (0 != i && nums[i] == nums[i - 1])
                    continue;
                for (auto j = i + 1; j < (size - 2); ++j) {
                    if (2 == helper(nums[i], nums[j], nums[j + 1], nums[j + 2]))
                        break;
                    if (1
                        == helper(nums[i], nums[j], nums[size - 2], nums[size - 1]))
                        continue;
                    if ((i + 1) != j && nums[j] == nums[j - 1])
                        continue;
                    
                    // 对撞指针开始
                    auto left = j + 1, right = size - 1;
                    while (left < right) {
                        auto result
                            = helper(nums[i], nums[j], nums[left], nums[right]);
                        if (1 == result)
                            ++left;
                        else if (2 == result)
                            --right;
                        else {
                            ans.emplace_back(vector<int> {
                                nums[i], nums[j], nums[left], nums[right] });
                            // 更新并去重
                            do {
                                ++left;
                            } while (left < right && nums[left] == nums[left - 1]);
                            // 更新并去重
                            do {
                                --right;
                            } while (
                                left < right && nums[right] == nums[right + 1]);
                        }
                    }
                }
            }
    
            return ans;
        }
    };
    

你可能感兴趣的:(数据结构与算法,链表,数据结构,算法)