【代码随想录】二刷-双指针法

8

双指针

27. 移除元素

// 时间复杂度 O(n)
// 空间复杂度 O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slow = 0;
        int fast = 0;
        int n = nums.size();
        while(fast < n){
            if(nums[fast] != val){
                swap(nums[slow++],nums[fast]);
            }
            fast++;
        }
        return slow;
    }
};

344. 反转字符串

// 时间复杂度 O(n)
// 空间复杂度 O(1)
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]);
        }
    }
};

剑指 Offer 05. 替换空格

// 时间复杂度O(n)
// 空间复杂度O(n)
class Solution {
public:
    string replaceSpace(string s) {
        int count = 0;
        int oldSize = s.size();
        for(char& c:s){
            if(c == ' ')count++;
        }
        s.resize(oldSize+count*2);
        int newSize = s.size();
        // "We are happy    ."
        //             i    j
        // %20
        // i越界之前,j肯定可以追上,从而终止循环。
        for(int i = oldSize-1,j=newSize-1;i < j;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. 反转字符串中的单词

  • 去掉多余空格
  • 整体反转
  • 反转每个单词
// 时间复杂度O(n)
// 空间复杂度O(1)
class Solution {
public:
    // 左闭右闭
    void myReverse(string& s,int begin,int end){
        for(int i =begin,j=end;i<j;i++,j--){
            swap(s[i],s[j]);
        }
    }
    void delExtraSpace(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);
    }
    string reverseWords(string s) {
        delExtraSpace(s);   
        myReverse(s,0,s.size()-1);
        int slow = 0;
        for(int i = 0;i <= s.size();i++){
            if(i == s.size() || s[i] == ' '){
                myReverse(s,slow,i-1);// 到空格了,其那面是单词
                slow = i+1;
            }
        }
        return s;
    }
};

206. 反转链表

// 时间复杂度 O(n)
// 空间复杂度 O(1)
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* tmp;
        ListNode* pre = nullptr;// 想下可以理解,调转过来,第一个结点的next为nullpter
        ListNode* cur = head;
        while(cur){
            tmp = cur->next;// 保存下一个结点

            cur->next = pre;
            pre = cur;// 当前结点为下一个的next
            cur = tmp;// 当前结点变为next
        }
        return pre;
    }
};
  • 递归
// 时间复杂度O(n)
// 空间复杂度O(1)
class Solution {
public:
    ListNode* dfs(ListNode* cur,ListNode* pre){
        if(cur == nullptr)return pre;
        ListNode* tmp = cur->next;
        cur->next = pre;
        // pre = cur; 直接在下面传cur即可
        return dfs(tmp,cur);
    }

    ListNode* reverseList(ListNode* head) {
        return dfs(head,nullptr);
    }
};

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

  • 至于为什么要让fast先走n+1步,感觉是这图独有的技巧性,还是主要理解虚拟头结点的使用。——统一处理所有结点。
// 时间复杂度O(n)
// 空间复杂度O(1)
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* tmpHead = new ListNode(0);
        tmpHead->next = head;
        ListNode* fast = tmpHead;
        ListNode* slow = tmpHead;
        n++;// 因为要删除结点,所以要停在目标结点前。fast先走n+1步
        while(fast && n--){
            fast = fast->next;
        }
        while(fast){
            fast = fast->next;
            slow = slow->next;
        }
        ListNode* tmp = slow->next;
        slow->next = tmp->next;
        delete tmp;
        return tmpHead->next;
    }
};

面试题 02.07. 链表相交

  • 让我想起了【设计链表那道题】,有关虚拟头结点的使用,一是可以统一管理所有结点,二来说也是为了删除结点,给出删除第几个,第几个指的是索引,从0开始,while(n--)会停在对应结点的前一个结点。
    • 联想出,删除第几个,如果这个第几个不是索引,就是字面意思的第几个,从1开始,那么我们可以将n-1,然后开始while(n--),最终停在指定结点的前一个。
// 时间复杂度O(n)
// 空间复杂度O(1)
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int lenA = 0;
        int lenB = 0;
        ListNode* curA = headA;
        ListNode* curB = headB;
        while(curA){
            lenA++;
            curA = curA->next;
        }
        while(curB){
            lenB++;
            curB = curB->next;
        }
        // 默认A为更长的
        if(lenB > lenA){
            swap(headA,headB);
            swap(lenA,lenB);
        }

        int dis = lenA - lenB;
        curA = headA;
        curB = headB;
        while(dis-- && curA){
            curA = curA->next;
        }
        // 同一个起跑线
        while(curA){
            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;
        ListNode* fast = head;
        while(fast && fast->next){
            slow = slow->next;
            fast = fast->next->next;
            if(slow == fast){
                ListNode* tmp = head;
                while(tmp != fast){
                    tmp = tmp->next;
                    fast = fast->next;
                }
                return fast;
            }
        }
        return NULL;
    }
};

15. 三数之和

// 时间复杂度 O(n²)
// 空间复杂度 O(n)
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>>ret;
        sort(nums.begin(),nums.end());
        int n = nums.size();
        for(int i = 0;i < n;i++){// a=nums[i]
            if(nums[i] > 0)break;// 去掉无意义的计算
            if(i > 0 && nums[i] == nums[i-1]){// a去重
                continue;
            }
            int left = i+1;
            int right = n-1;
            while(left < right){
                int tmpSum = nums[i]+nums[left]+nums[right];
                if(tmpSum > 0)right--;
                else if(tmpSum < 0)left++;
                else{
                    // 收集结果
                    ret.push_back({nums[i],nums[left],nums[right]});

                    // 去重
                    // left ->left                 right<-right
                    while(left < right && nums[left] == nums[left+1])left++;
                    while(left < right && nums[right] == nums[right-1])right--;
                    // 经过上述两个循环后,left right分别指向最后一个与上面收集结果时候相等的元素,经过下面再次++后,指向第一个与上述结果不相等我两个元素。
                    // 收缩-调整位置
                    left++;
                    right--;
                }
            }
        }
        return ret;
    }
};
  • 哈希表去重
// 时间复杂度O(n²)
// 空间复杂度O(n)
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>>ret;
        sort(nums.begin(),nums.end());
        int n = nums.size();
        for(int i = 0;i < n;i++){// a=nums[i]
            if(nums[i] > 0)break;// 去掉无意义的计算
            if(i > 0 && nums[i] == nums[i-1]){// a去重
                continue;
            }
            unordered_set<int>set;
            for(int j = i+1;j < n;j++){// b=nums[j]
                if(j > i + 2 && 
                    nums[j] == nums[j-1] &&
                    nums[j-1] == nums[j-2]){// b去重
                    continue;
                }
                int c = 0 - nums[i] - nums[j];//  c
                if(set.find(c) != set.end()){// 找到了
                    ret.push_back({nums[i],nums[j],c});
                    set.erase(c);// c去重
                }else{// 没找到
                    set.emplace(nums[j]);
                }
            }
        }
        return ret;
    }
};

18. 四数之和

  • 即在三数之和的基础上加一层for循环。
// 时间复杂度O(n³)
// 空间复杂度O(n)
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>>ret;
        sort(nums.begin(),nums.end());
        int n = nums.size();
        for(int i = 0;i < n;i++){// a = nums[i]
            if(nums[i] > target && nums[i] >= 0)break;// 剪枝,去掉无意义计算

            if(i > 0 && nums[i] ==  nums[i-1])continue;// 去重

            for(int j = i+1; j < n;j++){// b = nums[j]
                if(nums[i] + nums[j] > target && 
                    nums[i]+ nums[j] >= 0){// 剪枝,去掉无意义计算
                       continue;
                }
                if( j > i + 1 && nums[j] == nums[j-1])continue;// b去重
                int left = j + 1;
                int right = n-1;
                while(left < right){
                    // 防止溢出,转long
                    long tmpSum = (long)nums[i] + nums[j] + nums[left] + nums[right];
                    if(tmpSum > target)right--;
                    else if(tmpSum < target)left++;
                    else{// 收集结果
                        ret.push_back({nums[i],nums[j],nums[left],nums[right]});
                        while(left < right && nums[left] == nums[left+1])left++;
                        while(left < right && nums[right] == nums[right-1])right--;

                        right--;
                        left++;
                    }
                }

            }
        }
        return ret;
    }
};