双指针

一、双指针总结

1.1题目

快慢指针(主要解决链表中的问题)

  • 141.环形链表
  • 142.环形链表 II
  • 876.链表的中间结点
  • 剑指 Offer 22. 链表中倒数第k个节点

左右指针

  • 704.二分查找
  • 167.两数之和 II - 输入有序数组
  • 15.三数之和(去重)
  • 11.盛最多水的容器

滑动窗口(两个指针从一个起点出发)

  • 209.长度最小的子数组
  • 76.最小覆盖子串(hard)
  • 3.无重复字符的最长子串
  • 剑指 Offer 57 - II. 和为s的连续正数序列

1.2滑动窗口的思路

  • 1.不断扩张窗口,寻找一个可行解
  • 2.不断收缩窗口,优化这个可行解,直到不满足条件;
  • 3.重复1、2,即寻找下一个可行解,然后再优化。

(适用于求最小的问题,求最大的问题要改一下,不过代码结构差不多)

int left = 0, right = 0;

while (right < s.size()) {
    window.add(s[right]);

    while (valid) {
        window.remove(s[left]);
        left++;
    }
    right++;
}

稍微⿇烦的地⽅就是这个 valid 条件。

二、题目

141.环形链表

思路1:哈希表

bool hasCycle(ListNode *head) {
    if (!head) return false;
    set<ListNode*> st;
    
    while (head) {
        if (st.count(head)) {
            return true;
        }else{
            st.insert(head);
            head = head->next;
        }
    }
    return false;
}

思路2:双指针。
如果链表存在环,则快指针和慢指针一定会相遇。

bool hasCycle(ListNode *head) {
    ListNode *slow, *fast;
    slow = fast = head;
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
        
        if (slow == fast) return true;
    }
    return false;
}

142.环形链表 II

分析相遇时的情况:

从起点到环入口的路程记做a,环的长度记做b;
快指针走的路程记做f,慢指针走的路程记做s;
我们可以得到:
f = s + nb
f = 2s
  => s = nb  
  => s+a = 0+a

所以:当快慢指针相遇时,让头指针和慢指针同时开始走,它们相遇的点就是环的入口。

ListNode *detectCycle(ListNode *head) {
    ListNode *slow, *fast;
    slow = fast = head;
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast) break;
    }
    // 没有环
    if (!fast || !fast->next) return NULL;
    // 有环
    while (head != slow) {
        head = head->next;
        slow = slow->next;
    }
    return head;
}

876.链表的中间结点

题目描述:如果有两个中间结点,则返回第二个中间结点。

该问题常常是以后题目的子问题。注意两点:

  • 快慢指针一起出发
  • 判断条件(fast && fast->next)
ListNode* middleNode(ListNode* head) {
    ListNode *slow, *fast;
    slow = fast = head;
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

剑指 Offer 22. 链表中倒数第k个节点

假设 k 不会超过链表⻓度

ListNode* getKthFromEnd(ListNode* head, int k) {
    ListNode *slow, *fast;
    slow = fast = head;
    
    while (k--) fast = fast->next;
    
    while (fast) {
        slow = slow->next;
        fast = fast->next;
    }
    return slow;
}

704.二分查找

数组是升序的

int search(vector<int>& nums, int target) {
    int l = 0, r = nums.size()-1;
    while (l <= r) {
        int mid = (l + r) / 2;
        if (nums[mid] == target)
            return mid;
        else if(nums[mid] < target)
            l = mid + 1;
        else
            r = mid - 1;
    }
    return -1;
}

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

vector<int> twoSum(vector<int>& nums, int target) {
    int l = 0, r = nums.size()-1;
    while (l < r) {
        int sum = nums[l] + nums[r];
        if (sum == target)
            return {l, r};
        else if (sum < target)
            l++;
        else
            r--;
    }
    return {};
}

15.三数之和

题目描述:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

思路

  • 固定一个数,对剩下的数组使用双指针进行查找。
  • 注意去重操作。
vector<vector<int>> threeSum(vector<int>& nums) {
    vector<vector<int>> ans;
    if (nums.size()<3) return ans;
    
    sort(nums.begin(), nums.end());
    for (int i=0; i<nums.size()-2; i++) {
        // 去重
        if (i>0 && nums[i] == nums[i-1]) {
            continue;
        }
        int l = i+1, r = nums.size()-1;
        int val = 0 - nums[i];
        while (l < r) {
            int temp = nums[l] + nums[r];
            if (temp == val) {
                vector<int> t = {nums[i], nums[l], nums[r]};
                ans.push_back(t);
                // 去重
                while (l<r && nums[l] == nums[l+1]) {
                    l++;
                }
                l++;
                // 去重
                while (l<r && nums[r] == nums[r-1]) {
                    r--;
                }
                r--;
            }else if (temp < val){
                l++;
            }else{
                r--;
            }
        }
    }
    return ans;
}

11.盛最多水的容器

左右哪边小哪边往中间移动

int maxArea(vector<int>& height) {
    int i = 0, j = height.size()-1;
    int maxArea = 0;

    while (i < j) {
        int temp = min(height[i],height[j]) * (j-i);
        maxArea = max(temp, maxArea);

        if (height[i] > height[j]) {
            j--;
        }else{
            i++;
        }
    }
    return maxArea;
}

两个数组的交集 II

题目要求:结果中的元素应与在两个数组中出现的次数一致。
思路:先排序再双指针。

vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
    vector<int> ans;
    sort(nums1.begin(), nums1.end());
    sort(nums2.begin(), nums2.end());
    
    int p1 = 0, p2 = 0;
    int l1 = nums1.size(), l2 = nums2.size();
    while (p1 < l1 && p2 < l2) {
        int a = nums1[p1], b = nums2[p2];
        if (a == b) {
            ans.push_back(a);
            p1++; p2++;
        }else{
            a<b ? p1++ : p2++;
        }
    }
    return ans;
}

209.长度最小的子数组

题目描述:给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

思路:因为是求连续子数组,所以可以用滑动窗口的方法。

  • 1.不断扩张窗口,寻找一个可行解
  • 2.不断收缩窗口,优化这个可行解,直到不满足条件;
  • 3.重复1、2,即寻找下一个可行解,然后再优化。
int minSubArrayLen(int s, vector<int>& nums) {
    int lens = nums.size();
    if (lens==0) return 0;
    
    int l = 0, r = l;
    int sum = 0, ans = INT_MAX;
    while (r < lens) {
        sum += nums[r];
        r++;
        
        while (sum >= s) {
            ans = min(ans, r-l);
            sum -= nums[l];
            l++;
        }
    }
    // 考虑sum(nums)<s的情况
    return ans==INT_MAX ? 0: ans;
}

76.最小覆盖子串

思路:和上题滑动窗口的思路一样。只是判断条件麻烦了点。

如何判断 当前窗口 包含T中的所有字符?

  • ⽤⼀个哈希表 needs 记录t中包含的字符及次数;
  • ⽤另⼀个哈希表 window 记录当前窗⼝中包含的t中的字符及次数。

使用一个变量 match 保存 window 满足 needs 的个数,如果 match 等于 needs,则当前窗口满足条件。

string minWindow(string s, string t) {
    int start = 0, minLen = INT_MAX;
    int l = 0, r = 0;
    
    unordered_map<char, int> needs;
    unordered_map<char, int> windows;
    for (char c: t) needs[c]++;
    
    int match = 0;
    while (r < s.size()) {
        // 不断扩大窗口
        char c1 = s[r];
        if (needs.count(c1)) {
            windows[c1]++;
            if (windows[c1] == needs[c1]) {
                match++;
            }
        }
        r++;
        
        // 如果窗口符合条件,不断收缩窗口
        while (match == needs.size()) {
            if (r - l < minLen) {
                minLen = r - l;
                start = l;
            }
            
            char c2 = s[l];
            if (needs.count(c2)) {
                windows[c2]--;
                if (windows[c2] < needs[c2]) {
                    match--;
                }
            }
            l++;
        }
    }
    return minLen == INT_MAX ? 0: s.substr(start, minLen);
}

3.无重复字符的最长子串

思路:

  • 1.扩大窗口,寻找最长的子串
  • 2.如果遇到重复的情况,缩小子串,直到满足条件
  • 3.重复1、2,直到最后。
int lengthOfLongestSubstring(string s) {
    int l = 0, r = l;
    int ans = 0;
    unordered_map<char, int> map;
    while (r < s.size()) {
        char c1 = s[r];
        map[c1]++;
        r++;
        
        while (map[c1] > 1) {
            char c2 = s[l];
            map[c2]--;
            l++;
        }
        ans = max(ans, r-l);
    }
    return ans;
}

剑指 Offer 57 - II. 和为s的连续正数序列

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

示例:

输入:target = 9
输出:[[2,3,4],[4,5]]
vector<vector<int>> findContinuousSequence(int target) {
    vector<vector<int>> ans;
    int l = 1, r = l;
    int sum = 0;
    while (r < target) {
        sum += r;

        while (sum >= target) {
            if (sum == target && r-l+1 >= 2) {
                vector<int> temp;
                for (int i=l; i<=r; i++) {
                    temp.push_back(i);
                }
                ans.push_back(temp);
            }
            sum -= l;
            l++;
        }
        r++;
    }
    return ans;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349