双指针

一、双指针总结

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 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& 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 twoSum(vector& 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> threeSum(vector& nums) {
    vector> ans;
    if (nums.size()<3) return ans;
    
    sort(nums.begin(), nums.end());
    for (int i=0; i0 && 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 t = {nums[i], nums[l], nums[r]};
                ans.push_back(t);
                // 去重
                while (l

11.盛最多水的容器

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

int maxArea(vector& 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 intersect(vector& nums1, vector& nums2) {
    vector 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

209.长度最小的子数组

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

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

  • 1.不断扩张窗口,寻找一个可行解
  • 2.不断收缩窗口,优化这个可行解,直到不满足条件;
  • 3.重复1、2,即寻找下一个可行解,然后再优化。
int minSubArrayLen(int s, vector& 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)

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 needs;
    unordered_map 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 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> findContinuousSequence(int target) {
    vector> 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 temp;
                for (int i=l; i<=r; i++) {
                    temp.push_back(i);
                }
                ans.push_back(temp);
            }
            sum -= l;
            l++;
        }
        r++;
    }
    return ans;
}

你可能感兴趣的:(双指针)