leetcode200题之双指针(一)

1. 验证回文串

方法:我们直接在原字符串 s 上使用双指针。在移动任意一个指针时,需要不断地向另一指针的方向移动,直到遇到一个字母或数字字符,或者两指针重合为止。也就是说,我们每次将指针移到下一个字母字符或数字字符,再判断这两个指针指向的字符是否相同。

几个API:  islower(char c) 是否为小写字母
                isupper(char c) 是否为大写字母  
                isdigit(char c) 是否为数字
                isalpha(char c) 是否为字母
                isalnum(char c) 是否为字母或者数字
                toupper(char c) 字母小转大
                tolower(char c) 字母大转小

class Solution {
public:
    bool isPalindrome(string s) {
        int left=0,right=s.size()-1;
        while(left <= right){
            while(left<=right && !isalnum(s[left])){
                left++;
            }
            while(left<=right && !isalnum(s[right])){
                right--;
            }
            if(left<=right){   //防止出现空串
                if(tolower(s[left])!=tolower(s[right]))  return false;
            }
            left++;
            right--;
        }
        return true;
    }
};

2. 颜色分类

方法:我们用三个指针(p0, p2 和cur)来分别追踪0的最右边界,2的最左边界和当前考虑的元素

沿着数组移动 cur 指针,若 nums[cur] = 0,则将其与 nums[p0]互换,并将指针都向右移;若 nums[curr] = 2 ,则与 nums[p2]互换,并将 p2指针左移。 

Q: 为什么与p2交换,cur不右移?

A:curr 左边全都是0/1的有序序列!

  1. curr位置是0时,与左边的0位置交换,因为保证curr左边全是0/1, 所以交换过来的必然是0/1,状态维持住了;
  2. curr位置是2时,交换后,curr不能移动,因为一移动,没法保证交换过来的是0/1;所以这里不移动;这时状态也维持住了
class Solution {
public:
    void sortColors(vector<int>& nums) {
        int p0=0,cur=0,p2=nums.size()-1;
        while(cur<=p2){
            if(nums[cur]==0){
                swap(nums[cur],nums[p0]);
                cur++;
                p0++;
            }
            else if(nums[cur]==2){
                swap(nums[cur],nums[p2]);
                p2--;
            }
            else cur++;
        }
    }
};

3. 划分字母区间

分析:从第一个字母开始分析,假设第一个字母是 'a',那么第一个区间一定包含最后一次出现的 'a'。但第一个出现的 'a' 和最后一个出现的 'a' 之间可能还有其他字母这些字母会让区间变大。举个例子,在 "abccaddbeffe" 字符串中,第一个最小的区间是 "abccaddb"

方法:定义数组 last[char] 来表示字符 char 最后一次出现的下标定义 anchor 和 j 来表示当前区间的首尾。如果遇到的字符最后一次出现的位置下标大于 j, 就让 j=last[c] 来拓展当前的区间。当遍历到了当前区间的末尾时(即 i==j ),把当前区间加入答案,同时将 ancor 设为 i+1 去找下一个区间。

class Solution {
public:
    vector<int> partitionLabels(string S) {
        vector<int> last(26);
        for(int i=0; i<S.size(); i++){
            last[S[i]-'a']=i;
        }
        vector<int> res;
        int ancor=0, j=0;
        for(int i=0; i<S.size(); i++){
            j=max(j,last[S[i]-'a']);  //拓展当前的区间
            if(i==j){
                res.push_back(i-ancor+1);
                ancor=i+1;  //更新ancor
            }
        }
        return res;
    }
};

4.  合并两个有序数组

从前往后:最直接的算法实现是将指针p1 置为 nums1的开头, p2为 nums2的开头,在每一步将最小值放入输出数组中。由于 nums1 是用于输出的数组,需要将nums1中的前m个元素放在其他地方(复制一份数组和nums2比较,再nums1上修改),也就需要 O(m)的空间复杂度。

从后往前:如果我们从结尾开始改写 nums1 的值,这里没有信息,因此不需要额外空间。

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int i=m+n-1; //注意i的取值!!
        int p1=m-1,p2=n-1;
        while(p1>=0 && p2>=0){
            if(nums1[p1]<nums2[p2]){
                nums1[i--]=nums2[p2--];
            }
            else{
                nums1[i--]=nums1[p1--];
            }
        }
        while(p2>=0) nums1[i--]=nums2[p2--]; //nums1合并完,nums2还未合并完
    }
};

5. 三数之和

方法:排序+双指针

    1. 特判,对于数组长度 n,如果数组为 null 或者数组长度小于 3,返回 {}。
    2. 对数组进行排序。
    3. 遍历排序后数组:笃定三指针的最小数字的指针i,双指针L,R分别设在(i,len(nums))两端。
         (1)若 nums[i]>0:因为已经排序好,所以后面不可能有三个数加和等于0,直接返回结果。
         (2)对于重复元素:跳过,避免出现重复解  nums[k]==nums[k-1]   continue;
         (3)令左指针 L=i+1,右指针 R=n-1,当 L<R时,执行循环:
        当 nums[i]+nums[L]+nums[R]==0,并同时将 L,R移到下一位置,寻找新的解, 执行循环,
        判断左界和右界是否和下一位置重复,去除重复解。nums[L]==nums[L-1]  nums[R]==nums[R-1]
        若和大于 0,说明 nums[R] 太大,R 左移
        若和小于 0,说明 nums[L] 太小,L 右移 

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        if(nums.size()<3)  return {};

        sort(nums.begin(),nums.end()); //排序 

        for(int k=0; k+2<nums.size(); k++){
            if(nums[k]>0)  return res;
            if(k>0 && nums[k]==nums[k-1])  continue;
            
            int target = -nums[k];
            int l=k+1,r=nums.size()-1;
            while(l<r){
                if(nums[l]+nums[r]==target){
                    res.push_back({nums[k],nums[l],nums[r]});
                    l++;
                    r--;
                    while(l<r && nums[l]==nums[l-1])  l++;
                    while(l<r && nums[r]==nums[r+1])  r--;  
                }
                else if(nums[l]+nums[r]>target){
                    r--;
                }
                else{
                    l++;
                }
            } 
        }
        return res; 
    }
};

6.四数之和

思路:和三数之和一个道理。

使用四个指针。固定最小的 i 和 j 在左边,l=i+1, r=n-1 移动两个指针包夹求解。

保存使得nums[i]+nums[j]+nums[l]+nums[r]==target的解。偏大时 r 左移,偏小时 l 右移。c和d相遇时,表示以当前的 i 和j j 为最小值的解已经全部求得。

j++,进入下一轮循环b循环,当 j 循环结束后。 i++,进入下一轮 i 循环。 即(i 在最外层循环,里面嵌套 j 循环,再嵌套双指针l,r包夹求解)。

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        sort(nums.begin(),nums.end());
        int n=nums.size();
        if(n<4)  return res;
        for(int i=0; i+3<n; i++){
            if(i>0 && nums[i]==nums[i-1])  continue;
            for(int j=i+1; j+2<n; j++){
                if(j>i+1 && nums[j]==nums[j-1])  continue;
                int target1=target-nums[i]-nums[j];
                int l=j+1, r=n-1;
                while(l<r){
                    if(nums[l]+nums[r]==target1){
                        res.push_back(vector<int>{nums[i], nums[j], nums[l], nums[r]});
                        l++;
                        r--;
                        while(l<r && nums[l]==nums[l-1])  l++;
                        while(l<r && nums[r]==nums[r+1])  r--;
                    }
                    else if(nums[l]+nums[r]>target1){
                        r--;
                    }
                    else{
                        l++;
                    }
                }
            }
        }
        return res;
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值