算法双指针系列(一)

75. 颜色分类

75. 颜色分类

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 012 分别表示红色、白色和蓝色。

必须在不使用库内置的 sort 函数的情况下解决这个问题。

示例 1:

输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

题意:就是把数组按照顺序0-1-2排序,不能用sort,要原地排序

原地排序就是仅使用常数级的额外空间,要直接在数组上进行排序

思路:三指针,j代表0,k代表2,1不用排序,0和2排好了1自动就好了

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int n=nums.size()-1;
        // j:0
        // k:2
        for(int i=0,j=0,k=n;i<=k;){
            if(nums[i]==0)swap(nums[i++],nums[j++]);// 当前数为0,扔到j
            else if(nums[i]==2)swap(nums[i],nums[k--]);// 当前数为2,扔到k
            else i++;
        }
    }
};
  • 时间复杂度:O( n n n)
  • 空间复杂度:O( 1 1 1)


80. 删除有序数组中的重复项 II

80. 删除有序数组中的重复项 II

前身:26. 删除有序数组中的重复项

输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3。 不需要考虑数组中超出新长度后面的元素。

题意:原地删除重复出现的数组,超过2次的只能出现2次

思路:模拟一遍,数字最多出现2次,超过的不要

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int k=0;
        for(auto& x:nums){
            //小于2个直接进
            //左边第1个不等于当前数
            //左边第2个不等于当前数
            //再多就超了,不加入
            if(k<2 || (nums[k-1]!=x || nums[k-2]!=x))nums[k++]=x;
        }
        return k;
    }
};
  • 时间复杂度:O( n n n)
  • 空间复杂度:O( 1 1 1)



82. 删除排序链表中的重复元素 II

82. 删除排序链表中的重复元素 II

img

题意:删掉重复的元素

思路:模拟一遍,把前置指针放在2位置(如上图),找到下一个不重复的数字,再将p的next指向它(预知下两个2节点判断是否重复)

/**
 * 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* deleteDuplicates(ListNode* head) {
        auto dummy=new ListNode();
        dummy->next=head;
        auto p=dummy;
        while(p->next){
            auto t=p->next->next;
            while(t && t->val==p->next->val)t=t->next;//预知下两个节点
            if(t==p->next->next)p=p->next;// 没有重复,p向下走
            else p->next=t;
        }
        return dummy->next;
    }
};
  • 时间复杂度:O( n n n)
  • 空间复杂度:O( 1 1 1)



86. 分隔链表

86. 分隔链表

img

输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]

题意:小于x的节点放在x前面,大于x的放在x后面

思路:搞两个指针分别串一下小于x的和大于等于x的链表,然后合并下

/**
 * 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* partition(ListNode* head, int x) {
        auto lh=new ListNode(),rh=new ListNode();
        auto lh1=lh,rh1=rh;
        for(auto p=head;p;p=p->next)
            if(p->val<x)lh=lh->next=p;
            else rh=rh->next=p;
        lh->next=rh1->next;
        rh->next=nullptr;
        return lh1->next;
    }
};
  • 时间复杂度:O( n n n)
  • 空间复杂度:O( 1 1 1)



88. 合并两个有序数组

88. 合并两个有序数组

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。

题意:将两个有序数组合并

思路:2个指针从2个数组从后向前放到nums1数组(从后向前是因为nums1后面没数据,不影响前面的数据)

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int k=m+n-1;
        int x=m-1,y=n-1;
        while(x>=0 && y>=0){
            if(nums1[x]>=nums2[y])nums1[k--]=nums1[x--];
            else nums1[k--]=nums2[y--];
        }
        while(y>=0)nums1[k--]=nums2[y--];
    }
};
  • 时间复杂度:O( n n n)
  • 空间复杂度:O( 1 1 1)



125. 验证回文串

125. 验证回文串

输入: s = "A man, a plan, a canal: Panama"
输出:true
解释:"amanaplanacanalpanama" 是回文串。

题意:去掉所有没用的东西,剩下来的字母小写化,判断是否是回文串

思路:全部变小写,串一遍,i从前面,j从后面,两边扫一遍

class Solution {
public:
    bool isPalindrome(string s) {
        for(int i=0,j=s.size()-1;i<j;i++,j--){
            while(i<j && !isalnum(tolower(s[i])))i++;
            while(i<j && !isalnum(tolower(s[j])))j--;
            if(i<j && tolower(s[i])!=tolower(s[j]))return false;
        }
        return true;
    }
};
  • 时间复杂度:O( n n n)
  • 空间复杂度:O( 1 1 1)



141. 环形链表

141. 环形链表

img

题意:判断是否有环

思路:快慢指针,最多转2圈就相遇

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(!head || !head->next)return false;
        auto l=head,f=head;// f=head->next也行,无所谓,最后肯定相遇
        while(f){
            l=l->next,f=f->next;
            if(!f)return false;
            f=f->next;
            if(l==f)return true;
        }
        return false;
    }
};
  • 时间复杂度:O( n n n)
  • 空间复杂度:O( 1 1 1)



142. 环形链表 II

142. 环形链表 II

题意:找入环点

思路:快慢指针,这种环形链表,这种吊题基本都是找入环点,快慢指针,肯定能找到,数学证明此处不展开,自行搜索

/**
 * 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) {
        if(!head)return NULL;
        auto slow=head,fast=head;
        while(fast){
            slow=slow->next,fast=fast->next;
            if(!fast)return NULL;
            fast=fast->next;
            if(slow==fast){
                slow=head;
                while(slow!=fast){//入环点,可以数学证明
                    slow=slow->next,fast=fast->next;
                }
                return fast;
            }
        }
        return NULL;
    }
};
  • 时间复杂度:O( n n n)
  • 空间复杂度:O( 1 1 1)



148. 排序链表

148. 排序链表

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        // 创建一个虚拟头节点,方便操作
        auto dummy = new ListNode(-1);
        dummy->next = head;

        // 计算链表的长度
        int n = 0;
        for (auto p = head; p; p = p->next) n++;

        // 外层循环:归并的步长从1开始,每次翻倍
        for (int i = 1; i < n; i *= 2) {
            auto cur = dummy; // cur用于连接合并后的链表
            // 内层循环:将链表分成大小为i的若干段,两两合并
            for (int j = 1; j + i <= n; j += i * 2) {
                auto p = cur->next, q = p; // p和q分别指向两段链表的起点
                // 将q移动到第二段的起点
                for (int k = 0; k < i; k++) q = q->next;

                int l = 0, r = 0; // l和r分别记录两段链表的合并进度
                // 合并两段链表
                while (l < i && r < i && p && q) {
                    if (p->val <= q->val) {
                        cur->next = p;
                        p = p->next;
                        l++;
                    } else {
                        cur->next = q;
                        q = q->next;
                        r++;
                    }
                    cur = cur->next;
                }
                // 处理剩余未合并的节点
                while (l < i && p) {
                    cur->next = p;
                    p = p->next;
                    l++;
                    cur = cur->next;
                }
                while (r < i && q) {
                    cur->next = q;
                    q = q->next;
                    r++;
                    cur = cur->next;
                }
                // 将cur连接到下一段的起点
                cur->next = q;
            }
        }
        // 返回排序后的链表头节点
        return dummy->next;
    }
};
  • 时间复杂度:O(n log n)
  • 空间复杂度 😮(1)



151. 反转字符串中的单词

151. 反转字符串中的单词

输入:s = "the sky is blue"
输出:"blue is sky the"

题意:反转整体顺序,然后反转局部顺序

思路:题目就是思路,模拟一下,当然先局部再整体也一样

class Solution {
public:
    string reverseWords(string s) {
        int k = 0; // 记录新字符串的写入位置
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == ' ') continue; // 跳过空格

            // 提取一个单词
            int a = i, b = k;
            while (a < s.size() && s[a] != ' ') s[b++] = s[a++];

            // 反转单词
            reverse(s.begin() + k, s.begin() + b);

            // 添加一个空格
            s[b++] = ' ';

            // 更新索引
            i = a;
            k = b;
        }

        // 去除最后一个多余的空格
        if (k) k--;

        // 删除剩余字符
        s.erase(s.begin() + k, s.end());

        // 反转整个字符串
        reverse(s.begin(), s.end());

        return s;
    }
};
  • 时间复杂度:O( n n n)
  • 空间复杂度:O( 1 1 1)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值