当前位置: 首页 > news >正文

【算法/学习】双指针

✨                                                  少年要迎着朝阳,活得肆无忌惮        🌏 

📃个人主页:island1314

🔥个人专栏:算法学习

🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏  💞 💞 💞

 


引言:

双指针(Two Pointers):指的是在遍历元素的过程中,不是使用单个指针进行访问,而是使用两个指针进行访问,从而达到相应的目的。如果两个指针方向相反,则称为「对撞指针」。如果两个指针方向相同,则称为「快慢指针」。如果两个指针分别属于不同的数组 / 链表,则称为「分离双指针」。

在数组的区间问题上,暴力算法的时间复杂度往往是O(n^2)。而双指针利用了区间「单调性」的性质,可以将时间复杂度降到 O(n)。

指针一般情况下将分为三种类类型,分别是:

类型特点
快慢指针两个指针步长不同,一般情况下,快的走两步,慢的走一步
对撞指针两个指针分别指向头尾,并往中间移动,步长不确定,一般为1
区间指针一般为滑动窗口,两个指针及其间距视作整体,窗口有定长有变长,每次操作窗口整体向右滑动

不管是哪一种双指针,只考虑双指针部分的话 ,由于最多还是会遍历整个数组一次,因此时间复杂度取决于步长,如果步长是 1,2 这种常数的话,那么时间复杂度就是 O(N),如果步长是和数据规模有关(比如二分法),其时间复杂度就是 O(logN)。并且由于不管规模多大,我们都只需要最多两个指针,因此空间复杂度是 O(1)。下面我们就来看看双指针的常见套路有哪些。

1. 对撞指针(相向双指针)

1.1 基本概念

对撞指针 :指的是两个指针 left 、 right 分别指向序列第一个元素和最后一个元素,然后 left 指针不断递增, right 不断递减,直到两个指针的值相撞(即 left == right ),或者满足其他要求的特殊条件为止。 对撞指针一般用于顺序结构中,也称为左右端点指针。

求解步骤:

  • 对撞指针从两端向中间移动。一个指针从最左端开始,另一个从最右端开始,然后逐渐往中间逼近。
  • 对撞指针的终止条件一般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循环),也就是:

                        (1) left == right(两个指针指向同一个位置)

                        (2) left > right(两个指针错开)

1.2 伪码框架

left, right = 0, len(nums) - 1while left < right:if 满足要求的特殊条件:return 符合条件的值 elif 一定条件 1:left += 1elif 一定条件 2:right -= 1return 没找到 或 找到对应值

1.3 适用范围

对撞指针一般用来解决有序数组或者字符串问题:

  • 查找有序数组中满足某些约束条件的一组元素问题:比如二分查找、数字之和等问题。
  • 字符串反转问题:反转字符串、回文数、颠倒二进制等问题。

1.4 典型例题

🍎二分

二分法的使用条件:
二分法是适用于解决具有“二段性”(单调性)的问题的方法,通常表现为求解满足某一条件的最大值或者最小值

  1. 上下界确定。 我们可以通过上下界的折半来优化查找。
  2. 二段性: 对某一范围内的数据,存在一个临界点,使得临界点某一侧的所有数据都满足某一性质,另一侧的所有数据都不满足这一性质,就称这一范围内的数据具有二段性。

二段性包括单调性,即区间内有序,这样二分出来的结果是严格大于或者小于或等于target的。
但是,二段性也体现在非单调性上,也称为局部有序,可以参考 162. 寻找峰值 和 33. 搜索旋转排序数组。由这些题我们可以得知,二分法的奥义(本质)不在于单调性,而是二段性。也就是我们能对整体无序但局部有序的序列进行二分法。

下面列出了四种不同类型的目标:

有序数组的二分查找分为这四种类型,这四种实际上是可以相互转换的,但为了方便,一般都转换成类型一的 lower bound 形式,即 ≥ target以及它的变体

注意:下面下标从0开始,以1为单位,即是在数组中都是整数时可以用的。

nums = [1, 1, 2, 2 ,3 ,3 ,4, 4, 5]
target = 3

35. 搜索插入位置 - 力扣(LeetCode)

思路:

典型双指针二分问题,详情直接看下面代码 

AC代码如下

class Solution {
public:int searchInsert(vector<int>& nums, int target) {// 方法1: 闭区间 [left, right]int l = 0, r = nums.size() - 1; while (l <= r) //区间不为空{ int mid = ((r - l) >> 1) + l;if (nums[mid] < target)l = mid + 1;  //范围缩小到[mid + 1, right]elser = mid - 1;  //范围缩小到[left, mid - 1]}return l; // 或者 return r + 1// 方法2:左闭右开区间 [left, right)int l = 0, r = nums.size();while (l < r) //区间不为空{int mid = ((r - l) >> 1) + l;if (nums[mid] < target)l = mid + 1;  //范围缩小到[mid + 1, right)elser = mid;  //范围缩小到[left, mid)}return l; //或者 return r// 方法3:开区间 (left, right)int l = -1, r = nums.size();while (l + 1 < r) //区间不为空{int mid = ((r - l) >> 1) + l;if (nums[mid] < target)l = mid;  //范围缩小到(mid, right)elser = mid;  //范围缩小到(left, mid)}return r; //或者 return l + 1}
};

有人问,怎么不讲左开右闭模板?

左开右闭这种写法一般是处理最大值的,或者说是 < 和 ≤。但是这两都可以转换成 ≥,所以可以用左闭右开模板来解决。

换句话说,上面讲的这三种二分模板,都只需关注 left right 的初始值,以及二分判定条件的写法,其余的代码都是固定的。

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

思路:

可以用到我们上个样例的写法,然后可以直接得到第一个位置,最后一个位置的话再进行调整即可

AC代码如下:

class Solution {
public:// nums[mid] = target 时 仍然往左移动int start_searchInsert(vector<int>& nums, int target) { // 返回大于等于 target 的下标,若有target则返回其第一个下标int l = 0, r = nums.size() - 1;while (l <= r) //区间不为空{int mid = ((r - l) >> 1) + l;if (nums[mid] < target)l = mid + 1;  elser = mid - 1; }return l; //或者 return r + 1}//nums[mid] = target 时 仍然往右移动int right_searchInsert(vector<int>& nums, int target) { // 返回大于 target 的位置,若有target则返回其最后一个下标int l = 0, r = nums.size() - 1;while (l <= r) //区间不为空{int mid = ((r - l) >> 1) + l;if (nums[mid] > target) r = mid - 1;  elsel = mid + 1;  }return r;  //或者 return l - 1}vector<int> searchRange(vector<int>& nums, int target) {int start = start_searchInsert(nums, target);if (start == nums.size() || nums[start] != target)return { -1,-1 };// 获取最后一个位置//方法一:查找插入target + 1的位置,然后 - 1就是我们需要查找元素的最后一个下标int end = start_searchInsert(nums, target + 1) - 1;//方法二:int end = right_searchInsert(nums, target);return { start,end };}
};

🍅有序数组暴力枚举 - N数和问题

已知一组数组和一个目标值target,求不重复的在数组中取N个数的和为target的组合。

一般做这样的题的思路是用双指针,分别指向数组的左右两端,并且,数组需要排好序,从小到大。

因为排序后,左右指针才能有规律的移动,比如:当left+right的值大于target,说明他们两个太大了,需要减小,那么只能通过right左移来减小总体值(为什么不让left左移呢?因为不能重复取,之前取过了,就要移向新的值);当left+right的值小于target,那么只能通过left右移来增加总体值。

当然,当left+right的值等于target,即为结果

881. 救生艇 - 力扣(LeetCode)

 思路:

要使需要的船数尽可能地少,应当使载两人的船尽可能地多。

设 people 的长度为 n。考虑体重最轻的人:

  • 若他不能与体重最重的人同乘一艘船,那么体重最重的人无法与任何人同乘一艘船,此时应单独分配一艘船给体重最重的人。从 people 中去掉体重最重的人后,我们缩小了问题的规模,变成求解剩余 n−1 个人所需的最小船数,将其加一即为原问题的答案。

  • 若他能与体重最重的人同乘一艘船,那么他能与其余任何人同乘一艘船,为了尽可能地利用船的承载重量,选择与体重最重的人同乘一艘船是最优的。从 people 中去掉体重最轻和体重最重的人后,我们缩小了问题的规模,变成求解剩余 n−2 个人所需的最小船数,将其加一即为原问题的答案。

实现:先对 people 排序,然后用两个指针分别指向体重最轻和体重最重的人,按照上述规则来移动指针,并统计答案。

AC代码如下:

class Solution {
public:int numRescueBoats(vector<int>& people, int limit) {sort(people.begin(), people.end());int ans = 0, l = 0, r = people.size() - 1;while (l <= r){if (people[r] + people[l] > limit) r--; //只能坐下最胖的那个else { //胖的和廋的都可以坐下l++;r--;}++ans; //需要船的数量}return ans;}
};

🍈其他暴力枚举

11. 盛最多水的容器 - 力扣(LeetCode)

思路:

设两个指针 left、right 分别指向容器的左右两个端点,此时容器的容积为:

V = (right - left) * min( height[right], height[left])

容器的左边界为 height[left],右边界为 height[right]。

为了方便叙述,我们假设「左边边界」小于「右边边界」

如果此时我们固定一个边界,改变另一个边界,水的容积会有如下变化形式:

  • 容器的宽度一定变小。
  • 由于左边界较小,决定了水的高度。如果改变左边界,新的水面高度不确定,但是一定不会超过右边的柱子高度,因此容器的容积可能会增大。
  • 如果改变右边界,无论右边界移动到哪里,新的水面的高度一定不会超过左边界,也就是不会超过现在的水面高度,但是由于容器的宽度减小,因此容器的容积一定会变小。

由此可见,左边界和其余边界的组合情况都可以舍去。所以我们可以 left++ 跳过这个边界,继续去判断下一个左右边界。

当我们不断重复上述过程,每次都可以舍去大量不必要的枚举过程,直到 left 与 right 相遇。期间产生的所有的容积里面的最大值,就是最终答案

AC代码如下:

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

977. 有序数组的平方

思路:

使用两个指针分别指向位置 0 和 n−1,每次比较两个指针对应的数,选择较大的那个逆序放入答案并移动指针。而且这种方法无需处理某一指针移动至边界的情况,

 AC代码如下:

class Solution {
public:vector<int> sortedSquares(vector<int>& nums) {int n = nums.size();vector<int> ans(n);int l = 0, r = n - 1;int pos = n - 1; //ans的下标移动,从右往左while (l <= r){if (nums[l] * nums[l] > nums[r] * nums[r]) {ans[pos] = nums[l] * nums[l]; l++;}else {ans[pos] = nums[r] * nums[r];r--;}pos--;}return ans;}
};

2. 快慢指针

2.1 基本概念

快慢指针:又称为龟兔赛跑算法,指的是两个指针从同一侧开始遍历序列,且移动的步长一个快一个慢。移动快的指针被称为 「快指针(fast)」,移动慢的指针被称为「慢指针(slow)」。两个指针以不同速度、不同策略移动,直到快指针移动到数组尾端,或者两指针相交,或者满足其他特殊条件时为止。 这种方法对于处理「环形」链表或数组非常有用。

本方法需要我们对「Floyd 判圈算法」(又称龟兔赛跑算法)有所了解。

假想「乌龟」和「兔子」在链表上移动,「兔子」跑得快,「乌龟」跑得慢。当「乌龟」和「兔子」从链表上的同一个节点开始移动时,如果该链表中没有环,那么「兔子」将一直处于「乌龟」的前方;如果该链表中有环,那么「兔子」会先于「乌龟」进入环,并且一直在环内移动。等到「乌龟」进入环时,由于「兔子」的速度快,它一定会在某个时刻与乌龟相遇,即套了「乌龟」若干圈。

我们可以根据上述思路来解决本题。具体地,我们定义两个指针,一快一慢。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。

求解步骤:

  1. 使用两个指针 slow、fast。slow 一般指向序列第一个元素,即:slow = 0,fast 一般指向序列第二个元素,即:fast = 1。
  2. 在循环体中将左右指针向右移动。当满足一定条件时,将慢指针右移,即 slow += 1。当满足另外一定条件时(也可能不需要满足条件),将快指针右移,即 fast += 1。
  3. 到快指针移动到数组尾端(即 fast == len(nums) - 1),或者两指针相交,或者满足其他特殊条件时跳出循环体。

2.2 伪码框架

slow = 0
fast = 1
while 没有遍历完:if 满足要求的特殊条件:slow += 1fast += 1
return 合适的值

2.3 适用范围

  • 用于处理数组中的移动、删除元素问题
  • 判断一个链表是否存在环。
  • 找到链表的中间节点。
  • 判断链表是否为回文结构。

2.4 典型例题

🥑链表快慢指针

142. 环形链表 II - 力扣(LeetCode)

思路:

找数学规律:当快慢指针在环中相遇,链表的起点到入环点=快慢指针相遇点到入环点的距离。所以相遇之后,定义新的游标在链表起点,此时该游标和慢指针一起以相同步长走,相遇即到了入环点。

图解:

AC代码如下:

class Solution {
public:ListNode* detectCycle(ListNode* head) {int f = 0;ListNode* fast = head, * slow = head;// 1. 判断链表是否有环,fast走两步,slow走一步while (fast && fast->next){fast = fast->next->next;slow = slow->next;if (slow == fast){f = 1;break;}}// 2. 无环时直接返回空节点if (f == 0) return nullptr;// 3.然后找入环点fast = head;while (fast != slow){fast = fast->next;slow = slow->next;}return fast;}
};

🍉数组快慢指针

26. 删除有序数组中的重复项 - 力扣(LeetCode)

思路:

  1. 定义两个快慢指针 slow,fast。其中 slow 指向去除重复元素后的数组的末尾位置。fast 指向当前元素。
  2. 令 slow 在后, fast 在前。令 slow = 0,fast = 1。
  3. 比较 slow 位置上元素值和 fast 位置上元素值是否相等。
  4. 如果不相等,则将 slow 后移一位,将 fast 指向位置的元素复制到 slow 位置上。将 fast 右移 1 位。
  5. 重复上述步骤,直到 fast 等于数组长度。
  6. 返回 slow +  1 即为新数组长度。(防止最后一个元素无法遍历到)

AC代码如下:

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

202. 快乐数 - 力扣(LeetCode)

思路:

先书写一个函数获取该数的平方和,然后定义两个指针slow和fast,slow走一步,fast走两步,判断两个指针相遇的值即可

AC代码如下:

class Solution {
public:int bitSum(int n) // 平方和{int sum = 0;while (n){int t = n % 10;sum += t * t;n /= 10;}return sum;}bool isHappy(int n) {int slow = n, fast = bitSum(n);while (slow != fast){slow = bitSum(slow);fast = bitSum(bitSum(fast));}return slow == 1;}
};

3. 区间类型指针(同向双指针)

3.1 基本概念

  • 滑动:说明这个窗口是移动的,也就是移动是按照一定方向来的。

  • 窗口:窗口大小并不是固定的,可以不断扩容直到满足一定的条件;也可以不断缩小,直到找到一个满足条件的最小窗口;当然也可以是固定大小。

求解步骤:

  1. 定义变量:确定需要维护的变量数之和,最大最小长度,哈希表等
  2. 滑动窗口:确定滑动窗口的左右边界,开始滑动窗口
  3. 合法更新在滑动窗口有效的情况下,合法的更新需要维护的变量
  4. 非法更新(二次更新):在滑动窗口无效或者即将无效的情况下,更新维护的变量,并且收缩滑动窗口的左边界,非法更新的两种情况:
  5. 滑动窗口的长度是固定的!!! 使用 if条件来更新
    滑动窗口的长度是可变的!!! 使用 while条件来更新
  6. 返回与得到答案

3.2 伪码框架

int func(vector<int>& nums,int k)
{//Step1. 定义维护变量:1. unordered_map<char,int> m;	//在需要统计字符或者数字出现的次数的时候,使用哈希表2. int sum=0,res=0;			//在需要记录整数数组中的子序列和或者其他求和时,使用sum记录每一次滑动窗口的子和,再利用res得到最大的或者最小的结果	3. int len=0,start=0;		//得到字符串的字串,len记录字串长度,start标识字串开始位置//Step2. 确定滑动窗口的边界,开始滑动:int left=0,right=0;while (right< n) 	// n: 数组长度	{//Step3. 合法更新:每进行一次滑动时,必须要更新的变量:如Step1的哈希表,sum,res,len与start等等......if (满足条件) //满足某一种条件:例如滑动窗口的长度:right-left+1 与某个值相等时,则进行一次判断,保存临时结果{//更新:res=max(res,sum)  ......}//Step4: 非法更新//(1): 滑动窗口的长度固定:使用if来更新窗口while (right-left+1 > m.size())//举个例子:无重复字符的最长字串,你的窗口长度大于哈希表的长度,则一定含有重复元素,因此更新左边界,使用if{.....}//(2): 滑动窗口的长度不固定,使用while来更新窗口	if (right>=k-1)//举个例子:子数组的最大平均值,子数组规定长度不能超过k,因此滑窗长度固定{.....}right++;//此处可以改为for循环}return res;//Step5: 返回结果
}

3.3 适用范围

简而言之,滑动窗口算法在一个特定大小的字符串或数组上进行操作,而不在整个字符串和数组上操作,这样就降低了问题的复杂度,从而也达到降低了循环的嵌套深度。其实这里就可以看出来滑动窗口主要应用在数组和字符串上。

3.4 典型例题

☘️定长滑动窗口

1456. 定长子串中元音的最大数目 - 力扣(LeetCode)

 思路:

很典型的定长滑动窗口,我们可以遍历字符串 s 每个长度为 k 的子串,求出其中包含的元音字母个数,并找出最大值。

AC代码如下: 

class Solution {
public:bool Is_vowel(char x){if (x == 'a' || x == 'e' || x == 'i' || x == 'o' || x == 'u') return true;return false;}int maxVowels(string s, int k) {int l = 0, r = 0;int res = 0, ans = 0;while (r < s.length()){if (Is_vowel(s[r++])) ans++;while(r - l + 1 > k){if(Is_vowel(s[l++])) ans--;}if (r - l + 1 == k) {res = max(res, ans);}r++;}return res;}
};

  DNA序列

思路:

和上题基本大差不差,详情可见代码 

 AC代码如下: 

#include <iostream>
#include <string>
#include <vector>
using namespace std;//DNA序列
void solve3()
{string s;int k;cin >> s >> k;// cnr用来统计CG数目,maxcnt用来统计之前窗口内CG数目最大值,begin记录标记结果的起始位置int maxcnt = 0, cnt = 0, n = s.size(), begin = 0;string t, ret;方法一:暴力//for (int i = 0; i < n; i++)//{//	t = s.substr(i, k);//	cnt = 0;//	for (auto e : t)//	{//		if (e == 'C' || e == 'G') cnt++;//	}//	if (cnt > maxcnt) {//		maxcnt = cnt;//		begin = i;//	}//}//方法二:滑动窗口int l = 0, r = 0;while (r < n){if (s[r] == 'C' || s[r] == 'G') cnt++;while (r - l + 1 > k) //窗口内的总数量超过cnt,出窗口{if (s[l] == 'C' || s[l] == 'G')cnt--;l++;}if (r - l + 1 == k) {if (cnt > maxcnt) {begin = l;maxcnt = cnt;}}r++;}ret = s.substr(begin, k);cout << ret << endl;
}int main() {solve3();
}


🍀变长滑动窗口

1004. 最大连续1的个数 III - 力扣(LeetCode)

 思路:

  • 使用 left 和 right 两个指针,分别指向滑动窗口的左右边界。
  • right 主动右移:right 指针每次移动一步。当 nums[right] 为 0,说明滑动窗口内增加了一个 0;
  • left 被动右移:判断此时窗口内 0 的个数,如果超过了 K,则 left 指针被迫右移,直至窗口内的 0 的个数小于等于 K 为止。
  • 滑动窗口长度的最大值就是所求。

AC代码如下:

class Solution {
public:int longestOnes(vector<int>& nums, int k) {int res = 0, zeros = 0, l = 0, r = 0;while (r < nums.size()){if (nums[r] == 0) zeros++; //计算窗口内 0 的个数、while (zeros > k) {if (nums[l++] == 0) zeros--;}res = max(res, r - l + 1); // 每个窗口可得到最大数目1都计算一遍++r;}return res;}
};

209. 长度最小的子数组 - 力扣(LeetCode)

class Solution {
public:int minSubArrayLen(int target, vector<int>& nums) {int ans = INT_MAX; // 1.  l、r左右指针,sum计算窗口内总和int l = 0, r = 0, sum = 0; while (r < nums.size()) {// 2. 合法更新,窗口滑动一下,把这个数字想加,计算之和sum += nums[r];// 3. //4. 非法更新(二次更新):当sum满足条件时,试探是否有更好的办法可以实现,即缩小窗口,有没有长度更小的子数组满足>=targetwhile (sum >= target) {ans = min(ans, r - l + 1);sum -= nums[l++];}r++;}return ans == INT_MAX ? 0 : ans;}
};

 713. 乘积小于 K 的子数组 - 力扣(LeetCode)

思路:

 滑动窗口做法,右边入窗口,若窗口内的值超过k就出窗口,每走一步就需要加上 j - l + 1,对应子数组的数目

注:特判 k=0 和 k=1,这样 while 循环中就无需判断 i <= j 了。

AC代码如下:

class Solution {
public:int numSubarrayProductLessThanK(vector<int>& nums, int k) {if (k <= 1) return 0;int l = 0, r = 0, ans = 0;int prod = 1; //窗口内的总乘积while (r < nums.size()){prod *= nums[r];while (prod >= k){ //出队列prod /= nums[l++];}ans += r - l + 1;r++;}return ans;}
};

4. 其他类型指针

4.1 中心扩展算法

5. 最长回文子串 - 力扣(LeetCode)

思路:

回文中点可能是一个字符或者两个字符,以这为起点,两个左右指针分别扩散切判断是否相等,达到临界条件则存储和比较最大回文串。

AC代码如下: 

class Solution {
public:string longestPalindrome(string s) {//中心扩展算法//从每一个位置mid出发,向两边扩散int begin = 0;//记录最长回文子串的起点int maxLen = 0;//记录最长回文子串的长度int len = 1;for (int mid = 0; mid < s.length(); mid++) {int l = mid - 1, r = mid + 1;//向左侧扩展,直到超过边界或遇到与中心字符不等跳出while循环while (l >= 0 && s[l] == s[mid]) {l--;//left字符与mid字符一致,继续左移len++;//与mid字符一致,回文长度+1}//向右侧扩展,直到超过边界或遇到与中心字符不等跳出while循环while (r < s.size() && s[r] == s[mid]) {r++;//right字符与mid字符一致,继续左移len++;//与mid字符一致,回文长度+1}//同时向左右两侧扩展while (l >= 0 && r < s.size() && s[l] == s[r]) {//注意此处,在最后一次循环中,即最长回文子串索引为:i~j,此时的left=i-1,right=j+1l--;r++;len += 2;}if (len > maxLen) {begin = l;maxLen = len;}len = 1; }//返回子串,从pos位开始,长度为lenreturn s.substr(begin + 1, maxLen);}
};

4.2 快慢指针变体

392. 判断子序列 - 力扣(LeetCode)

思路:

从前往后匹配,初始化两个指针 i 和 j,分别指向 s 和 t 的初始位置。每次贪心地匹配,匹配成功则 i 和 j 同时右移,匹配 s 的下一个位置,匹配失败则 j 右移,i 不变,尝试用 t 的下一个字符匹配 s。

AC代码如下: 

class Solution {
public:bool isSubsequence(string s, string t) {int i = 0, j = 0;while (i < s.length() && j < t.length()){if (s[i] == t[j]) i++;j++;}return i == s.size();}
};

4.3 从尾到头逆序

88. 合并两个有序数组 - 力扣(LeetCode)

思路:

使用双指针方法。每次从两个数组中取出较大的值插入num1中,从后往前插入

AC代码如下: 

class Solution {
public:void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {int p1 = m - 1, p2 = n - 1, k = m + n - 1; while (p1 >= 0 || p2 >= 0) //从后往前插{if (p2 < 0 || (p1 >= 0 && nums1[p1] >= nums2[p2])) // 当p2 < 0 时说明num2已经遍历完毕,只用遍历nums1即可nums1[k--] = nums1[p1--];else nums2[k--] = nums2[p2--];}}
};


📖总结

以上就是双指针算法的全部内容啦,后面我会写一篇关于双指针算法练习的博客,敬请期待啦!!!

💞 💞 💞那么本篇到此就结束,希望我的这篇博客可以给你提供有益的参考和启示,感谢大家支持!!!祝大家天天开心

相关文章:

【算法/学习】双指针

✨ 少年要迎着朝阳&#xff0c;活得肆无忌惮 &#x1f30f; &#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;算法学习 &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞 &a…...

Springboot集成Liquibase笔记整理

添加依赖<dependency><groupId>org.liquibase</groupId><artifactId>liquibase-core</artifactId> </dependency>添加配置spring:liquibase:contexts: dev,testenabled: true编写liquibase配置类Configuration EnableConfigurationPropert…...

Python拆分无atlas图集(瑕疵版)

老板给了张没有atlas文件的图集让我拆图&#xff0c;简单写了一版凑合用。 存在的问题&#xff1a; 可能会拆出来一些小尺寸的透明像素图片&#xff1b;可能会拆出来一些偏大的小图的整体集合&#xff1b;可能会把应该是一块的小图批成两半&#xff0c;不过不多&#xff1b;其…...

SQLALchemy 排序

SQLALchemy 排序 基本用法多列排序使用函数或表达式进行排序注意事项在SQLAlchemy中,排序(Ordering)是通过order_by()方法实现的。这个方法允许你指定一个或多个列(或表达式),用于对查询结果进行排序。你可以指定升序(默认)或降序排序。 基本用法 假设你有一个User模…...

【iOS】Block底层分析

目录 前言Block底层结构Block捕获变量原理捕获局部变量&#xff08;auto、static&#xff09;全局变量捕获实例self Block类型Block的copyBlock作为返回值将Block赋值给__strong指针Block作为Cocoa API中方法名含有usingBlock的方法参数Block作为GCD API的方法参数Block属性的写…...

复现dom破坏案例和靶场

文章目录 靶场网址第一个实验步骤和原理(代码为示例要根据自己的实验修改) 第二个实验步骤和原理(代码为示例要根据自己的实验修改) 靶场网址 注册后点击 第一个实验 此实验室包含一个 DOM 破坏漏洞。注释功能允许“安全”HTML。为了解决这个实验&#xff0c;请构造一个 HT…...

【高校科研前沿】南方科技大学冯炼教授等人在遥感顶刊RSE发文:全球人类改造的基塘系统制图

1.文章简介 论文名称&#xff1a;Global mapping of human-transformed dike-pond systems&#xff08;全球人类改造的基塘系统制图&#xff09; 第一作者及单位&#xff1a;Yang Xu&#xff08;南方科技大学环境学院&#xff09; 第一通讯作者及单位&#xff1a;冯炼&#x…...

How to run angular CICD on gitlab-runner of wsl?

前提文件 .gitlab-ci.yml, .dockerignore, ci-funcs.sh, Dockerfile, karma.conf.js, nginx.conf, nginx-custom.conf, sonar-project.properties 1.test.ts const context require.context(./app/pages, true, /\.spec\.ts$/); 2.sonar-project.properties sonar.sourcessrc/…...

搭建Java集成开发环境IntelliJ IDEA

搭建Java集成开发环境&#xff08;Integrated Development Environment&#xff0c;简称IDE&#xff09;IntelliJ IDEA是一个涉及多个步骤的过程&#xff0c;旨在帮助Java开发者高效、舒适地进行编程工作。IntelliJ IDEA由JetBrains公司开发&#xff0c;以其强大的代码自动补全…...

JS逆向浏览器脱环境专题:事件学习和编写、DOM和BOM结构、指纹验证排查、代理自吐环境通杀环境检测、脱环境框架、脱环境插件解决

&#x1f310;JS逆向浏览器脱环境专题&#xff1a;事件学习和编写、DOM和BOM结构、指纹验证排查、代理自吐环境通杀环境检测、脱环境框架、脱环境插件解决 &#x1f5a5;️ 浏览器事件学习和编写 浏览器事件是用户与网页交互的主要方式&#xff0c;了解并掌握这些事件的处理方…...

驾校预约学习系统--论文pf

TOC springboot373驾校预约学习系统--论文pf 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往往是人们思想上不可跨域…...

交叉编译ARM平台的OpenCV1.0

首先,从http://www.opencv.org.cn下载1.0的源码包,然后解压出来,进入解压后的目录,再进行下面的修改: 将configure 文件下列内容注释掉(有两处)&#xff0c;只保留GTK_CFLAGS"" 、GTK_LIBS"" 、have_gtkno 三项内容&#xff08;如下黑体所示&#xff09;&…...

牛客周赛 Round 56 AK

背景 语言艺术 A题&#xff1a;面包店故事 题意 一块面包要x元&#xff0c;加培根要y元&#xff0c;有n元&#xff0c;问能否买到加培根的面包 思路 大水题&#xff0c;gpt秒了 代码 #include <bits/stdc.h> using namespace std; int main() {int x, y, n; cin …...

LeetCode 热题 HOT 100 (038/100)【宇宙最简单版】

【动态规划】No. 0337 打家劫舍III【中等】&#x1f449;力扣对应题目指路 希望对你有帮助呀&#xff01;&#xff01;&#x1f49c;&#x1f49c; 如有更好理解的思路&#xff0c;欢迎大家留言补充 ~ 一起加油叭 &#x1f4a6; 欢迎关注、订阅专栏 【力扣详解】谢谢你的支持&a…...

SQLALchemy ORM 的关联关系之 ORM 中的一对一

SQLALchemy ORM 的关联关系之 ORM 中的一对一 场景示例实现一对一关系使用 `relationship()` 和外键(FK)插入和查询数据总结在 SQLAlchemy ORM 中,一对一(One-to-One)关联关系是一种比较少见的模型关系,但它确实有其应用场景,特别是在你需要将一个对象与另一个对象紧密绑…...

模型部署 - docker

docker简介 Docker 是一种开源的容器化平台&#xff0c;允许开发者将应用程序及其依赖项打包到一个标准化的单元中&#xff0c;称为“容器”。这些容器可以在任何支持 Docker 的系统上运行&#xff0c;无需担心环境差异。 为什么需要 Docker&#xff1f; 在传统的开发中&…...

学懂C++(三十四):深入详解 C++ 高级多线程编程技术中的并发设计模式

引言 在现代软件开发中&#xff0c;多线程编程已成为提升性能和响应能力的重要手段。设计模式为解决并发问题提供了有效的解决方案。本文将探讨常见的并发设计模式&#xff0c;包括生产者-消费者模式、读者-写者模式、单例模式、帧-工作者模式以及Future-Task模式&#xff0c;并…...

大数据产业链图谱_产业链全景图_大数据行业市场分析

数据作为新型生产要素&#xff0c;是数字化、网络化、智能化的基础&#xff0c;已快速融入生产、分配、流通、消费和社会服务管理等各环节&#xff0c;影响着千行百业&#xff0c;推动着我国数字经济的蓬勃发展。 大数据又称巨量数据、海量数据&#xff0c;是由数量巨大、结构…...

photonserver 部署相关教程

Photon Server 是 Exit Games 开发的高性能、可扩展的多人游戏服务器框架。部署 Photon Server 需要一些基础的服务器管理知识和配置技巧。以下是一个基本的部署教程&#xff0c;帮助你将 Photon Server 部署在 Windows 服务器上。 目录 1. 下载并安装 Photon Server 2. 配置…...

GEE训练:sentinel-1数据的投影、显示和导出

函数 projection() Returns the default projection of an Image. Throws an error if the bands of the image dont all have the same projection. 返回图像的默认投影。如果图像带的投影不一致,则会抛出错误。 Arguments: this:image (Image): The image from which …...

后端学习笔记(七)--MyBatis参数传递

5.MyBatis参数传递 ​ *MyBatis接口方法中可以接收各种各样的参数&#xff0c;MyBatis底层对于这些参数进行不同的封装处理方式 ​ *单个参数&#xff1a; 1.POJO类型&#xff1a;直接使用&#xff0c;属性名和参数占位符名称一致 2.Map集合&#xff1a;直接使用&#xff0c;…...

uniapp 网络请求自动处理loading

文章目录 背景整理思路V1版本V2版本V3版本 背景 最近在写uniapp&#xff0c;发现执行网络请求的时候经常要处理Loading效果。 比如&#xff0c;在发送网络请求之前&#xff0c;触发Loadng&#xff1b;无论请求成功还是失败都要关闭Loading&#xff1b;请求失败的时候我们还要…...

【Solidity】函数的使用

构造函数 构造函数仅在部署合约时调用一次&#xff0c;它的作用主要是初始化一些状态变量。 contract Demo {address public owner;uint public num;constructor(uint _num) {owner msg.sender;num _num;} }函数装饰器 函数装饰器可以在函数执行之前或之后插入代码逻辑&am…...

详解golang内存管理

介绍 要搞明白 Go 语言的内存管理,就必须先理解操作系统以及机器硬件是如何管理内存的。因为 Go 语言的内部机制是建立在这个基础之上的,它的设计,本质上就是尽可能的会发挥操作系统层面的优势,而避开导致低效情况。 操作系统内存管理 其实现在计算机内存管理的方式都是…...

C++ 线程 一些同步方式

C 线程一些同步方式 1.互斥锁&#xff08;Mutex&#xff09;2. 读写锁&#xff08;Reader-Writer Lock&#xff09;3. 信号量&#xff08;Semaphore&#xff09;4. 原子操作&#xff08;Atomic&#xff09;5. 屏障&#xff08;Barrier&#xff09;6. 条件变量&#xff08;Condi…...

【开发语言】编译型语言和解释性语言有啥区别?

作为一名从业多年的程序员,对于编译型语言和解释型语言之间的区别有着深入的理解。这两种类型的编程语言在将源代码转换成可执行代码的过程中采用了不同的机制,这导致了它们在执行效率、跨平台性、安全性以及开发效率等方面存在一些差异。 编译型语言(Compiled Languages)…...

将A服务器上指定文件夹中的文件,批量同步到B服务器上

需求&#xff1a;最近有一个需求&#xff0c;需要定期将A服务器上的PDF文件&#xff0c;同步到B服务器上&#xff0c;于是便写个脚本记录一下&#xff01; 下面是使用Python3脚本实现的方法 import os import paramikodef copy_pdf_files(source_ip, source_user, source_pas…...

2024.8.17

130124202408171002 DATE #:20240817 ITEM #:DOC WEEK #:SATURDAY DAIL #:捌月拾肆 TAGS < BGM "快哉风 -- 黄金玉米王" > < theme oi-language > < theme oi-graph theory > < [空] > < [空] >取次花丛懒回顾&#xff0c;半缘修道…...

十分钟搭建一个RTMP服务器

使用SRS搭建RTMP服务器 如果您需要搭建一个RTMP服务器&#xff0c;您可以使用SRS&#xff08;Simple-RTMP-Server&#xff09;来完成此任务。SRS是一个开源的RTMP服务器下面是一个简单的步骤指南&#xff1a; 获取srs srs官⽹&#xff1a;https://github.com/ossrs/srs 码云…...

Spring Boot解决循环注入问题

Spring Boot解决循环依赖注入问题 代码问题回显启动错误日志解决方案&#xff1a;使用事件驱动或通过 ApplicationContext 手动获取 Bean1. 事件驱动设计2. 使用 ApplicationContext 手动获取 Bean3. 拆分逻辑 总结 代码问题回显 现有代码1 在InterestService中依赖MemberInte…...

《数据挖掘》期末考核重点

1.数据预处理的目的与形式 数据预处理的目的是提供干净&#xff0c;简洁&#xff0c;准确的数据&#xff0c;以达到简化模型和提高算法泛化能力的目的&#xff0c;使挖掘过程更有效&#xff0c;更容易&#xff0c;提高挖掘效率和准确性。 2.数据预处理的形式 数据清理&#…...

Golang | Leetcode Golang题解之第334题递增的三元子序列

题目&#xff1a; 题解&#xff1a; func increasingTriplet(nums []int) bool {n : len(nums)if n < 3 {return false}first, second : nums[0], math.MaxInt32for i : 1; i < n; i {num : nums[i]if num > second {return true} else if num > first {second n…...

HarmonyOs编写一个案例实现一个照片选择(阶段进阶 四种需求 逐一完善)

需求1. .实现照片选择 并将选择好的照片展示出来 import { GoodItem } from ../06/modules;Entry Component struct PhotoPage {State message: string 实现一个相册;State List: GoodItem[] [{goods_name: dsfjlsjkfsf,goods_price: 100,goods_img: https://img1.baidu.com…...

洗衣机洗衣服一些知识

01智能:按衣物多少自动调节合适水位的标准洗涤程序 (需要30分钟时间) 02:大物:较大,较厚的衣服洗涤 03:轻柔:毛织品或内衣洗涤 04:快速:少量清污衣服洗涤 (13分钟) 05:浸泡:先浸泡一段时间再洗涤 06:单洗:只洗衣不脱水 07:单脱:只脱水不洗衣 08:洁桶:清洁洗衣桶 准备工作: (1)…...

探索文件系统:高效、可靠的文件管理与访问机制

文件系统的功能规划 内存就像是一个书包&#xff0c;容量有限&#xff0c;只能带着一部分东西。而图书馆则是一个专门存储和管理文件的地方&#xff0c;拥有更大的容量&#xff0c;并且可以永久保存文件。为了能够快速找到需要的文件&#xff0c;我们需要有一个书单来记录每本…...

启程与远征Ⅸ--优化生成式人工智能以满足业务需求的框架

生成类似人类的文本和语音曾经只存在于科幻小说中。但 GPT-3 和 PaLM 等大型语言模型 (LLM) 的快速发展让这一愿景更接近现实&#xff0c;解锁了从聊天机器人到内容创作等一系列有前景的商业应用。 然而&#xff0c;通用基础模型往往无法满足行业用例的需求。企业对其生成式 A…...

canal数据同步工具介绍与应用

canal服务 canal介绍canal版本与环境canal 服务集canal应用场景&#xff1a; canal常见问题xml配置问题连接认证问题jar版本问题连接问题 canal介绍 ‌1、Canal是‌阿里巴巴开源的‌MySQL增量数据订阅和消费工具&#xff0c;通过模拟MySQL的‌slave与‌master交互&#xff0c;捕…...

ubuntu18.04 设置静态地址

修改配置文件 sudo vim /etc/netplan/01-network-manager-all.yaml 代码如下&#xff1a; network: version: 2 renderer: NetworkManager ethernets: ens33: # 配置的网卡名称&#xff0c;可以使用ifconfig -a查看本机的网卡 dhcp4: no # 关闭动态IP设置 …...

jira敏捷开发管理工具视频教程Confluence工作流协同开发(2024)

正文&#xff1a; 随着Jira敏捷开发方法论的普及&#xff0c;Jira已经成为全球软件开发团队管理项目、任务和问题的首选工具。为了帮助团队更好地掌握Jira的核心功能&#xff0c;精心准备了一套全面开发技术及案例视频教程——《Jira敏捷开发管理工具视频教程Confluenc…...

【网络】TCP回显服务器和客户端的构造,以及相关bug解决方法

文章目录 ServerSocket构造方法方法 Socket构造方法方法 回显服务器&#xff08;Echo Server&#xff09;1. 构造方法2. 建立连接processConnection 方法的创建1. 读取请求并解析2. 根据请求计算响应3. 把响应写回给客户端 3. 完整代码 客户端&#xff08;Echo Client&#xff…...

Python知识点:如何使用Boto3进行AWS服务管理

使用 boto3 来管理 AWS 服务是一个非常强大的方式&#xff0c;因为 boto3 是 AWS 提供的官方 Python SDK。下面是使用 boto3 管理 AWS 服务的基本步骤&#xff0c;包括设置、操作和常见的 AWS 服务示例。 1. 安装 boto3 首先&#xff0c;确保你已经安装了 boto3。可以使用 pi…...

Java - 正则表达式

Java 提供了 java.util.regex 包&#xff0c;它包含了 Pattern 和 Matcher 类&#xff0c;用于处理正则表达式的匹配操作。 正则表达式的模式 正则表达式的模式可以包括以下内容&#xff1a; 字面值字符&#xff1a;例如字母、数字、空格等&#xff0c;可以直接匹配它们自身。…...

Vue一款流行的JavaScript前端框架

1.Vue简介 Vue是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助你高效地开发用户界面。无论是简单还是复杂的界面&#xff0c;Vue 都可以胜任。 Vue所关注的核心是MVC…...

GPT-SoVITS

文章目录 model archS1 ModelS2 model model arch S1 model: AR model–ssl tokensS2 model: VITS&#xff0c;ssl 已经是mel 长度线性相关&#xff0c;MRTE(ssl_codes_embs, text, global_mel_emb)模块&#xff0c;将文本加强相关&#xff0c;学到一个参考结果 S1 Model cla…...

linux高级编程——文件IO(常用函数大全)

1.相关介绍及常用函数 Linux高级编程中的目录IO操作是文件系统编程的一个重要组成部分&#xff0c;主要涉及到目录的打开、读取、遍历和关闭等操作。以下是一些基本的目录IO操作和相关的系统调用函数 1.1 opendir函数 打开目录&#xff1a;使用opendir函数打开一个目录&#…...

matplotlib画图

Matplotlib 先写一个最简单的&#xff1a; import matplotlib.pyplot as plt plt.plot([1,4],[2,8]) #plot画折线图plt.show() 确定两个点画一条线 import matplotlib.pyplot as plt x[1,23,4,56,7,6] #x轴数据 y[22,44,56,67,43,2] #y轴数据 s[22,43,33,44,43,7] plt.p…...

Jetpack 各种框架简介

Jetpack是Google推出的一套为Android开发提供极大便利的组件、工具和指导集&#xff0c;旨在帮助开发者快速构建高质量的应用&#xff0c;并遵循最佳实践。 Jetpack不仅是一个提高开发效率的工具集&#xff0c;还是Android开发的未来方向。它通过整合各种组件和工具&#xff0…...

海康VisionMaster使用学习笔记5-开机自启动

开机自启动 在实际应用中&#xff0c;用户会希望机台上电开机后&#xff0c;软件能自启动避免现场人员误操作&#xff0c;减少机台重新上电时的操作步骤以提升效率。 设置 打开VM,点击设置,软件设置->开机自启动->勾选开机自启动->确定 默认运行界面 启动时以设定的…...

驾驭数据之序:SQL序列的奥秘与实现

标题&#xff1a;驾驭数据之序&#xff1a;SQL序列的奥秘与实现 摘要 在数据库管理中&#xff0c;保证数据的有序性和唯一性是至关重要的。SQL序列&#xff08;Sequence&#xff09;作为一种强大的数据库对象&#xff0c;为我们提供了一种在不同数据库系统中生成连续数字的手…...

【LeetCode】148. 排序链表

排序链表 题目描述&#xff1a; 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4]示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0] 输出&#xff1a;…...