双指针(leetcode 344、leetcode 345、 leetcode 125、 leetcode 11、 leetcode 141、 leetcode 879)

双指针的基本思想: 使用两个指针, 以不同的方向或不同的速度移动, 尤其在数组类题目中常见, 它也是优化的算法的一种方案。

可为了: 对撞双指针、快慢双指针。

对撞双指针:一个指针指向头, 一个指针指向尾, 两个指针都向中间移动,相遇就停止。
快慢双指针: 一个指针走的快,在前面找符合条件的元素, 一个指针走的慢, 用于将符合条件的元素换到前面来, 通常 [0 ~ 慢指针]的所有元素都是满足条件的元素值。

对撞双指针

写leetcode的,大概都听过大名鼎鼎的 ”两数之和问题“ 吧, 这是个典型的双指针题; ”三数之和“ 、”四数之和“、这跟两数之和问题大同小异。可以参考这个博文,写的清晰易懂 https://labuladong.gitbook.io/algo/di-ling-zhang-bi-du-xi-lie/nsum。

LeetCode - 344. Reverse String(反转字符串)

用对撞指针的思想, 两个指针的值进行交换。

class Solution {
public:
    void reverseString(vector<char>& s) {
        for(int i = 0, j = s.size() - 1; i < j; i++,j--){
            swap(s[i],s[j]);
        }
    }
};

LeetCode - 345. Reverse Vowels of a String(反转字符串中的元音字母)

这题不一定只有字母, 也会有别的字符在其中, 需要特判。
此外用一个散列表记录 aeiou 这五个字母, 那么判断是否为元音字母就可以在很方便的完成。

class Solution {
public:
    string reverseVowels(string s) {
        bool f[26];
        memset(f,0,sizeof(f));
        //利用散列表记录aeiou
        f['a' - 'a'] = f['e' - 'a'] = f['o' - 'a'] = f['u' - 'a'] = f['i' - 'a'] = 1;
        for(int i = 0, j = s.size() - 1; i < j;){
        	//不是元音字母就移动双指针
            if(isalpha(s[i]) && isalpha(s[j]) && f[tolower(s[i]) - 'a'] && f[tolower(s[j]) - 'a'])
             {swap(s[i],s[j]); i++; j--; continue;}
            i++; j--;
            if(isalpha(s[i - 1]) && f[tolower(s[i - 1]) - 'a']) i--;
            if(isalpha(s[j + 1]) && f[tolower(s[j + 1]) - 'a']) j++;
        }
        return s;
    }
};

LeetCode - 125. Valid Palindrome(验证回文串)

class Solution {
public:
    bool isPalindrome(string s) {
        if(s.size() == 0) return true;
        for(int i = 0, j = s.size() - 1; i < j;){
            while(!isalnum(s[i]) && i < j) i++;
            while(!isalnum(s[j]) && i < j) j--;
            if(i >= j) return true;
            if(tolower(s[i]) == tolower(s[j])){
                i++; j--;
            }
            else 
                return false;
        }
        return true;
    }
};

LeetCode - 11. Container With Most Water(盛最多水的容器)

这题也可以用对撞指针解决, 对于数组中的任意一个元素值,以它做为容器的边,那么用盛最多的水,另外一条边应该离它尽量远并且大于等于它,那么得到的就是这条边的最大盛水。

class Solution {
public:
    int maxArea(vector<int>& height) {
        int maxwater = -1;
        for(int i = 0, j = height.size() - 1;i < j; ){
        	//左边的边小于右边的边, 那么最大盛水:左右的距离 * 左边的高度
            if(height[i] <= height[j]) {
                maxwater = max(maxwater,(j - i) * height[i]);
                i++;
            }
            //左边的边大于右边的边, 那么最大盛水:左右的距离 * 右边的高度
            if(height[i] > height[j]){
                maxwater = max(maxwater,(j - i) * height[j]);
                j--;
            }
        }
        return maxwater;
    }
};

参考博客:https://zhuanlan.zhihu.com/p/107457778

快慢指针

Leetcode 141-环形链表

所谓快慢指针,就是在同一个循环里,快慢指针同时移动,但快指针永远比慢指针多走几步(通常是2倍关系)。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode *low = head,*quick = head;
        if(quick == NULL) return false;
        while(true){
            quick = quick -> next;
            if(quick != NULL) quick = quick -> next;
            low = low->next;
            if(quick == NULL) return false;
            if(quick == low)  return true;
        }
        return true;
    }
};

Leetcode 879-链表的中间结点

这道题就是针对我在上一题所总结的规律的典型应用。利用快指针永远比慢指针多走2n(n通常等于1)的特点,只要快指针走到了链表尾部,那慢指针就一定到了链表的中间.

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode *low,*quick;
        int qnums,lnums;
        lnums = 1;
        qnums = 1;
        low = quick = head;
        while(quick != NULL){
            if(qnums / 2 + 1 > lnums) {low = low->next; lnums++;}
            quick = quick -> next;
            qnums++;
        }
        return low;
    }
};

Leetcode 142- 环形链表 II

这题有个重要的结论:当快慢指针相遇时,它们距离入环节点的距离等于从头节点到入环节点的距离。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *low = head,*quick = head;
        if(quick == NULL) return NULL;
        while(true){
            quick = quick -> next;
            if(quick != NULL) quick = quick -> next;
            low = low->next;
            if(quick == NULL) return NULL;
            if(quick == low)  break;
        }
        low = head;
        while(low != quick){
            low = low -> next;
            quick = quick -> next;
        }
        return low;
    }
};

总结:
快慢指针的题目做的不是很多,但它的规律比双指针更加明显:
同样总是以链表/数组为背景;
一定能构成一个循环(或成环),促使快指针能“追上”慢指针;
快指针通常比慢指针多走一步,形成两倍关系(视情况而定)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值