LeetCode 题解随笔:双指针法

目录

元素和问题

15. 三数之和

18. 四数之和

1237. 找出给定方程的正整数解

链表相关问题

21. 合并两个有序链表

86. 分隔链表

876. 链表的中间结点

160. 相交链表

83. 删除排序链表中的重复元素

167. 两数之和 II - 输入有序数组

 接雨水问题

11.盛最多水的容器

42. 接雨水


元素和问题

15. 三数之和

vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i++) {
            // 排序之后如果第一个元素已经大于零,无解,直接返回
            if (nums[i] > 0) {
                return result;
            }
            //去除i指向的不合适的重复元素
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int left = i + 1;
            int right = nums.size() - 1;
            while (left < right) {
                if (nums[i] + nums[left] + nums[right] > 0) {
                    right--;
                    //去除right指向的不合适的重复元素
                    while (left < right && nums[right] == nums[right + 1]) right--;
                }
                else if (nums[i] + nums[left] + nums[right] < 0) {
                    left++;
                    //去除left指向的不合适的重复元素
                    while (left < right && nums[left] == nums[left - 1]) left++;
                }
                else {
                    result.push_back(vector<int> {nums[i], nums[left], nums[right]});
                    //去除符合要求的重复元素
                    while (left < right && nums[right] == nums[right - 1]) right--;
                    while (left < right && nums[left] == nums[left + 1]) left++;
                    //同时移动双指针
                    right--;
                    left++;
                }
            }
        }
        return result;
    }

该算法复杂度为O(n^2),固定一个元素后,后续元素采用双指针法进行筛选,在迭代的过程中注意去掉重复元素,并注意去重的时机。

  • 对于i元素,由于i-1次循环时已经把i-1后的元素全部考虑过了,所以当nums[i]==nums[i-1]时,应对i进行剔除。【在最外层i循环时,已经把第一个元素为nums[i]的结果全都找齐了!】
  • left和right所指向元素的剔除,则是根据是已经确定了该元素满足/不满足条件。
  • 总结而言:已经完成了条件判断后,再予以剔除。

加速算法的几个关键在于:提前对数组进行排序、确定循环退出的时机。

18. 四数之和

vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i++) {
            //在最外层i循环时,已经把第一个元素为nums[i]的结果全都找齐了
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            for (int j = i + 1; j < nums.size(); j++) {
                //同理,已经把第一个元素和第二个元素都确定了的结果全部找齐了
                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }
                int left = j + 1;
                int right = nums.size() - 1;
                while (left < right) {
                    if (nums[left] + nums[right] > target - (nums[i] + nums[j])) {
                        right--;
                        while (left < right && nums[right] == nums[right + 1])   right--;
                    }
                    else if (nums[left] + nums[right] < target - (nums[i] + nums[j])) {
                        left++;
                        while (left < right && nums[left] == nums[left - 1]) left++;
                    }
                    else {
                        res.push_back(vector<int>{nums[i], nums[j], nums[left], nums[right]});
                        //剔除满足结果的重复元素
                        while (left < right && nums[right] == nums[right - 1])   right--;
                        while (left < right && nums[left] == nums[left + 1]) left++;
                        right--;
                        left++;
                    }
                }
            }
        }
        return res;
    }

本题和三数之和很类似,在外层嵌套一个循环,时间复杂度变为O(n^3)。但照搬上题暗藏以下陷阱:

  1. 不能采用上题中if(nums[i] > target) 就return res的方式,因为负数相加结果会越来越小;
  2. 多个数相加小心溢出,尽量更改代码的写法。例如采用:if (nums[left] + nums[right] > target - (nums[i] + nums[j]))
  3. 移动双指针的过程中,循环中的判断要始终保证(left <right),尤其是剔除重复元素时的while循环。

1237. 找出给定方程的正整数解

vector<vector<int>> findSolution(CustomFunction& customfunction, int z) {
        vector<vector<int>> res;
        // 双指针法:从小到大枚举x,从大到小枚举y
        for (int x = 1, y = 1000; x <= 1000, y >= 1; x++) {
            while (y >= 1 && customfunction.f(x, y) > z) {
                y--;
            }
            if (y >= 1 && customfunction.f(x, y) == z) {
                res.push_back({ x,y });
            }
        }
        return res;
    }

 本题的一种解法是遍历x的同时,对y进行二分查找,从而时间复杂度是O(mlog(n)).

这里使用的双指针法原理为:


 

链表相关问题

21. 合并两个有序链表

ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {       
        // 如果p1或p2为空,直接返回
        if (!list1)    return list2;
        if (!list2)    return list1;
        // 双指针法,在链表1上进行操作
        ListNode* p1 = list1;
        ListNode* p2 = list2;
        ListNode* dummy_head = new ListNode(0);
        ListNode* cur = dummy_head;
        while (p1 && p2) {
            if (p1->val <= p2->val) {
                cur->next = new ListNode(p1->val);
                p1 = p1->next;
            }
            else {
                cur->next = new ListNode(p2->val);
                p2 = p2->next;
            }
            cur = cur->next;
        }
        if (p1)  cur->next = p1;
        if (p2)  cur->next = p2;
        return dummy_head->next;
    }

86. 分隔链表

ListNode* partition(ListNode* head, int x) {
        ListNode* dummy_head_small = new ListNode(0);
        ListNode* dummy_head_big = new ListNode(0);
        ListNode* p_small = dummy_head_small;
        ListNode* p_big = dummy_head_big;
        ListNode* p = head;
        while (p) {
            if (p->val < x) {
                p_small->next = new ListNode(p->val);
                p_small = p_small->next;
            }
            else {
                p_big->next = new ListNode(p->val);
                p_big = p_big->next;
            }
            p = p->next;
        }
        p_small->next = dummy_head_big->next;
        return dummy_head_small->next;
    }

本题和上一题很类似,只不过上一题是合并链表,本题是拆分链表为一大一小。

876. 链表的中间结点

ListNode* middleNode(ListNode* head) {
        ListNode* dummy_head = new ListNode(0);
        dummy_head->next = head;
        ListNode* fast = dummy_head;
        ListNode* slow = dummy_head;
        while (fast->next && fast->next->next) {
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow->next;
    }

快指针走两步,慢指针走一步。

160. 相交链表

ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
        int length_A = 0;
        int length_B = 0;
        ListNode* p_A = headA;
        ListNode* p_B = headB;
        while (p_A) {
            length_A++;
            p_A = p_A->next;
        }
        while (p_B) {
            length_B++;
            p_B = p_B->next;
        }
        int length_delta;
        p_A = headA;
        p_B = headB;
        if (length_A >= length_B) {
            length_delta = length_A - length_B;
            while (length_delta--) {
                p_A = p_A->next;
            }
        }
        else {
            length_delta = length_B - length_A;
            while (length_delta--) {
                p_B = p_B->next;
            }
        }
        while (p_A && p_B) {
            if (p_A == p_B)  return p_A;
            p_A = p_A->next;
            p_B = p_B->next;
        }
        return NULL;
    }

将A、B末端对齐,从长度相等的地方开始找。

83. 删除排序链表中的重复元素

ListNode* deleteDuplicates(ListNode* head) {
        ListNode* slow = head;
        ListNode* fast = head;
        while (fast && fast->next) {
            fast = fast->next;
            if (fast->val == slow->val) {
                slow->next = fast->next;
            }
            else {
                slow = fast;
            }
        }
        return head;
    }

167. 两数之和 II - 输入有序数组

vector<int> twoSum(vector<int>& numbers, int target) {
        int left = 0;
        int right = numbers.size() - 1;
        while (left < right) {
            if (numbers[left] == target - numbers[right]) {
                return { left + 1, right + 1 };
            }
            else if (numbers[left] < target - numbers[right]) {
                left++;
            }
            else {
                right--;
            }
        }
        return { 0,0 };
    }

 接雨水问题

11.盛最多水的容器

    int maxArea(vector<int>& height) {
        // left、right分别记录[0,left]、[right,end]的最大值
        int left = 0;
        int right = height.size() - 1;
        int res = 0;
        while (left < right) {
            res = max(res, (right - left) * min(height[left], height[right]));
            // 移动较低的指针
            if (height[left] < height[right])   left++;
            else    right--;
        }
        return res;
    }

用 left 和 right 两个指针从两端向中心收缩,一边收缩一边计算 [left, right] 之间的矩形面积,取最大的面积值即是答案。值得注意的是双指针的移动方式:选择较低的一边移动,以期让能装的水增多。

42. 接雨水

    int trap(vector<int>& height) {
        // left、right分别记录[0,left]、[right,end]的最大值
        int left = 0, right = height.size() - 1;
        int left_max = 0, right_max = 0;
        int res = 0;
        while (left < right) {
            left_max = max(left_max, height[left]);
            right_max = max(right_max, height[right]);
            // 接雨水:由较低的边决定
            if (left_max < right_max) {
                res += left_max - height[left];
                left++;
            }
            else {
                res += right_max - height[right];
                right--;
            }
        }
        return res;
    }

本题在《单调栈》一章中分别利用单调栈和动态规划进行了求解,但相比之下双指针法更加简单易懂,如下图所示(图片来源:labuladong):

left_max 和 right_max 代表的是 height[0..left] 和 height[right..end] 的最高柱子高度。已经知道 l_max < r_max 时,装的水只和较低的 l_max 之差有关。因此至于 r_max 是不是右边最大的并不重要。即height[i] 能够装的水只和较低的 l_max 之差有关。

【双指针法的动态过程可以参考leetcode题解】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值