Bootstrap

双指针算法大总结!!看完这篇文章再也没有难的双指针题!!例题C++解法!

讲解双指针

使用双指针是降低算法复杂度的一个有效途径,有些问题的暴力解法时间O(n2),但是使用双指针可以大幅度降低算法复杂度。
和贪心算法一样,双指针难在想不到
常用的双指针法有以下几类:
左右指针:两个指针,相向而走,中间相遇
快慢指针:两个指针,有快有慢,同向而行
灵活运用:两个指针,灵活运用,伺机而动
下面从例题里感受这三类情况

题目一二是左右指针
题目三四是快慢指针

题目一:盛最多水的容器

题源力扣11.点击跳转力扣

给定一个长度为 n 的整数数组 height 。
有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。
在这里插入图片描述
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

思路

最优解法为左右双指针算法,双指针法的难点在于难以想到也难以证明,需要大量的刷题去练习题感
暴力法:
找出每一种情况,求出盛水值,最大的就是答案

  1. i 指左挡板,从第一块到遍历倒数第二块
  2. j 指右挡板,从倒数第一块遍历到 i 后面一块
  3. res 保存最大值,不断更新
  4. 返回 res
class Solution{
publicint maxArea(vector<int>& height){
		if(height.size() <= 1) return 0;
		int res = 0;
		for(int i = 0; i < height.size() - 1; i ++)//以 i 为左挡板,从 0 开始
			for(int j = height.size() - 1; j > i; j --){
				int l = j - i;//底边
				int h = min(height[i], height[j]);
				res = max(res, l * h);
			}
		return res;
	}
}

答案正确,但是会超时,双指针算法还有可以优化的空间

优化:
假设S[l,r]为左挡板为l,右挡板为r的盛水值,那么我们有多少种情况?
在这里插入图片描述
再回顾一下,height数组[1,8,6,2,5,4,8,3,7]
暴力法里面,我们将所有情况都计算了一遍,实际上有很多情况不需要计算,因为一定不是答案
假设现在挡板是S[0,8],左高度是1,右高度是7,其中低一点高度的是1,现在移动右挡板还有意义吗?
没有意义
只要是向内移动右挡板,底边长一定变短,如果移动后高度比1高,就会变成底边长变短,高度不变,盛水量变小
只要是向内移动右挡板,底边长一定变短,如果移动后高度比1低,就会变成底边长变短,高度变低,盛水量变小

那么移动左挡板有意义吗?
有意义
只要是向内移动左挡板,底边长一定变短,移动后如果比1高,就会是底边长变短,高度变高,这种情况值得计算!

所以总结一下就是,每一种S[l,r]的情况下,计算后需要比较 l 和 r 的高度,如果height[l] < height[r]就执行 l ++
如果height[l] > height[r]就执行 r –

AC代码

class Solution {
public:
    int maxArea(vector<int>& height) {
        int l = 0;
        int r = height.size() - 1;
        int res = 0;
        while(l < r){
            res = max(min(height[l], height[r]) * (r - l), res);
            if(height[l] < height[r]){
                l ++;
            }else{
                r --;
            }
        }
        return res;
    }
};

题目二:查找总价格为目标值的两个商品

题源力扣179.点击跳转力扣

购物车内的商品价格按照升序记录于数组 price。请在购物车中找到两个商品的价格总和刚好是 target。若存在多种情况,返回任一结果即可。

思路

题目很容易想到双指针,但是如果不留意冗余情况也容易超时
暴力法:
穷举每一种情况,如果两数之和等于target,就输出答案

  1. i 指向第一个数,从 0 到 price,size() - 2
  2. j 指向第二个数,从price.size() -1 到 i + 1
  3. 如果price[i] + price[j] == target,返回price[i], price[j]]
    不贴代码了,因为我感觉没人会真的暴力吧,不会吧不会吧(罒ω罒)

优化
暴力法显然没有考虑到数组有序的条件
小时候听说过1+2+3+…+99,问有多少个100,巧妙算法咋算的?是不是头加尾,1 + 99, 2 + 98…
这题也是一样的
j 应该从price.size() - 1开始
不多说了,这题很容易想

AC代码

class Solution {
public:
    vector<int> twoSum(vector<int>& price, int target) {
        int j = price.size() - 1;
        for(int i = 0; i < price.size() - 2; i ++){
            while((price[i] + price[j]) > target) j --;
            if(price[i] + price[j] == target) return vector<int>{price[i], price[j]};
        }
        return {-1, -1};
    }
};

题目三:链表的中间节点

题源力扣876.点击跳转力扣

给你单链表的头结点 head ,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
在这里插入图片描述
这题是快慢指针,快慢指针是指移动的步长,即每次向前移动速度的快慢。(例如,让快指针每次前移动2步,慢指针每次向前移动1步)。

思路

暴力法:

  1. 用一个指针遍历一遍链表,求出链表的最后一个节点编号n
  2. 中间节点为n/2,向上取整
  3. 用一个指针从表头往后走n/2向上取整步,指向的节点就是答案
class Solution{
public:
	ListNode* middleNode(ListNode* head) {
		if(!head) return NULL;
		int n = 0;
		ListNode *p = head;
		while(p->next){
			n ++;
			p = p->next;
		}
		int mid = (n + 1) / 2;
		p = head;
		while(mid --){
			p = p->next;
		}
		return p;
	}
}

暴力法可以AC,但是还有优化的空间

优化:
快慢指针,想象两个人跑步,一个人速度是另一个人的两倍,当速度快的到达终点的时候,另一个一定在中间,公式S=tv,t相同,v1 = 2*v2,那么路程也是两倍

AC代码

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
       if(!head) return NULL;
       ListNode *fast, *slow;
       fast = head;
       slow = head;
       while(fast->next)
       {
           fast = fast->next;
           if(fast->next) fast = fast->next;//fast走两步
           slow = slow->next;//slow走一步
       }
       return slow;
    }
};

题目四:环形链表

题源力扣141.点击跳转力扣

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。
在这里插入图片描述

思路

暴力法
遍历所有节点,每次遍历到一个节点时,判断该节点此前是否被访问过。

具体地,使用哈希表来存储所有已经访问过的节点。每次到达一个节点,如果该节点已经存在于哈希表中,则说明该链表是环形链表,否则就将该节点加入哈希表中。重复这一过程,直到遍历完整个链表即可。

class Solution {
public:
    bool hasCycle(ListNode *head) {
        unordered_set<ListNode*> h;//保存访问过的节点
        while (head != nullptr) {
            if (h.count(head)) {//如果当前访问节点在h中,则有换
                return true;
            }
            h.insert(head);//将当前节点放入h
            head = head->next;
        }
        return false;
    }
};

暴力法也能AC

优化
快慢指针做,一个人快一个人慢,如果有圈,两个人必然相遇,一个人跑两圈,第二个人跑一圈,会在起始位置相遇

AC代码

class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head == NULL || head->next == NULL) return false;
        ListNode *fast, *slow;
        slow = head;
        fast = head->next;
        while(slow != fast){
            if(fast == NULL || fast->next == NULL) return false;
            fast = fast->next->next;
            slow = slow->next;
        }
        return true;
    }
};

题目五:环形链表II

题源力扣142.点击跳转力扣
在这里插入图片描述

思路

和I的区别就是如果有环需要返回入环第一个节点
其实这题我学过了
但是我又忘记了
啊啊啊小dream这次好好学,可不能再忘了o(╥﹏╥)o
在这里插入图片描述
现在假设slow和fast在圈内相遇了,slow走的路程是x+y,fast走的路程是x+y+n(z+y),其中n是圈数且大于等于1,因为fast速度是slow的2倍,所以它的节点数一定是slow的两倍,所以可以得到公式:x+y == n(z+y)。
现在需要知道环形的入口,所以需要知道x的长度,x = n(z+y)-y=(n-1)(z+y)+z
当n = 1的时候,x=z,意味着从头结点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点。
当n > 1的时候,就是fast指针在环形转n圈之后才遇到 slow指针。其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,fast指针在环里 多转了(n-1)圈,然后再遇到slow,相遇点依然是环形的入口节点。

我有时候觉得编程就是数学(* ̄︶ ̄)

AC代码

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;
                ListNode* index2 = head;
                while (index1 != index2) {
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index2; // 返回环的入口
            }
        }
        return NULL;
    }
};

题目六:反转字符串中的元音字符

题源力扣345.点击跳转力扣

给你一个字符串 s ,仅反转字符串中的所有元音字母,并返回结果字符串。
元音字母包括 ‘a’、‘e’、‘i’、‘o’、‘u’,且可能以大小写两种形式出现不止一次。
在这里插入图片描述

思路

双指针法:
定义指针i, j, 初始化时指针i = 0, j = s.length() - 1;
从指针i开始从左到右遍历以找到第一个元音字符,从指针j开始从右到左遍历以找到第一个元音字符;
交换指针i与指针j锁指向的元音字符即可;
指针i和指针j在遍历过程中要注意数组越界情况。

AC代码

class Solution {
public:
    bool isVowel(char c){
        return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' || c =='A' || c == 'E' || c == 'I' || c == 'O' || c == 'U';
    }
    string reverseVowels(string s) {
        int i = 0;
        int j = s.size() - 1;
        while(i < j){
            if(!isVowel(s[i])){
                i ++;
                continue;
            }
            if(!isVowel(s[j])){
                j --;
                continue;
            }
            swap(s[i ++], s[j --]);

        }
        return s;
    }
};

题目七:最长连续不重复子序列

题源acwing799点击跳转acwing

给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
在这里插入图片描述

思路

  1. 遍历数组a中的每一个元素 a[i],对于每一个 i ,找到j使得双指针 [j,i] 维护的是以a[i]结尾的最长不重复子序列,长度为 i - j + 1,将这一长度与r的较大者更新给r。
  2. 对于每一个 i ,如何确定 j 的位置:由于[j,i - 1]是前一步得到的最长连续不重复子序列,所以如果[j,i]中有重复元素,一定是a[i],因此右移 j 一直到 a[i] 不重复为止,(由于[j, i - 1]已经是前一步的最优解,此时j只可能右移以剔除重复元素a[i],不可能左移增加元素,因此,j具有“单调性”、本题可用双指针降低复杂度)。
  3. 用数组 s 记录子序列 a[j ~ i] 中各元素出现次数,遍历过程中对于每一个 i 有四步操作:cin元素a[i] -> 将a[i]出现次数s[a[i]]加1 -> 若a[i]重复则右移j(s[a[j]]要减1) -> 确定j及更新当前长度i - j + 1给r。

细节:当a[i]重复时,先把a[j]次数减1,再右移j。

AC代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N];
int s[N];
int main(){
    int n;
    int r = 0;
    cin >> n;
    for(int i = 0, j = 0; i < n; i ++){
        cin >> a[i];
        s[a[i]] ++;//记录个数
        while(s[a[i]] > 1){
            -- s[a[j ++]];
        }
        r = max(r, i - j + 1);
    }
    cout << r;
}

题目八:判断子序列

在这里插入图片描述

思路

双指针算法
这题能扩展一种动态规划算法

  1. j 指针用来扫描整个b数组,i 指针用来扫描a数组。若发现a[i] == b[j],则让 i 指针后移一位。
  2. 整个过程中,j 指针不断后移,而i指针只有当匹配成功时才后移一位,若最后若 i == n,则说明匹配成功。

动态规划
bool dp[i][j] = true 表示a数组0 ~ i的序列是b数组0 ~ j序列的子序列
递推公式:
if(a[i - 1] == b[j - 1]) dp[i][j] = dp[i - 1][j - 1]
else dp[i][j] = dp[i][j - 1],i是不能减的,无论无何都要确保a数组的所有元素都在b数组中,所以只能b退一步去比较,

AC代码

//双指针
#include<iostream>
using namespace std;
const int N=1e5+10;
int a[N],b[N];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 0;i < n; i++) scanf("%d",&a[i]);
    for(int j = 0;j < m; j++) scanf("%d",&b[j]);

    int i = 0;
    for(int j = 0;j < m; j++)
    {
        if(i < n&&a[i] == b[j])  i++;
    }
    if(i == n) puts("true");
    else puts("false");
    return 0;
}
//动态规划
class Solution {
public:
    bool isSubsequence(string s, string t) {
        int lens = s.size();
        int lent = t.size();
        vector<vector<bool>> dp(lens + 1, vector<bool>(lent + 1, false));
        for(int i = 0; i <= lent; i ++) dp[0][i] = true;
        for(int i = 1; i <= lens; i ++){
            for(int j = 1; j <= lent; j ++){
                if(s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1];
                else dp[i][j] = dp[i][j - 1];
            }
        }
        return dp[lens][lent];
    }
};

题目九:移除元素

题源力扣27.点击跳转力扣
在这里插入图片描述

思路

暴力法
做法就是 i 去遍历整个数组,遇到nums[i] == val就定义j,将其覆盖
双指针法
通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置

快指针比慢指针快一步,当遇到需要删除的节点时,快指针将值赋给慢指针

AC代码

//暴力
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int size = nums.size();
        for (int i = 0; i < size; i++) {
            if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
                for (int j = i + 1; j < size; j++) {
                    nums[j - 1] = nums[j];
                }
                i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
                size--; // 此时数组的大小-1
            }
        }
        return size;
    }
};
//双指针
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){
                nums[slow] = nums[fast];
                slow ++;
            }
        }
        return slow;//slow才标志着数组的大小,实际数组并没有处理所有元素,必须返回slow而不是nums.size()
    }
};

题目十:替换数字

题源卡码网.点击跳转
在这里插入图片描述

思路

使用双指针法,不使用额外的辅助空间

首先扩充数组到每个数字字符替换成number之后的大小。例如 字符串 “a5b” 的长度为3,那么 将 数字字符变成字符串 “number” 之后的字符串为 “anumberb” 长度为 8。
然后从后向前替换数字字符,也就是双指针法,i指向新长度的末尾,j指向旧长度的末尾。
如果nums[j] 为需要被替换的数字的话,nums[i] 就开始倒着填充number
如果nums[j] 不是需要被替换的数字的话,nums[i] 就赋值 nums[j]

AC代码

#include<bits/stdc++.h>
using namespace std;
int main(){
	string s;
	while(cin >> s){
		int sOldIndex = s.size() - 1;
		int count = 0;
		for(int i = 0; i < s.size(); i ++){
			if(s[i] >= '0' && s[i] <= '9')
				count ++;
		}
		s.resize(s.size() + count * 5);//将s扩充大小
		int sNewIndex = s.size() - 1;
		while(sOldIndex >= 0){
			if(s[sOldIndex] >= '0' && s[sOldIndex] <= '9'){
				s[sNewIndex--] = 'r';
				s[sNewIndex--] = 'e';
				s[sNewIndex--] = 'b';
				s[sNewIndex--] = 'm';
				s[sNewIndex--] = 'u';
				s[sNewIndex--] = 'n';
			}else{
				s[sNewIndex--] = s[sOldIndex];
			}
			sOldIndex--;
		}
		cout << s << endl;
	}
}		

题目十一:翻转字符串里的单词

题源力扣151.点击跳转力扣
在这里插入图片描述

思路

思路为:
移除多余空格->将整个字符串反转->将每个单词反转

  1. 移除空格,使用和移除元素一样的方法,使用fast和slow指针
   void removeExtraSpace(string& s){
        int slow = 0, fast = 0;
        // 跳过前导空格
        while (fast < s.size() && s[fast] == ' ') fast++;
        // 处理中间的空格
        for (; fast < s.size(); fast++) {
            if (fast > 0 && s[fast] == ' ' && s[fast - 1] == ' ') continue;
            s[slow++] = s[fast];
        }
        // 处理尾部空格
        if (slow > 0 && s[slow - 1] == ' ') slow--;
        s.resize(slow);
    }
  1. 翻转字符串,两个指针分别头尾,swap即可
void reverse(string& s, int start, int end){
	for(int i = start, j = end; i < j; i ++, j --) swap(s[i], s[j]);

AC代码

class Solution {
public:
    void reverse(string& s, int start, int end){
        while (start < end) {
            swap(s[start++], s[end--]);
        }
    }
    void removeExtraSpace(string& s){
        int slow = 0, fast = 0;
        // 跳过前导空格
        while (fast < s.size() && s[fast] == ' ') fast++;
        // 处理中间的空格
        for (; fast < s.size(); fast++) {
            if (fast > 0 && s[fast] == ' ' && s[fast - 1] == ' ') continue;
            s[slow++] = s[fast];
        }
        // 处理尾部空格
        if (slow > 0 && s[slow - 1] == ' ') slow--;
        s.resize(slow);//重要!!因为size已经改变
    }

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

题目十二:反转链表

题源力扣206.点击跳转力扣
题意:反转一个单链表。

示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

思路

不应该开辟新的内存空间
实际上,根本不用移动元素
改变指针方向即可
->变成<-

不是很双指针
注意:cur一定要从head就开始!!!!head是第一个元素

AC代码

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* temp;
        ListNode* pre = nullptr;
        ListNode* cur = head;
        while(cur){
            temp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;

    }
};

题目十三:删除链表的倒数第N个节点

题源力扣点击跳转
删除链表倒数第n个节点,并返回头结点
只用一趟扫描,如何实现?

思路

思考一下快慢指针的应用
如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
fast始终比slow快n个,也就是他俩间隔n个元素,当fast到达链表尾部,slow指向的位置就是链表倒数第n个节点

AC代码

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* slow = dummyHead;
        ListNode* fast = dummyHead;
        for (int i = 0; i <= n; ++i) {
            fast = fast->next;
        }
        while (fast != NULL) {
            slow = slow->next;
            fast = fast->next;
        }
        slow->next = slow->next->next;
        return dummyHead->next;
    }
};

题目十四:链表相交

题源力扣。点击跳转力扣
在这里插入图片描述

思路

求出两个链表分别的长度,再求出差值,让更长的链表先移动这个差值的距离
接着两个一起移动
如果两个指针指向了同一个节点,就返回这个节点

注意:在返回之前,必须要先判断两个节点是不是一样,需不需要返回。再移动,不能反

AC代码

/**
 * 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) {
        ListNode* p1 = headA;
        ListNode* p2 = headB;
        int lenA = 0;
        int lenB = 0;
        while(p1 != NULL){
            lenA ++;
            p1 = p1->next;
        }
        while(p2 != NULL){
            lenB ++;
            p2 = p2->next;
        }
        p1 = headA;
        p2 = headB;
        if(lenA < lenB){//较长的那一边是lenA和p1
            swap(lenA, lenB);
            swap(p1, p2);
        }
        int gap = lenA - lenB;
        while(gap --) p1 = p1->next;
        for(int i = 0; i < lenB; i ++){
            if(p1 == p2) return p1;
            p1 = p1->next;
            p2 = p2->next; 
        }
        return NULL;
    }
};

题目十五:三数之和

在这里插入图片描述
题源力扣传送到力扣

思路

题目二可以理解为两数之和
这个题目是三数之和,做法是两数之和上面的进阶,固定1个数,剩下两个数用两数之和双指针做法即可
当然首先得sort一下
逻辑:固定i->固定j->找到k->res.push_back->固定第二个j->找到k…
a的去重
是判断nums[i] 与 nums[i + 1]是否相同,还是判断 nums[i] 与 nums[i-1] 是否相同呢?
一开始可能会觉得,这有啥子区别嘛
区别大了,如果我们判重条件是if (nums[i] == nums[i + 1]) continue的话,那么{-1,-1,2}这组数据就被直接continue没了
a去重的重心是,不能有重复的三元组,但三元组内的元素是可以重复的
所以写法应该是:if(i > 0 && nums[i] == nums[i - 1]) continue,i > 0是保证nums[]有效

b和c的去重
常规去重
是在移动left和right的时候,判断nums[right]是否等于nums[right + 1],如果相等的话,就没必要用nums[right]了,因为用过了
但这种去重其实对提升程序运行效率是没有帮助的。这种去重是可以不加的。

AC代码

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        //双指针做法必须有序,不能去重,因为可能有-1 -1 2这样的序列
        sort(nums.begin(),nums.end());
        for(int i=0;i<nums.size();i++){//枚举一个i,j和k使用双指针做法
            //如果和上一个一样就可以跳过
            if(i&&nums[i]==nums[i-1]) continue;//i不能是起始位置,因为要和之前一个比较是否相等
            for(int j=i+1,k=nums.size()-1;j<k;j++){//双指针一头一尾
                if(j>i+1&&nums[j]==nums[j-1]) continue;//j不能是起始位置,因为要和之前一个比较是否相等
                while(j<k&&nums[i]+nums[j]+nums[k]>0) k--;//j<k必须带着,因为除了for语句以外,while会对k操作,for语句没办法维护这里
                if(j<k&&nums[i]+nums[j]+nums[k]==0) res.push_back({nums[i],nums[j],nums[k]});//j<k必须带着,毕竟代码走到这里k已经有改动,for无法维护啦
            }
        }
        return res;
    }
};

题目十六:四数之和

在这里插入图片描述
题源力扣传送过去

思路

ok啊老弟,看我手势,再干最后一题嗷。干完这16题,再也没有难题啦,接下来就是codeTop刷刷刷,妈的刷穿高频题!!刷穿地心!!!!

经过了三数之和的洗礼,想必四数之和拿着就有想法了
有一点要注意:这里四个nums相加,-109 <= nums[i] <= 109
int的最大是231,10为数,2开头(我就记得这个)
109已经是10位数,4个相加就是4开头的十位数,完全可以超过int,所以这里的和应该用(long long)

AC代码

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        sort(nums.begin(), nums.end());
        vector<vector<int>> res;
        for(int i = 0; i < nums.size(); i ++){
            if(i && nums[i] == nums[i - 1]) continue;
            for(int j = i + 1; j < nums.size(); j ++){
                if(j > i + 1 && nums[j] == nums[j - 1]) continue;
                for(int k = j + 1, u = nums.size() - 1; k < u; k ++){//双指针部分,只需要一个for
                    if(k > j + 1 && nums[k] == nums[k - 1]) continue;           
                    while(k < u && (long long)nums[i] + nums[j] + nums[k] + nums[u] > target) u--;
                    if(k < u && (long long)nums[i] + nums[j] + nums[k] + nums[u] == target) res.push_back({nums[i], nums[j], nums[k], nums[u]});
                }
            }
        }
        return res;
    }
};

2024/8/1
如果后续碰到更经典的双指针题目
还会更新在里面ヾ(◍°∇°◍)ノ゙

更新8/4

题目十七:合并两个有序链表

力扣21
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

思路

没有要求原地合并,所以新建链表
创头结点->p指针安在头结点上

AC代码

public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        //虚拟头节点,以此为基础建立新链表
        ListNode* dummy = new ListNode(0);
        ListNode* p = dummy;
        //p1和p2
        ListNode* p1 = list1;
        ListNode* p2 = list2;
        while(p1 && p2){
            if(p1->val < p2->val){
                p->next = p1;
                p1 = p->next->next;          
            }else{
                p->next = p2;
                p2 = p->next->next;       
            }
            p = p->next;
        }
        if(p1!= NULL){
            p->next = p1;
        }
        if(p2 != NULL){
            p->next = p2;
        }
        return dummy->next;
    }
};

题目十八:分隔链表

在这里插入图片描述
题目描述特别怪,简单来说这里需要分解让你把原链表一分为二。具体来说,我们可以把原链表分成两个小链表,一个链表中的元素大小都小于 x,另一个链表中的元素都大于等于 x,最后再把这两条链表接到一起,就得到了题目想要的结果。

思路

和题目十七差不多,建两个虚拟头结点
也就是搞两个链表

有一点要注意cur指针,也就是在主链上的指针,在前进之前需要断开
不然就会有节点有两根线连着他

AC代码

class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        ListNode* dummy1 = new ListNode(0);
        ListNode* p1 = dummy1;

        ListNode* dummy2 = new ListNode(0);
        ListNode* p2 = dummy2;

        ListNode* cur = head;
        while(cur){
            if(cur->val < x){
                p1->next = cur;
                p1 = p1->next;
            }else{
                p2->next = cur;
                p2 = p2->next;
            }
            ListNode* temp = cur;
            cur = cur->next;
            temp->next = NULL;
        }
        p1->next = dummy2->next;
        delete dummy2;
        return dummy1->next;

    }
};
;