【代码随想录】双指针法

简介: 【代码随想录】双指针法

双指针法


双指针是一种做题思路,并不隶属于某一种特定的数据结构,数组、链表、字符串都可以出双指针的题目


27. 移除元素【简单,快慢指针,首尾指针】



  1. 快慢指针


  1. 首尾指针


class Solution {
public:
  //快慢指针,  时间复杂度:O(n)     空间复杂度:O(1)
  int removeElement(vector<int>& nums, int val) {
    int slow = 0;
    for (int fast = 0; fast < nums.size(); fast++) {
      if (nums[fast] != val) {
        nums[slow] = nums[fast];
        slow++;
      }
    }
    return slow;
  }
  //双指针优化,避免了不必要的赋值
  //首尾指针
    //把等于val的值全部放到后面去
  int removeElement01(vector<int>& nums, int val) {
    int left = 0;
    int right = nums.size() - 1;
    while (left <= right) {
      if (nums[left] == val) {
        //nums[left] = nums[right];
                swap(nums[left, nums[right]])
        right--;
      }
      else {//不是需要删除的元素
        left++;
      }
    }
    return left;
  }
};


26. 删除有序数组中的重复项【简单,快慢指针】



  1. erase暴力破解


  1. 快慢指针


class Solution {
public:
  int removeDuplicates(vector<int>& nums) {
    if (nums.size() == 0)  return 0;
    int left = 0, right = 1;
    for (right; right < nums.size(); right++) {
      if (nums[right] != nums[left]) {
        left++;
        nums[left] = nums[right];
      }
    }
    return left + 1;
  }
};


844. 比较含退格的字符串【简单】



思路一:栈的压入和弹出


思路二:string容器实现栈的功能


class Solution {
public:
  //时间复杂度:O(N+M)
  //空间复杂度:O(N+M)
  string build(string str) {
    string ret;
    for (char ch : str) {
      if (ch != '#') {
        ret.push_back(ch);
      }
      else if (ch=='#' && !ret.empty()) {
        ret.pop_back();
      }
    }
    return ret;
  }
  bool backspaceCompare(string S, string T) {
    return build(S) == build(T);  //比较两字符串相等
  }
};


思路三:双指针,分别指向两个字符串


时间复杂度:O(n+m) m,n为两字符串长度


空间复杂度:O(1)


从后往前比较两个字符串


class Solution {
public:
  bool backspaceCompare(string S, string T) {
    int i = S.length() - 1, j = T.length() - 1;
    int skipS = 0, skipT = 0;  //表示当前待删除的字符的数量  
    while (i >= 0 || j >= 0) {
      while (i >= 0) {
        if (S[i] == '#') {
          skipS++, i--;
        }
        else if (S[i] != '#' && skipS > 0) {
          skipS--, i--;
        }
        else if (S[i] != '#' && skipS == 0) {
          break;  // 找到待比较的字符的索引
        }
      }
      while (j >= 0) {
        if (T[j] == '#') {
          skipT++, j--;
        }
        else if (T[j] != '#' && skipT > 0) {
          skipT--, j--;
        }
        else if (T[j] != '#' && skipT == 0) {
          break;  // 找到待比较的字符的索引
        }
      }
      if (i >= 0 && j >= 0) {
        if (S[i] != T[j])  return false;
        i--, j--;
      }
      else if (i >= 0 && j < 0)  return false;  //bxj##  bxj###
      else if (i < 0 && j >= 0)  return false;
    }
    return true;
  }
};


977. 有序数组的平方【简单,首尾指针】



思路一:先for遍历换元,再sort排序;代码不记录


时间复杂度:O(NlogN) 空间复杂度:O(logN) 需要 O(logn) 的栈空间进行排序。


思路二:首尾指针,但需要容器,不能原地解


时间复杂度:O(logn) 空间复杂度:O(n)


class Solution {
public:
  vector<int> sortedSquares(vector<int>& nums) {
    vector<int> v(nums.size(), 0);                   //初始化容器大小  和容器初始值
    int left = 0, right = nums.size() - 1;           //指向原来数组的首尾位置
    for (int i = nums.size() - 1; i >= 0; i--) {     //对容器中元素从后往前赋值
            v[i] = abs(nums[left]) > abs(nums[right]) ? pow(nums[left++], 2) : pow(nums[right--], 2);
      //if (abs(nums[left]) < abs(nums[right])) {
      //  v[i] = pow(nums[right], 2);
      //  right--;
      //}
      //else {
      //  v[i] = pow(nums[left], 2);
      //  left++;
      //}
    }
    return v;
  }
};


344. 反转字符串【简单】



题目意思:实现库函数reverse的功能


思路:首尾指针实现交换


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. 替换空格【简单,双指针】



思路一:使用额外辅助空间;重新生成一条字符串,不是空格就正常拼接字符串,是空格就拼接%20


时间复杂度:O(n)


空间复杂度:O(n)


思路二:不使用辅助空间


双指针实现;需要先扩容


时间复杂度:O(n)


空间复杂度:O(1)


class Solution {
public:
    string replaceSpace(string s) {
        int count = 0; // 1.统计空格的个数
        int sOldSize = s.size();
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == ' ') {
                count++;
            }
        }
        // 2. 扩充字符串s的大小,也就是每个空格替换成"%20"之后的大小
        s.resize(s.size() + count * 2);
        int sNewSize = s.size();
        // 3. 0从后先前将空格替换为"%20"
        for (int i = sNewSize - 1, j = sOldSize - 1; j < i; i--, j--) {
            if (s[j] != ' ') {
                s[i] = s[j];
            } else {
                s[i] = '0';
                s[i - 1] = '2';
                s[i - 2] = '%';
                i -= 2;
            }
        }
        return s;
    }
};


151. 翻转字符串里的单词【简单】



原地解法思路:


1.先去掉所有冗余空格


2.再整体反转字符串


3.再一个一个单词反转


步骤一种去空格我们不能用erase,因为erase的复杂度为O(n)


主要是明白上面的做题思路,还有会实现去空格的方法


class Solution {
public:
    //函数一:反转字符串s中左闭右闭的区间[start, end]
    void reverse(string& s, int start, int end) {
        for (int i = start, j = end; i < j; i++, j--) {   //首尾指针实现
            swap(s[i], s[j]);
        }
    }
    //函数二:移除冗余空格:使用双指针(快慢指针法)O(n)的算法
    void removeExtraSpaces(string& s) {
        int slow = 0, fast = 0; // 定义快指针,慢指针
        for (fast; fast < s.size(); fast++) {   //生成无多余空格的新字符串
            if (s[fast] != ' ') {
                s[slow] = s[fast];    
                slow++;       
            }
            else if (fast - 1 >= 0 && s[fast - 1] != ' ' && s[fast] == ' ') {
                s[slow] = ' ';
                slow++;
            }
        }
        if (slow - 1 > 0 && s[slow - 1] == ' ') {  //检查字符串最末尾是不是空格
            s.resize(slow - 1);
        }
        else {
            s.resize(slow);
        }
    }
    string reverseWords(string s) {
        removeExtraSpaces(s);                   // 1.去掉冗余空格
        reverse(s, 0, s.size() - 1);            // 2.将字符串全部反转
        int slow = 0;
        int fast = 0;
        for (fast; fast < s.size(); fast++) {   //3.再反转各个单词
            if (fast == s.size() - 1) {                //处理最末尾一个单词
                reverse(s, slow, fast);
                return s;
            }
            if (s[fast] == ' ') {
                reverse(s, slow, fast - 1);
                slow = fast + 1;
            }
        }
        return s;
    }
};


206. 反转链表【简单】


思路一:栈


倒序反转的最适合栈;可以存储val值,也可以存储结点;然后重新生成一条链表


思路二:使用双指针修改指向


时间复杂度:O(n)


空间复杂度:O(1)


class Solution {
public:
    //双指针实现修改指向  下一个指向前一个
    ListNode* reverseList(ListNode* head) {
        ListNode* fast = head;        //快指针指向头结点  慢指针指向头结点的前一个结点,即NULL
        ListNode* slow = NULL;
        while (fast != nullptr) {
            ListNode* next = fast->next;
            fast->next = slow;      //核心代码,修改指向
            slow = fast;            //前移两个指针
            fast = next;
        }
        return slow;
    }
};


思路三:递归套娃


时间复杂度:O(n)


空间复杂度:O(n); 取决于递归调用的栈空间


class Solution {
public:
    ListNode* reverse(ListNode* pre,ListNode* cur){
        if(cur == NULL) return pre;
        ListNode* temp = cur->next;
        cur->next = pre;
        // 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
        // pre = cur;
        // cur = temp;
        return reverse(cur,temp);
    }
    ListNode* reverseList(ListNode* head) {
        // 和双指针法初始化是一样的逻辑
        // ListNode* cur = head;
        // ListNode* pre = NULL;
        return reverse(NULL, head);
    }
};


19.删除链表的倒数第N个节点【简单】


思路一:使用vector容器存结点,索引找到待删除节点的前一个结点,修改指向


思路二:使用栈的压入和弹出,翻转就要想到栈


思路三:两次遍历,第一次遍历计数,第二次遍历实现删除


思路四:快慢双指针;使得两指针维持一定间隔,当快指针指向空结点时,慢指针指向需要删除的结点的前一个结点


class Solution {
public:
  ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode* start = new ListNode(0, head);  //1.head前面生成一个结点
    ListNode* fast = head;
    ListNode* slow = start;
    for (int i = 0; i < n; ++i) {  //2.移动快指针   使得 fast-slow 中间隔了n个结点
      fast = fast->next;
    }
    while (fast) {           //3.快速移动fast指向最后
      fast = fast->next;
      slow = slow->next;
    }   //fast指向null时  slow结点是需要删除结点的前一个结点 
    slow->next = slow->next->next;  //4.删除节点
    ListNode* res = start->next;
    delete start;
    return res;
  }
};


160. 相交链表【简单】


相同题目:剑指 Offer 52. 两个链表的第一个公共节点


相同题目:面试题 02.07. 链表相交



思路一:双栈


自己实现了这个


思路:两条链表后面的结点肯定是相同的,我们倒序找到最后一个相同的结点。


实现倒序就要用到栈


class Solution {
public:
  //双栈
  //时间复杂度:O(m+n)    遍历两链表
  //空间复杂度:O(m+n)    存入两链表
  ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
    stack<ListNode*> sA;
    stack<ListNode*> sB;
    if (headA == nullptr || headB == nullptr) return NULL;   //排除特殊情况
    auto nodeA = headA;                 //1.两条链表入两个栈
    while (nodeA != nullptr) {
      sA.push(nodeA);
      nodeA = nodeA->next;
    }
    auto nodeB = headB;
    while (nodeB != nullptr) {
      sB.push(nodeB);
      nodeB = nodeB->next;
    }
    if (sA.top() != sB.top()) return NULL;   //两链表最后一个数都不相同  就没有交集
    int sizeMin = min(sA.size(), sB.size());
    while (sizeMin != 0) {
      if (sA.top() == sB.top()) {
        sA.pop();
        sB.pop();
      }
      else {
        return sA.top()->next;     //2.找到交集的起始结点
      }
      sizeMin--;
    }
    return sA.size() > sB.size() ? headB : headA;   //while循环没else语句跳出  则其中短的链表就是交集   所以返回它的起始结点
  }
};


思路二:哈希表存链表A的结点;然后遍历查找链表B的结点


class Solution {
public:
  //哈希表也能实现功能
  //时间复杂度:O(m+n)   mn分别为两链表的长度  因为需要遍历两链表
  //空间复杂度:O(m)     一链表的长度
  ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
    unordered_set<ListNode*> set;
    ListNode* nodeA = headA;     //1.先把链表A的结点全部存入
    while (nodeA) {
      set.insert(nodeA);
      nodeA = nodeA->next;
    }
    auto nodeB = headB;
    while (nodeB) {
      if (set.count(nodeB))   return nodeB;//2.在哈希表中查找链表B的结点  查到则返回
      nodeB = nodeB->next;
    }
    return NULL;   //3.没查到,说明没有交集
  }
};


思路三:双指针,浪漫的相遇


具体思路看此人的动图演示:链接


空间复杂度:O(1)


class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *node1 = headA;
        ListNode *node2 = headB;
        while (node1 != node2) {
            node1 = node1 != NULL ? node1->next : headB;
            node2 = node2 != NULL ? node2->next : headA;
        }
        return node1;
    }
};


思路四:对齐链表右边,再遍历


时间复杂度: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在同一起点上(末尾位置对齐)
        for(int i = 0; i < gap; i++){
            curA = curA->next;
        }
        // 遍历curA 和 curB,遇到相同则直接返回
        while (curA != NULL) {
            if (curA == curB)   return curA;
            curA = curA->next;
            curB = curB->next;
        }
        return NULL;
    }
};


141. 环形链表【简单,哈希表,双指针】


升级版本题目:142. 环形链表 II



题目意思:给你链表判断是否有环


思路一:哈希表


//时间复杂度:O(n)  
//空间复杂度:O(n)   多余容器空间


思路二:双指针


快指针走两步,慢指针走一步


正版双指针思路:https://leetcode-cn.com/problems/linked-list-cycle/solution/dai-ma-sui-xiang-lu-141-huan-xing-lian-b-h1jq/


时间复杂度:O(n)


空间复杂度:O(1)


class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head==nullptr || head->next==nullptr)  return false;    //排除0,1结点情况
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast != NULL && fast->next != NULL) {
            slow = slow->next;               //慢指针走一步  快指针走两步  相遇证明有环
            fast = fast->next->next;
            if (slow == fast) return true;   // 快慢指针相遇,说明有环
        }
        return false;                       //能走到NULL说明就没环
    }
};


142. 环形链表 II【中等】



思路一:哈希表


与上一题相比,只是修改了返回值


时间复杂度:O(n)


空间复杂度:O(n)


思路二:快慢指针,慢的走一步,快的走两步


就是这个难,可以结合官方讲解和代码随想录的一起学习


时间复杂度:O(n)


空间复杂度:O(1)


class Solution {
public:
  ListNode* detectCycle(ListNode* head) {
    ListNode* fast = head;
    ListNode* slow = head;
    while (fast != NULL && fast->next != NULL) {
      slow = slow->next;
      fast = fast->next->next;
      // 快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇
      if (slow == fast) {             //有环,寻找环的入口
        ListNode* index1 = fast;     //一个从head开始,一个从相交点开始
        ListNode* index2 = head;
        while (index1 != index2) {
          index1 = index1->next;
          index2 = index2->next;
        }
        return index2;    // 返回环的入口
      }
    }
    return NULL;   //无环遍历到底端
  }
};


15. 三数之和【中等,双指针】




思路:排序+双指针


哈希表时间复杂度和空间复杂度都很高;难度主要在于降重


时间复杂度:O(n^2)


空间复杂度:O(1)


class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        // 找出a + b + c = 0     a = nums[i], b = nums[left], c = nums[right]
        for (int i = 0; i < nums.size(); i++) {
            // 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组
            if (nums[i] > 0) return result;
            // 正确去重方法
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int left = i + 1;
            int right = nums.size() - 1;
            while (right > left) {
                // 去重复逻辑如果放在这里,0,0,0 的情况,可能直接导致 right<=left 了,从而漏掉了 0,0,0 这种三元组
                if (nums[i] + nums[left] + nums[right] > 0) {
                    right--;
                }
                else if (nums[i] + nums[left] + nums[right] < 0) {
                    left++;
                }
                else {
                    result.push_back(vector<int>{nums[i], nums[left], nums[right]});
                    // 去重逻辑应该放在找到一个三元组之后
                    while (right > left && nums[right] == nums[right - 1])  right--;
                    while (right > left && nums[left] == nums[left + 1])    left++;
                    // 找到答案时,双指针同时收缩
                    right--;
                    left++;
                }
            }
        }
        return result;
    }
};


18. 四数之和【中等】



与上一题思路一样


class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        for (int k = 0; k < nums.size(); k++) {
            // 去重
            if (k > 0 && nums[k] == nums[k - 1]) {
                continue;
            }
            for (int i = k + 1; i < nums.size(); i++) {
                // 正确去重方法
                if (i > k + 1 && nums[i] == nums[i - 1]) {
                    continue;
                }
                int left = i + 1;
                int right = nums.size() - 1;
                while (right > left) {
                    if (nums[k] + nums[i] > target - (nums[left] + nums[right])) {
                        right--;
                    }
                    else if (nums[k] + nums[i] < target - (nums[left] + nums[right])) {
                        left++;
                    }
                    else {
                        result.push_back(vector<int>{nums[k], nums[i], nums[left], nums[right]});
                        // 去重逻辑应该放在找到一个四元组之后
                        while (right > left && nums[right] == nums[right - 1]) right--;
                        while (right > left && nums[left] == nums[left + 1]) left++;
                        // 找到答案时,双指针同时收缩
                        right--;
                        left++;
                    }
                }
            }
        }
        return result;
    }
};


目录
相关文章
|
9月前
代码随想录——双指针(一)
代码随想录——双指针(一)
|
算法
代码随想录刷题-数组双指针
代码随想录刷题-数组双指针
80 0
代码随想录 Day21 - 二叉树(七)
代码随想录 Day21 - 二叉树(七)
48 0
代码随想录 Day14 - 二叉树(一)
代码随想录 Day14 - 二叉树(一)
66 1
代码随想录 Day17 - 二叉树(四)
代码随想录 Day17 - 二叉树(四)
50 1
代码随想录 Day22 - 二叉树(八)
代码随想录 Day22 - 二叉树(八)
53 0
代码随想录 Day20 - 二叉树(六)
代码随想录 Day20 - 二叉树(六)
65 0
代码随想录 Day23 - 二叉树(九)
代码随想录 Day23 - 二叉树(九)
64 0
代码随想录 Day15 - 二叉树(二)
代码随想录 Day15 - 二叉树(二)
48 0
代码随想录 Day18 - 二叉树(五)
代码随想录 Day18 - 二叉树(五)
62 0