代码随想录学习记录——双指针篇

27、移除元素

这道题虽然看起来很简单,用双指针法可以快速解决,但是还是有非常多需要注意的细节的。

我的思路是:有两个指针, l e f t P leftP leftP从数组的左边开始, r i g h t P rightP rightP从数组的右边开始查找,每当发现 l e f t P leftP leftP位置的元素等于目标值时就进行左右两个指针元素的交换,同时记录长度的变量要减一。具体代码如下:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int leftP = 0;
        int rightP = nums.size()-1;
        int lenNums = nums.size();  // 记录长度的变量
        if(nums.size() == 0){
            return 0; // 为空直接返回
        }
        while(rightP >= 0 && nums[rightP] == val ){  //这里注意一定要先判断rightP是否大于等于0,否则会出现索引超出范围的问题!
            rightP--;  // 将右端目标值不断去掉
            lenNums--; // 注意更新长度
        }
        while(leftP <= rightP){ 
            if(nums[leftP] == val){
                swap(nums[leftP],nums[rightP]);  // 交换两个值
                lenNums--;  
                rightP--;  // 只有交换了才需要往后移动
            }
            leftP++;  //放在这里是因为不管有没有交换都要往后移动
            while(rightP >= 0 && nums[rightP] == val ){  //这里同样要先判断rightP
                rightP--;
                lenNums--;
            }
        }

        nums.resize(lenNums);
        return lenNums;
    }
};

这里要注意循环中的判断条件必须为 l e f t P < = r i g h t P leftP <= rightP leftP<=rightP,因为遍历最后一定是两个指针相等,而此时如果是 l e f t P leftP leftP逐步增加到两个指针相等,虽然进行该循环后如果该值等于 v a l val val时进行交换相当于没有交换,但会使得 l e n N u m s lenNums lenNums减一,也相当于把这个值给去掉了。

代码随想录中的双指针法则更为简便,其主要思想是双指针同时从头开始移动,一旦发现目标值则慢指针暂停移动,快指针继续移动去寻找下一个不是目标的元素后进行交换,具体代码如下:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slowP = 0;
        for( int fastP = 0; fastP < nums.size(); fastP++){
            if(val != nums[fastP]){
                nums[slowP++] = nums[fastP];
            }
        }
        return slowP;
    }
};

它的另一种双指针跟我的思路一样单独代码更为简洁方便,很值得学习:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int leftIndex = 0;
        int rightIndex = nums.size() - 1;
        while (leftIndex <= rightIndex) {
            // 找左边等于val的元素
            while (leftIndex <= rightIndex && nums[leftIndex] != val){
                ++leftIndex;
            }
            // 找右边不等于val的元素
            while (leftIndex <= rightIndex && nums[rightIndex] == val) {
                -- rightIndex;
            }
            // 将右边不等于val的元素覆盖左边等于val的元素
            if (leftIndex < rightIndex) {
                nums[leftIndex++] = nums[rightIndex--];
            }
        }
        return leftIndex;   // leftIndex一定指向了最终数组末尾的下一个元素
    }
};
26、删除排序数组中的重复项

经过上一道题的提醒,这道题的思路就很清晰了,同样用双指针法可以很快地解决,具体思路为快指针初始化为1,慢指针初始化为0,比较快慢指针对应位置元素是否相等,如果相等则慢指针保持不动,快指针往后移动;如果不相等,则将快指针位置的元素赋予到慢指针的下一个位置处,具体代码如下:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        if(nums.size() == 0){
            return 0;
        }
        int slowP = 0;
        int fastP = 1;
        for(; fastP < nums.size() ; fastP ++){
            if(nums[slowP] != nums[fastP]){
                nums[slowP+1] = nums[fastP];
                slowP++;
            }
        }
        return slowP+1;
    }
};
283、移动零

其实根据前两道题基本上已经可以掌握双指针法的套路了

那么就是细节上的处理问题了,我们的思路是移动快慢指针,如果为0则慢指针就停住,然后快指针去移动找到下一个非零的位置,再将快指针当前这个非零赋予慢指针这个为零的

  • 首先快指针要初始化为0,因为这里不是比较前后相同,因此快指针要从零开始
  • 只要快指针对应位置的元素不为0,那么就执行赋予操作
  • **循环结果后我们需要将慢指针之后的元素全部都设置为0,因为是将0移动的后面的位置
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int slowP = 0;
        int fastP = 0;
        for(; fastP < nums.size(); fastP++){
            if( nums[fastP] != 0){
                nums[slowP] = nums[fastP];
                slowP++;
            }
        }
        for(int i = slowP; i < nums.size(); i++){
            nums[i] = 0;
        }
        
    }
};
844、比较含退格的字符串

这道题在之前的内容中有,具体查看这篇文章[点此跳转]。我重写了一遍代码如下,解释就看那篇文章中的解释吧。

class Solution {
public:
    bool backspaceCompare(string s, string t) {
        int sIndex = s.size() - 1;
        int tIndex = t.size() - 1;
        int sCount = 0;
        int tCount = 0;
        while(sIndex >= 0 || tIndex >= 0){
            while(sIndex >= 0){
                if( s[sIndex] == '#'){
                    sIndex--;
                    sCount++;
                }else if(sCount != 0){
                    sCount--;
                    sIndex--;
                }else{
                    break;
                }
            }
            while(tIndex >= 0){
                if( t[tIndex] == '#'){
                    tIndex--;
                    tCount++;
                }else if(tCount != 0){
                    tCount--;
                    tIndex--;
                }else{
                    break;
                }
            }
            if( sIndex >= 0 && tIndex >= 0){
                if(s[sIndex] != t[tIndex]){
                    return false;
                }
            }
            else{
                if(sIndex >= 0 || tIndex >= 0){
                    return false;
                }
            }
            sIndex--;
            tIndex--;
        }
        return true;
    }
};
977、有序数组的平方

这道题也在之前的题目中出现过,我在之前的文章中写了好几种解答方法和详细的注释,这里就不过多做解释了。这里补充上我刚写的代码吧:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int leftP = 0;
        int rightP = nums.size()-1;
        vector<int>result(nums.size(),0);
        int rIndex = result.size() - 1;
        while( leftP <= rightP){
            if(nums[leftP]*nums[leftP] > nums[rightP] * nums[rightP]){
                result[rIndex] = nums[leftP]*nums[leftP];
                rIndex--;
                leftP++;
            }else{
                result[rIndex] = nums[rightP]*nums[rightP];
                rIndex--;
                rightP--;
            }
        }
        return result;
    }
};

这里需要注意一个点:在将值放入result时最好实现确定其大小然后用索引的方式插入,不要不确定大小然后每次都用insert来插入,因为这个函数的时间复杂度会更高,不太好

除此之外我在力扣中也多做了几道双指针的题目,也将自己的见解放于此处了,之后有补充也会慢慢更新的

88. 合并两个有序数组

这道题如果采用双指针法从两个数组的开头开始比较的话,就需要另外一个数组来存储nums1中前m个字符的内容,为了节约了这些空间,可以从后面开始比较起,即从nums1[m-1]和nums2[n-1]开始比较,将大的逐步放入

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int nums1Index = m-1;
        int nums2Index = n-1;
        int count = m+n-1;
        while( nums1Index >= 0 && nums2Index >= 0){
            if(nums1[nums1Index] < nums2[nums2Index]){
                nums1[count] = nums2[nums2Index];
                count--;
                nums2Index--;
            }else{
                nums1[count] = nums1[nums1Index];
                count--;
                nums1Index--;
            }
        }
        for(;nums1Index >=0;nums1Index--){  //检查是否还剩余元素
            nums1[count] = nums1[nums1Index];
            count--;
        }
        for(;nums2Index >= 0; nums2Index--){  //检查是否还剩余元素
            nums1[count] = nums2[nums2Index];
            count--;
        }
    }
};
141、环形链表

这道题很经典,思路都是非常重要的让快指针走路的速度是慢指针的两倍,如果存在环那么它们一定会相遇,主要就是在一些细节的处理上:

/**
 * 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* fastP = head;
        ListNode* slowP = head;
        while(fastP != NULL && fastP->next !=NULL && fastP->next->next != NULL){
            fastP = fastP->next->next;
            slowP = slowP->next;
            if(fastP == slowP){
                return true;
            }
        }
        return false;
    }
};
125、验证回文串

这道题最简单的思路就是事先将字符串处理一下,只取出其中的字符并且将其转换为小写,之后就采用双指针法就很方便了

class Solution {
public:
    bool isPalindrome(string s) {
        string temStr;
        for(char c:s){
            if(isalnum(c)){  // 判断是否是字符和数字
                temStr += tolower(c);  // 将其转换为小写
            }
        }
        int leftP = 0;
        int rightP = temStr.size() - 1;
        while(leftP <= rightP){
            if(temStr[leftP] != temStr[rightP]){
                return false;
            }
            leftP++;
            rightP--;
        }
        return true;
    }
};
234、回文链表

这道题我一直想着双指针法,但是目前仍然没有在链表上用双指针法的方法,因此只能将链表上的元素转到数组上,因此空间和时间都挺高的

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        //先将数值复制到数组然后再用双指针法来做
        
        ListNode* temPtr = head;
        int lenhead = 0;
        while(temPtr != NULL){
            lenhead++;
            temPtr = temPtr->next;
        }
        temPtr = head;
        vector<int>temV(lenhead,0);
        for(int i = 0; i < lenhead; i++){
            temV[i] = temPtr->val;
            temPtr = temPtr->next;
        }
        int leftP = 0;
        int rightP = temV.size()-1;
        while(leftP <= rightP){
            if(temV[leftP] != temV[rightP]){
                return false;
            }
            leftP++;
            rightP--;
        }
        return true;
    }
};
345、反转字符串中的元音字母

这道题也很简单,事先将元音字母的大小写用集合存起来,然后用双指针法一个从头找元音字母,一个从尾找元音字母,找到就交换即可

class Solution {
public:
    string reverseVowels(string s) {
        unordered_set<char> temSet{'a','A','e','E','i','I','o','O','u','U'};
        int leftP = 0;
        int rightP = s.size() - 1;
        while(leftP <= rightP){
            while( temSet.find(s[leftP]) == temSet.end() && leftP <= rightP){
                leftP++;
            }
            while(temSet.find(s[rightP]) == temSet.end() && leftP <= rightP){
                rightP--;
            }
            if(leftP <= rightP){
                swap(s[leftP],s[rightP]);
            }
            leftP++;
            rightP--;
        }
        return s;
    }
};
392、判断子序列

这道题也是双指针法的典型应用,只要慢指针指向t中的每个字符,然后快指针法在s中找对应字符,找到了t才可以移动,按照此思路补充边界条件即可:

class Solution {
public:
    bool isSubsequence(string s, string t) {
        int sIndex = 0;
        int tIndex = 0;
        if(t.size() < s.size()){
            return false;
        }
        while( tIndex < t.size() && sIndex < s.size()){
            if( t[tIndex] == s[sIndex]){
                sIndex++;
            }
            tIndex++;
        }
        if( sIndex == s.size()){
            return true;
        }
        else{
            return false;
        }
    }
};
557、反转字符串中的单词2

结合前面反转字符串的题目,因此反转单词这部分很简单,主要就是要用快指针向前探路,找到空格时,快指针和慢指针之间就夹着一个单词,进行翻转即可

class Solution {
public:
    string reverseWords(string s) {
        int slowP = 0;
        int fastP = 0;
        while(fastP < s.size()){
            while( fastP < s.size() && s[fastP] != ' '){
                fastP++;
            }
            for(int i = 0; i < (fastP - slowP)/2 ; i++){
                swap(s[slowP + i],s[fastP-1-i]);
            }
            slowP = fastP+1;
            fastP = fastP+1;
        }
        return s;
    }
};
821、字符的最短距离

这道题虽然在双指针的类型题目中,但我用的方法并不是双指针的方法。由于每个字符的两边都可能会有目标字符的存在,而它与两端的目标字符的距离的较小值就是我们要求的答案,因此可以从两边进行扫描,相当于将每个字符左右两边距离目标字符的距离都求出来,然后取小值即可

class Solution {
public:
    vector<int> shortestToChar(string s, char c) {
        vector<int> result(s.size(),0);
        int lenS = s.size();
        for( int i = 0, cIndex = -lenS; i < lenS; i++){ //注意cIndex初始化
            if(s[i] == c){
                cIndex = i;  //目标字符出现,更新cIndex
            }
            result[i] = i - cIndex;
        }
        for ( int i = lenS-1, cIndex = 2 * lenS; i >= 0; i--){
            if(s[i] == c){
                cIndex = i;
            }
            result[i] = min(result[i], cIndex - i);
        }
        return result;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值