【算法2】双指针

119 阅读3分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击查看活动详情

220913-双指针

1. 相关题目

这两道题真是用了好久(─.─||)

167. 两数之和 II - 输入有序数组

本来想着用倒叙的暴力解法,在循环之前先过滤掉大于target的数,但这样的方法完完全全忽略了数组首部可能存在的负数。

这道题有几种解法:

  1. 首尾指针法(缩减搜索空间)

    int i = 0;
    int j = numbers.size() - 1;
    while(numbers[i] + numbers[j] != target) {
       if(numbers[i] + numbers[j] > target) j--;
       else i++;
    }
    

    这样一来,时间复杂度为O(n),因为额外建立了长度为2的数组,空间复杂度为O(1)。

  2. hashmap法(可以针对一般的无序数组)

    vector<int> twoSum(vector<int>& nums, int target) {
       vector<int> v(2);
       unordered_map<int, int> map;
       for (int i = 0; i < nums.size(); ++i) {
           auto found = map.find(target - nums.at(i));
           if (found != map.end()) {
               v.at(0) = found->second + 1;
               v.at(1) = i + 1;
               return v;
           }
           map.insert(pair<int, int>(nums.at(i), i));
       }
       return v;
    }
    

    时间复杂度O(n) ,空间复杂度O(n)。

283. 移动零

  1. 一次遍历法

    使用双指针,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部。(参考了快速排序的思想)

    int i = 0, j = 0;
       while(j < nums.size()) {
       if(nums[j]) {
           swap(nums[i], nums[j]);
           i++;
       }
       j++;
    }
    

    时间复杂度为O(n), 空间复杂度为O(1)。

  2. 两次遍历法

    第一次遍历记录非0的个数,第二次遍历将末尾填0。

    int count = 0;
    for(int i = 0; i < nums.size(); i++) {
       if(nums[i]) nums[count++] = nums[i];
    }
    for(int i = count; i < nums.size(); i++) {
       nums[i] = 0;
    }
    

    时间复杂度为O(n),空间复杂度为O(1)。

额外学到

  • unordered_map容器是使用hashTable实现的,平均而言,搜索、插入和删除的时间复杂度为 O(1)。

220914-双指针

344. 反转字符串

557. 反转字符串中的单词 III

220915-双指针

876. 链表的中间结点

  1. 单指针法

    双次遍历,第一次count,第二次指向中间节点。

    int count = 0;
    ListNode* i= head;
    while(i != NULL) {
       count++;
       i = i->next;
    }
    int mid = count/2;
    ListNode* j = head;
    while(mid--) {
       j = j->next;
    }
    return j;
    

    时间复杂度为O(N),空间复杂度为O(1),只需要常数空间存放变量和指针。

  2. 建立指针数组法

    vector<ListNode*> arr;
    while(head != NULL) {
       arr.push_back(head);
       head = head->next;
    }
    return arr[arr.size() / 2];
    

    时间复杂度为O(N),空间复杂度为O(N)。

  3. 快慢指针法

    slow指针走一步,fast指针走两步。

    ListNode* slow = head;
    ListNode* fast = head;
    while(fast != NULL && fast->next != NULL) {
       slow = slow->next;
       fast = fast->next->next;
    }
    return slow;
    

    时间复杂度为O(N),空间复杂度为O(1),只需常数空间存放两个指针。

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

本题的一种情况是删除头指针,对此需要做额外的处理。在对链表进行操作时,一种常见的作法是引入一个哑节点,其next指向头指针,这样就不用再对删除头指针的情况做额外的处理了。

  1. 单指针法(对标上题的单指针法)

    ListNode* dummy = new ListNode(0, head);
    ListNode* i = dummy;
    // count
    int num = count - n - 1;
    while(num--) {
       i = i->next;
    }
    i->next = i->next->next;
    head = dummy->next; // 注意这里不能直接返回head
    delete(dummy);
    return head;
    

    时间复杂度为O(N),空间复杂度为O(1)。

  2. 栈(对标上题的指针数组法)

    将节点全部入栈,在出栈N个节点后,栈顶指针即为被删元素的前一个节点。

    ListNode* dummy = new ListNode(0, head);
    stack<ListNode*> stk;
    ListNode* cur = dummy;
    while(cur) {
       stk.push(cur);
       cur = cur->next;
    }
    while(n--) {
       stk.pop();
    }
    cur = stk.top();
    cur->next = cur->next->next;
    head = dummy->next;
    delete(dummy);
    return head;
    

    时间复杂度为O(N),空间复杂度为O(N)。

  3. 双指针法(对标上题的快慢指针法)

    ListNode* dummy = new ListNode(0, head);
    ListNode* j = dummy;
    ListNode* i = dummy;
    while(n--) j = j->next;
    while(j->next != NULL) {
      i = i->next;
      j = j->next;
    }
    i->next = i->next->next;
    head = dummy->next;
    delete(dummy);
    return head;
    

    时间复杂度为O(N),空间复杂度为O(1)。

周赛310

2404. 出现最频繁的偶数元素

使用unordered_map。

本周总结

本周依旧专注于双指针的算法题,其中167. 两数之和 II - 输入有序数组的首尾指针法和876. 链表的中间结点的快慢指针法都给我提供了很大的灵感。

本周刷的题依然不多,但贵在坚持,冲冲冲!