• LeetCode力扣刷题——玩转双指针


    双指针


    一、算法解释

            双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。也可以延伸到多
    个数组的多个指针。
            若两个指针指向同一数组,遍历方向相同且不会相交,则也称为滑动窗口(两个指针包围的
    区域即为当前的窗口),经常用于区间搜索。
            若两个指针指向同一数组,但是遍历方向相反,则可以用来进行搜索,待搜索的数组往往是
    排好序的。
            对于 C++ 语言,指针还可以玩出很多新的花样。一些常见的关于指针的操作如下:
    指针与常量
    1. int x;
    2. int * p1 = &x; // 指针可以被修改,值也可以被修改
    3. const int * p2 = &x; // 指针可以被修改,值不可以被修改(const int)
    4. int * const p3 = &x; // 指针不可以被修改(* const),值可以被修改
    5. const int * const p4 = &x; // 指针不可以被修改,值也不可以被修改
    指针函数与函数指针
    1. // addition是指针函数,一个返回类型是指针的函数
    2. int* addition(int a, int b) {
    3. int* sum = new int(a + b);
    4. return sum;
    5. }
    6. int subtraction(int a, int b) {
    7. return a - b;
    8. }
    9. int operation(int x, int y, int (*func)(int, int)) {
    10. return (*func)(x,y);
    11. }
    12. // minus是函数指针,指向函数的指针
    13. int (*minus)(int, int) = subtraction;
    14. int* m = addition(1, 2);
    15. int n = operation(3, *m, minus);

    二、经典问题 

    1. Tow Sum

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

    167. Two Sum II - Input Array Is Sorted

            给你一个下标从 1 开始的整数数组 numbers ,该数组已按非递减顺序排列  ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。

            以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。你所设计的解决方案必须只使用常量级的额外空间。

            因为数组已经排好序,我们可以采用方向相反的双指针来寻找这两个数字,一个初始指向最 小的元素,即数组最左边,向右遍历;一个初始指向最大的元素,即数组最右边,向左遍历。
            如果两个指针指向元素的和等于给定值,那么它们就是我们要的结果。如果两个指针指向元 素的和小于给定值,我们把左边的指针右移一位,使得当前的和增加一点。如果两个指针指向元 素的和大于给定值,我们把右边的指针左移一位,使得当前的和减少一点。
    1. class Solution {
    2. public:
    3. vector<int> twoSum(vector<int>& numbers, int target) {
    4. int l = 0,r = numbers.size() - 1;
    5. int sum;
    6. while(l < r){
    7. sum = numbers[l] + numbers[r];
    8. if(sum == target)
    9. break;
    10. if(sum < target)
    11. ++l;
    12. else
    13. --r;
    14. }
    15. return vector<int>{l+1,r+1};
    16. }
    17. };

    2. 归并两个有效数组

    88. 合并两个有序数组

    88. Merge Sorted Array

            给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

            请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

            注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

            因为这两个数组已经排好序,我们可以把两个指针分别放在两个数组的末尾,即 nums1 m 1 位和 nums2 n 1 位。每次将较大的那个数字复制到 nums1 的后边,然后向前移动一位。 因为我们也要定位 nums1 的末尾,所以我们还需要第三个指针,以便复制。
            在以下的代码里,我们直接利用 m n 当作两个数组的指针,再额外创立一个 pos 指针,起 始位置为 m + n 1 。每次向前移动 m n 的时候,也要向前移动 pos 。这里需要注意,如果 nums1 的数字已经复制完,不要忘记把 nums2 的数字继续复制;如果 nums2 的数字已经复制完,剩余 nums1 的数字不需要改变,因为它们已经被排好序。
    1. class Solution {
    2. public:
    3. void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
    4. int pos = m + n - 1;
    5. m--,n--;
    6. while(m >= 0 && n >= 0){
    7. nums1[pos--] = nums1[m] > nums2[n] ? nums1[m--] : nums2[n--];
    8. }
    9. while(n >= 0){
    10. nums1[pos--] = nums2[n--];
    11. }
    12. }
    13. };

     3. 快慢指针

    142. 环形链表 II

    142. Linked List Cycle II

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

            如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

            不允许修改链表。

            对于链表找环路的问题,有一个通用的解法—— 快慢指针(Flody判圈法) 。给定两个指针, 分别命名为 slow fast ,起始位置在链表的开头。每次 fast 前进两步, slow 前进一步。如果 fast 可以走到尽头,那么说明没有环路;如果 fast 可以无限走下去,那么说明一定有环路,且一定存 在一个时刻 slow fast 相遇。当 slow fast 第一次相遇时,我们将 fast 重新移动到链表开头,并让 slow fast 每次都前进一步。当 slow fast 第二次相遇时,相遇的节点即为环路的开始点。
    1. /**
    2. * Definition for singly-linked list.
    3. * struct ListNode {
    4. * int val;
    5. * ListNode *next;
    6. * ListNode(int x) : val(x), next(NULL) {}
    7. * };
    8. */
    9. class Solution {
    10. public:
    11. ListNode *detectCycle(ListNode *head) {
    12. ListNode *slow = head,*fast = head;
    13. // 判断是否存在环路
    14. do{
    15. if(!fast || !fast->next)
    16. return nullptr;
    17. fast = fast->next->next;
    18. slow = slow->next;
    19. }while(fast != slow);
    20. // 如果存在,查找环路节点
    21. fast = head;
    22. while(fast != slow){
    23. fast = fast->next;
    24. slow = slow->next;
    25. }
    26. return fast;
    27. }
    28. };

    4. 滑动窗口

    76. 最小覆盖子串

    76. Minimum Window Substring

            给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

            本题使用滑动窗口求解,即两个指针 l r 都是从最左端向最右端移动,且 l 的位置一定在 r 的左边或重合。注意本题虽然在 for 循环里出现了一个 while 循环,但是因为 while 循环负责移 动 l 指针,且 l 只会从左到右移动一次,因此总时间复杂度仍然是 O ( n ) 。本题使用了长度为 128 的数组来映射字符,也可以用哈希表替代;其中 chars 表示目前每个字符缺少的数量, flag 表示 每个字符是否在 t 中存在。
    1. class Solution {
    2. public:
    3. string minWindow(string s, string t) {
    4. vector<bool> flag(128,false);
    5. vector<int> chars(128,0);
    6. // 先统计t中的字符情况
    7. for(int i=0;isize();i++){
    8. flag[t[i]] = true;
    9. ++chars[t[i]];
    10. }
    11. // 移动滑动窗口,不断更改统计数据
    12. int cnt = 0,l = 0,min_l = 0,min_size = s.size() + 1;
    13. for(int r=0;rsize();++r){
    14. if(flag[s[r]]){
    15. if(--chars[s[r]] >= 0){
    16. ++cnt;
    17. }
    18. // 若目前滑动窗口已包含t中全部字符,
    19. // 则尝试将l右移,在不影响结果的情况下获得最短子字符串
    20. while(cnt == t.size()){
    21. if(r - l + 1 < min_size){
    22. min_l = l;
    23. min_size = r - l + 1;
    24. }
    25. if(flag[s[l]] && ++chars[s[l]] > 0){
    26. --cnt;
    27. }
    28. ++l;
    29. }
    30. }
    31. }
    32. return min_size > s.size() ? "" : s.substr(min_l,min_size);
    33. }
    34. };

    三、巩固练习 

    633. 平方数之和

    633. Sum of Square Numbers

            给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a2 + b2 = c 。

    Two Sum 题目的变形题之一。

    1. class Solution {
    2. public:
    3. bool judgeSquareSum(int c) {
    4. long long l = 0,r = sqrt(c),sum = 0;
    5. while(l <= r){
    6. sum = l*l + r*r;
    7. if(sum == c){
    8. return true;
    9. }else if(sum > c){
    10. --r;
    11. }else{
    12. ++l;
    13. }
    14. }
    15. return false;
    16. }
    17. };

    680. 验证回文串 II

    680. Valid Palindrome II

            给你一个字符串 s最多 可以从中删除一个字符。

            请你判断 s 是否能成为回文字符串:如果能,返回 true ;否则,返回 false 。

    两层双指针:
    1. 外一层,寻找不相同的 begin 与 end 下标,如未找到,则 s 本身自己就是回文字符串
    2. 找着了,则分两种情况,begin 后移 或者 end 前移,再进行一次双指针循环判断

    1. class Solution {
    2. public:
    3. bool validPalindrome(string s) {
    4. int begin = 0,end = s.length() - 1;
    5. while(begin < end){
    6. if(s[begin] != s[end]){
    7. return validHelper(s,begin + 1,end) || validHelper(s,begin,end - 1);
    8. }
    9. ++begin;
    10. --end;
    11. }
    12. return true;
    13. }
    14. bool validHelper(string s,int begin,int end){
    15. while(begin < end){
    16. if(s[begin] != s[end]){
    17. return false;
    18. }
    19. ++begin;
    20. --end;
    21. }
    22. return true;
    23. }
    24. };

    524. 通过删除字母匹配到字典里最长单词

    524. Longest Word in Dictionary through Deleting

            给你一个字符串 s 和一个字符串数组 dictionary ,找出并返回 dictionary 中最长的字符串,该字符串可以通过删除 s 中的某些字符得到。

            如果答案不止一个,返回长度最长且字母序最小的字符串。如果答案不存在,则返回空字符串。

    我们可以先对 dictionary 根据题意进行自定义排序:

    1. 长度不同的字符串,按照字符串长度排倒序;
    2. 长度相同的,则按照字典序排升序。                                                                               

    然后我们只需要对 dictionary 进行顺序查找,找到的第一个符合条件的字符串即是答案。

    1. class Solution {
    2. public:
    3. string findLongestWord(string s, vector& dictionary) {
    4. sort(dictionary.begin(),dictionary.end(),[](string& a,string& b)
    5. {
    6. if(a.length() == b.length())
    7. return a < b;
    8. return a.length() > b.length();
    9. });
    10. for(auto ans : dictionary){
    11. int n = s.length();
    12. int m = ans.length();
    13. if(n < m) continue;
    14. int i = 0,j = 0;
    15. while(i < n && j < m){
    16. if(s[i] == ans[j]) ++j;
    17. ++i;
    18. if(j == m) return ans;
    19. }
    20. }
    21. return "";
    22. }
    23. };

    欢迎大家共同学习和纠正指教

  • 相关阅读:
    Python 算法:学习二分法
    centos 编译安装的php多版本 切换
    如何比较两个文件是否完全一样,Windows、MacOS、Linux(使用自带命令比较)
    LeetCode每日一题(1300. Sum of Mutated Array Closest to Target)
    【CDH】超详细-搭建本地大数据研发环境(16G内存+CDH)
    Java线程池ExecutorService和Executors应用(Spring Boot微服务)
    未来无界 | 「DaoCloud 道客」联合轻流发布企业级云原生无代码解决方案
    【RT-Thread】nxp rt10xx 设备驱动框架之--hwtimer搭建和使用
    java的Timer全网最详细总结
    PyTorch构建神经网络预测气温(数据集对比,CPU与GPU对比)
  • 原文地址:https://blog.csdn.net/Hello_world_n/article/details/126919186