代码随想录刷题笔记5——双指针

移除元素

例题27(简单)移除元素

注意要点:

  1. 在数组专题中已经有涉及,直接快慢指针,如果碰到要删除的元素,就直接快指针右移跳过;否则就把快指针的值赋值给慢指针,然后一起右移。

下面贴出代码:

CPP版本

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

C版本

int removeElement(int* nums, int numsSize, int val){
    int slow = 0;
    for (int fast = 0; fast < numsSize; fast++)
    {
        if (nums[fast] == val) {continue;}
        else {nums[slow++] = nums[fast];}
    }
    return slow;
}

反转字符串

例题344(简单)反转字符串

注意要点:

  1. 相向双指针,交换当前两指针的值后,均向内移动一位。

下面贴出代码:

CPP版本

class Solution {
public:
    void reverseString(vector<char>& s) {
        for (int i = 0; i < s.size() / 2; i++)
        {
            int tmp = s[i];
            s[i] = s[s.size() - 1 - i];
            s[s.size() - 1 - i] = tmp;
        }
    }
};

C版本

void reverseString(char* s, int sSize){
    for (int i = 0; i < sSize / 2; i++)
    {
        int tmp = s[i];
        s[i] = s[sSize - 1 - i];
        s[sSize - 1 - i] = tmp;
    }
}

替换空格

例题 剑指Offer05(简单)替换空格

注意要点:

  1. 需要注意替换的方向:从前向后就是O(n^2)(还需要在填充后进行移位操作);而预先扩充数组空间后从后向前就仅有O(n)。

下面贴出代码:

CPP版本

class Solution {
public:
    string replaceSpace(string s) {
        //统计空格数量
        int count = 0;
        for (int i = 0; i < s.size(); i++)
        {
            if (s[i] == ' ') {count++;}
        }
        //扩充为所需的数组大小
        int oldSize = s.size();
        s.resize(s.size() + 2 * count);
        int newSize = s.size();
        for (int i = newSize - 1, j = oldSize - 1; i >= 0, j >= 0; i--, j--)
        {
            if (s[j] == ' ')
            {
                s[i--] = '0';
                s[i--] = '2';
                s[i] = '%';
            }
            else {s[i] = s[j];}
        }
        return s;
    }
};

C版本

char* replaceSpace(char* s){
    int size = 0;
    for (int i = 0; i < strlen(s); i++)
    {
        if (s[i] != ' ') {size++;}
        else {size += 3;}
    }
    char* ans = (char* )malloc(sizeof(char) * (size + 1));
    int ptr_s = strlen(s) - 1, ptr_ans = size;
    ans[ptr_ans] = '\0';
    ptr_ans--;
    while (ptr_s >= 0)
    {
        if (s[ptr_s] != ' ')
        {
            ans[ptr_ans] = s[ptr_s];
            ptr_s--;
            ptr_ans--;
        }
        else
        {
            ans[ptr_ans] = '0';
            ans[ptr_ans-1] = '2';
            ans[ptr_ans-2] = '%';
            ptr_ans -= 3;
            ptr_s--;
        }
    }
    return ans;
}

翻转字符串中的单词

例题151(中等)反转字符串中的单词

注意要点:

  1. 最重要的思路:首先先删掉多余的空格,反转整个字符串,然后寻找到每一个单词,再把单词翻转
  2. 删除多余的空格,使用快慢指针就可以,类似之前的移除元素;

下面贴出代码:

CPP版本

class Solution {
public:
    void myreverse(string& s, int start, int end)
    {
        int mid = start + (end - start) / 2;
        for (int i = start; i < mid; i++)
        {
            swap(s[i], s[end - 1 - (i - start)]);
        }
    }

    void removeExtraSpaces(string& s)
    {
        int slow = 0;
        for (int i = 0; i < s.size(); i++)
        {
            if (s[i] != ' ')
            {
                if (slow) {s[slow++]  = ' ';}
                while (i < s.size() && s[i] != ' ') {s[slow++] = s[i++];}
            }
        }
        s.resize(slow);
    }

    string reverseWords(string s) {
        removeExtraSpaces(s);
        myreverse(s, 0, s.size());
        int start = 0;
        for (int i = 0; i <= s.size(); i++)
        {
            if (i == s.size() || s[i] == ' ')
            {
                myreverse(s, start, i);
                start = i + 1;
            }
        }
        return s;
    }
};

C版本

char * reverseWords(char * s){
    int slow = 0;
    for (int i = 0; i < strlen(s); i++)
    {
        if (s[i] != ' ')
        {
            if (slow) {s[slow++]  = ' ';}
            while (i < strlen(s) && s[i] != ' ') {s[slow++] = s[i++];}
        }
    }
    s[slow] = '\0';

    int left = 0, right = strlen(s) - 1;  // 当前头部仍可能有空格时的字符串长度
    while (left < right)
    {
        char tmp = s[right];
        s[right] = s[left];
        s[left] = tmp;
        left++;
        right--;
    }
    int len_tmp = strlen(s);

    int len = strlen(s);
    // 接下来翻转单词
    int word_left = 0;
    for (int i = 0; i < len + 1; i++)
    {
        if (s[i] == ' ' || s[i] == '\0')
        {
            int word_right = i - 1;
            while (word_left < word_right)
            {
                char tmp = s[word_right];
                s[word_right] = s[word_left];
                s[word_left] = tmp;
                word_left++;
                word_right--;
            }
            word_left = i + 1;
        }
    }
    return s;
}

翻转链表

例题206(简单)翻转链表

注意要点:

  1. 翻转的思路是找到当前节点的前一个节点,并修改当前节点的next指针,然后向后移动一位;所以需要保存pre以及cur->next辅助计算

下面贴出代码:

CPP版本

/**
 * 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:
    ListNode* reverseList(ListNode* head) {
        ListNode* cur = head;
        ListNode* pre = nullptr;
        while (cur)
        {
            ListNode* tmp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
};

C版本

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* reverseList(struct ListNode* head){
    struct ListNode* dummy = (struct ListNode* )malloc(sizeof(struct ListNode));
    dummy->next = head;
    struct ListNode* cur = dummy->next;
    struct ListNode* pre = NULL;
    while (cur)
    {
        struct ListNode* tmp = cur->next;
        cur->next = pre;
        pre = cur;
        cur = tmp;
    }
    return pre;
}

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

例题19(中等)删除链表的倒数第N个节点

注意要点:

  1. 避免删除的是头结点,所以设置虚拟头节点辅助计算;
  2. 通过快慢指针,快指针移动(n+1)次,再与慢指针一起移动直至链表尾部,慢指针所在位置即为倒数(n+1)所在
  3. 移动n+1次,就可以直接通过next来删除倒数第n个,避免还要记录pre节点。

下面贴出代码:

CPP版本

/**
 * 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:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        ListNode* slow = dummy;
        ListNode* fast = dummy;
        while (n--) {fast = fast->next;}
        fast = fast->next;
        while (fast)
        {
            slow = slow->next;
            fast = fast->next;
        }
        ListNode* tmp = slow->next;
        slow->next = slow->next->next;
        delete(tmp);
        tmp = nullptr;
        return dummy->next;
    }
};

C版本

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
    struct ListNode* dummyhead = malloc(sizeof(struct ListNode));
    dummyhead->next = head;
    struct ListNode* slow = dummyhead;
    struct ListNode* fast = dummyhead;
    while (n--) {fast = fast->next;}
    fast = fast->next;
    while (fast)
    {
        slow = slow->next;
        fast = fast->next;
    }
    slow->next = slow->next->next;
    return dummyhead->next;
}

链表相交

例题160(简单)链表相交

注意要点:

  1. 通过遍历长度,并使得两链表的当前指针对齐,进而可以同时移动指针判断是否相交
  2. 对齐方法:计算链表长度差,并在长链表指针移动长度差个次数,来对齐两个指针;
  3. 相交是地址相等,不能简单判断值相等。

下面贴出代码:

CPP版本

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int totalA = 0, totalB = 0;
        ListNode* curA = headA;
        ListNode* curB = headB;
        //统计两个链表的长度
        while (curA) {curA = curA->next; totalA++;}
        while (curB) {curB = curB->next; totalB++;}

        //移动到距离结尾相同距离处
        curA = headA, curB = headB;
        int sub = totalA - totalB;
        if (sub > 0)
        {
            while (sub--) {curA = curA->next;}
        }
        else {while (sub++) {curB = curB->next;}}

        //判断地址是否相同,而不是val是否相同
        while (curA)
        {
            if (curA == curB) {return curA;}
            else
            {
                curA = curA->next;
                curB = curB->next;
            }
        }
        return NULL;
    }
};

C版本

/**
 1. Definition for singly-linked list.
 2. struct ListNode {
 3.     int val;
 4.     struct ListNode *next;
 5. };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode* p1 = malloc(sizeof(struct ListNode));
    struct ListNode* p2 = malloc(sizeof(struct ListNode));
    p1->next = headA;
    p2->next = headB;
    int l1 = 0, l2 = 0;
    while (p1->next)
    {
        p1 = p1->next;
        l1++;
    }
    while (p2->next)
    {
        p2 = p2->next;
        l2++;
    }
    p1 = headA;
    p2 = headB;
    int delta = l1 - l2;
    // printf("delta = %d\n", delta);
    // printf("p1 = %d\n", p1);
    if (delta >= 0)
    {
        while(delta--) {p1 = p1->next;}
    }
    else
    {
        while(delta++) {p2 = p2->next;}
    }
    // printf("p1 = %d\n", p1);
    while (p1)
    {
        if (p1 == p2)
        {
            return p1;
        }
        p1 = p1->next;
        p2 = p2->next;
    }
    return NULL;
}

环形链表

例题142(中等)环形链表II

注意要点:

  1. 是否成环的判断:快慢指针,快指针走两步,慢指针走一步,如果能相遇,则证明成环;记录下当前节点;
  2. 寻找成环点快指针从当前节点出发,慢指针从头结点出发,移动相同步数,相遇点即为成环点。

这道题涉及到的数学推导可以自行退导一下,不乐意推导就直接记结论就可以做题了。

下面贴出代码:

CPP版本

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
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)  //有环
            {
                //从相遇节点,slow回到head,再次移动同样步数,再次相遇就是成环点
                slow = head;
                while (slow != fast)
                {
                    slow = slow->next;
                    fast = fast->next;
                }
                return slow;
            }
        }
        return NULL;
    }
};

C版本

/**
 1. Definition for singly-linked list.
 2. struct ListNode {
 3.     int val;
 4.     struct ListNode *next;
 5. };
 */
struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while (fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast)
        {
            while (slow != head)
            {
                slow = slow->next;
                head = head->next;
            }
            return head;
        }
    }
    return NULL;
}

三数之和

例题15(中等)三数之和

注意要点:

  1. 首先把数组变成单调数组,这样才可以简化后续的算法复杂度;
  2. for循环内部,通过单调的特性,进行双指针的简化复杂度算法;如果>target,那么right指针就左移来减小和;如果<target,那么left指针就右移来增大和

下面贴出代码:

CPP版本

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> ret;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size() - 2; i++)
        {
            //排序后的最小值已经是正的,不可能达到和为0
            if (nums[i] > 0) {return ret;}
            //剪枝操作,不可以从第一个开始,这样会使得答案有遗漏
            if (i && nums[i] == nums[i - 1]) {continue;}
            int left = i + 1, right = nums.size() - 1;
            while (left < right)
            {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum > 0) {right--;}
                else if (sum < 0) {left++;}
                else
                {
                    ret.push_back(vector<int>{nums[i], nums[left], nums[right]});
                    while (left < right && nums[left] == nums[left + 1]) {left++;}
                    while (left < right && nums[right] == nums[right - 1]) {right--;}
                    left++;
                    right--;
                }
            }
        }
        return ret;
    }
};

C版本

/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
 */

int cmp(const void* a, const void* b)
{
    return *(int* )a - *(int* )b;
}


int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){
    int** ans = (int** )malloc(sizeof(int* ) * 3000 * 6);
    *returnSize = 0;
    if (numsSize < 3) {return ans;}
    qsort(nums, numsSize, sizeof(int), cmp);

    for (int i = 0; i < numsSize - 2; i++)
    {
        if (nums[i] > 0) {break;}
        else
        {
            if (i && nums[i] == nums[i - 1]) {continue;}
            int left = i + 1, right = numsSize - 1;
            while (right > left)
            {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum > 0) {right--;}
                else if (sum < 0) {left++;}
                else
                {
                    int* arr = (int* )malloc(sizeof(int) * 3);
                    arr[0] = nums[i];
                    arr[1] = nums[left];
                    arr[2] = nums[right];
                    ans[(*returnSize)++] = arr;
                    while (right > left && nums[right] == nums[right - 1]) {right--;}
                    while (right > left && nums[left] == nums[left + 1]) {left++;}
                    right--;
                    left++;
                }
            }
        }
    }
    *returnColumnSizes = (int* )malloc(sizeof(int) * (*returnSize));
    for (int k = 0; k < (*returnSize); k++) {(*returnColumnSizes)[k] = 3;}
    return ans;
}

四数之和

例题18(中等)四数之和

注意要点:

  1. 方法与三数之和很类似,只不过for需要循环两次,注意剪枝和去重都可以写两次。

下面贴出代码:

CPP版本

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> ret;
        if (nums.size() < 4) {return ret;}
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size() - 3; i++)
        {
            if (nums[i] > target && nums[i] >= 0) {break;}
            if (i && nums[i] == nums[i - 1]) {continue;}
            for (int j = i + 1; j <nums.size() - 2; 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
                    {
                        ret.push_back(vector<int>{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--;}
                        left++;
                        right--;
                    }
                }
            }
        }
        return ret;
    }
};

C版本

/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
 */
int cmp(const void* a, const void* b)
{
    return *(int* )a - *(int* )b;
}

int** fourSum(int* nums, int numsSize, int target, int* returnSize, int** returnColumnSizes){
    int** res= (int** )malloc(sizeof(int* ) * 1001);
    *returnSize = 0;
    if (numsSize < 4) {return res;}
    qsort(nums, numsSize, sizeof(int), cmp);
    int len = numsSize;
    for (int i = 0; i < len - 3; i++)
    {
        if (nums[i] > target && nums[i] >= 0) {break;}
        if (i && nums[i] == nums[i - 1]) {continue;}
        for (int j = i + 1; j < len - 2; 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 = len - 1;
            while (left < right)
            {
                if ((long)nums[i] + nums[j] + nums[left] + nums[right] < target) {left++;}
                else if ((long)nums[i] + nums[j] + nums[left] + nums[right] > target) {right--;}
                else
                {
                    int* arr = (int* )malloc(sizeof(int) * 4);
                    arr[0] = nums[i];
                    arr[1] = nums[j];
                    arr[2] = nums[left];
                    arr[3] = nums[right];
                    res[(*returnSize)++] = arr;
                    while (left < right && nums[left] == nums[left + 1]) {left++;}
                    while (left < right && nums[right] == nums[right-1]) {right--;} 
                    left++;
                    right--;
                }
            }
        }
    }
    *returnColumnSizes = (int* )malloc(sizeof(int* ) * (*returnSize));
    for (int i = 0; i < (*returnSize); i++) {(*returnColumnSizes)[i] = 4;}
    return res;
}

总结

  1. 双指针法非常常用,只要涉及删改或者交换,都可以考虑是否需要用双指针法,包括有快慢指针,以及相向指针
  2. 双指针法用来降低时间复杂度,大多需要配合数组的预排序
  3. 反向操作就需要先记录pre,以及当前的cur形成双指针。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值