双指针法-代码随想录-刷题笔记

双指针法效率的优势:通过两个指针在一个for循环下完成两个for循环的工作。
除了链表一些题目一定要使用双指针,其他题目都是使用双指针来提高效率,一般是将O(n^2)的时间复杂度,降为O(n)

27. 移除元素

数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。

法一,暴力

// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int size = nums.size();
        for (int i = 0; i < size; i++) {
            if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
                for (int j = i + 1; j < size; j++) {
                    nums[j - 1] = nums[j];
                }
                i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
                size--; // 此时数组的大小-1
            }
        }
        return size;


    }
};

法二,双指针

//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
         //双指针
         int j=0;
         for(int i=0;i<nums.size();i++){
             if(nums[i]!=val){
                 nums[j]=nums[i];
                 j++;
             }
         }
         return j;
    }
};
/**
* 相向双指针方法,基于元素顺序可以改变的题目描述改变了元素相对位置,确保了移动最少元素
* 时间复杂度:O(n)
* 空间复杂度:O(1)
*/
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一定指向了最终数组末尾的下一个元素
    }
};

344.反转字符串

//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
    void reverseString(vector<char>& s) {
        for (int i = 0, j = s.size() - 1; i < s.size()/2; i++, j--) {
            swap(s[i],s[j]);
        }
    }
};

image.png

题目:剑指Offer 05.替换空格

其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
这么做有两个好处:

  1. 不用申请新数组。
  2. 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。
//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
    string replaceSpace(string s) {
        int oldSize=s.size();
        int count=0;
        for(auto &i:s){
            if(i==' '){
                count++;
            }
        }
        s.resize(oldSize+2*count);
        int newSize=s.size();
        for(int i=oldSize-1,j=newSize-1;j>i;i--,j--){
             if(s[i]!=' '){
                 s[j]=s[i];
             }
             else{
                s[j]='0';
                s[j-1]='2';
                s[j-2]='%';
                j-=2;
             }
        }
        return s;
    }
};

151.翻转字符串里的单词

class Solution {
public:
    //去除多余空格,双指针
    void removeExtraSpaces(string &s){
        int slow=0;
        for(int i=0;i<s.size();i++){
            if(s[i]!=' '){
                if(slow!=0) s[slow++]=' ';
                while(i<s.size()&&s[i]!=' '){
                     s[slow++]=s[i++];
                }           
            }
        }
        s.resize(slow);//注意resize
    }
    
    //区间翻转
    void  reverseStr(string &s,int start,int end){
        for(int i=start,j=end;i<j;i++,j--){
            swap(s[i],s[j]);
        }
    }


    string reverseWords(string s) {
        //去除空格
        removeExtraSpaces(s);
        //整体翻转
        reverseStr(s,0,s.size()-1);
        //局部翻转
        int count=0;
        for(int i=0;i<=s.size();i++){
            if(i==s.size()||s[i]==' '){
               reverseStr(s,count,i-1);
               count=i+1;
            }
        }      
        return s;
    }
};

206.反转链表

//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode*pre=NULL;
        ListNode*cur=head;
        while(cur){
            ListNode*next=cur->next;
            cur->next=pre;
            pre=cur;
            cur=next;
        }
        return pre;
    }
};
//时间复杂度:O(n)
//空间复杂度:O(n)
class Solution {
public:
    ListNode*reverseNode(ListNode* pre,ListNode*cur){
       if(cur==NULL){
           return pre;
       }
       ListNode*next=cur->next;
       cur->next=pre;
       return reverseNode(cur,next);
    }

    ListNode* reverseList(ListNode* head) {
        return reverseNode(NULL,head);
    }
};

19.删除链表的倒数第N个节点

//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* slow = dummyHead;
        ListNode* fast = dummyHead;
        while(n-- && fast != NULL) {
            fast = fast->next;
        }
        fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点
        while (fast != NULL) {
            fast = fast->next;
            slow = slow->next;
        }
        slow->next = slow->next->next; 
        
        // ListNode *tmp = slow->next;  C++释放内存的逻辑
        // slow->next = tmp->next;
        // delete tmp;
        
        return dummyHead->next;
    }
};

面试题 02.07. 链表相交

//时间复杂度:O(m+n)
//空间复杂度:O(1)
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA = 0, lenB = 0;
        while (curA != NULL) { // 求链表A的长度
            lenA++;
            curA = curA->next;
        }
        while (curB != NULL) { // 求链表B的长度
            lenB++;
            curB = curB->next;
        }
        curA = headA;
        curB = headB;
        // 让curA为最长链表的头,lenA为其长度
        if (lenB > lenA) {
            swap (lenA, lenB);
            swap (curA, curB);
        }
        // 求长度差
        int gap = lenA - lenB;
        // 让curA和curB在同一起点上(末尾位置对齐)
        while (gap--) {
            curA = curA->next;
        }
        // 遍历curA 和 curB,遇到相同则直接返回
        while (curA != NULL) {
            if (curA == curB) {
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return NULL;
    }
};

142.环形链表II

//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
       ListNode*slow=head,*fast=head;
       while(fast&&fast->next){
           fast=fast->next->next;
           slow=slow->next;
           if(slow==fast){
              ListNode*cur=head;
              while(cur!=slow){
                  cur=cur->next;
                  slow=slow->next;
              }
              return cur;
           }
       }
       return NULL;
    }
};

第15题. 三数之和

// 时间复杂度: O(n2)
// 空间复杂度:O(n)
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>>ans;
        sort(nums.begin(),nums.end());
        for(int i=0;i<nums.size();i++){ 
            if(nums[i]>0) return ans;
            if(i>0&&nums[i]==nums[i-1])  continue;       
            int j=i+1,k=nums.size()-1;
            while(j<k){        
                if(nums[i]+nums[j]+nums[k]>0){
                    k--;
                }
                else if(nums[i]+nums[j]+nums[k]<0){
                    j++;
                }
                else{
                    ans.push_back({nums[i],nums[j],nums[k]});
                    while(j<k&&nums[k]==nums[k-1]){
                        k--;
                    }
                    while(j<k&&nums[j]==nums[j+1]){
                        j++;
                    } 
                    j++;
                    k--;             
                }
            }          
        }
        return ans;
    }
};

第18题. 四数之和

有一些细节需要注意,例如: 不要判断nums[k] > target 就返回了,三数之和 可以通过 nums[i] > 0 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。比如:数组是[-4, -3, -2, -1],target是-10,不能因为-4 > -10而跳过。但是我们依旧可以去做剪枝,逻辑变成nums[i] > target && (nums[i] >=0 || target >= 0)就可以了。

//时间复杂度: O(n3)
// 空间复杂度:O(n)
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>>ans;
        sort(nums.begin(),nums.end());
        for(int i=0;i<nums.size();i++){
            if(nums[i]>target&&nums[i]>=0) break;
            if(i>0&&nums[i]==nums[i-1]) continue;
            for(int j=i+1;j<nums.size();j++){
                if(nums[i]+nums[j]>target&&nums[i]+nums[j]>=0) break;
                if(j>i+1&&nums[j]==nums[j-1]) continue;
                int left=j+1,right=nums.size()-1;
                while(left<right){
                    if((long)nums[i]+nums[j]+nums[left]+nums[right]>target){
                        right--;
                    }
                    else if((long)nums[i]+nums[j]+nums[left]+nums[right]<target){
                        left++;
                    }
                    else{
                       ans.push_back({nums[i],nums[j],nums[left],nums[right]});
                       while(left<right&&nums[right]==nums[right-1]){
                           right--;
                       }
                       while(left<right&&nums[left]==nums[left+1]){
                           left++;
                       }
                       left++;
                       right--;
                    }
                }
            }
        }
        return ans;
      
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值