力扣玩转双指针

双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。也可以延伸到多个数组的多个指针。
若两个指针指向同一数组,遍历方向相同且不会相交,则也称为滑动窗口(两个指针包围的区域即为当前的窗口),经常用于区间搜索。
若两个指针指向同一数组,但是遍历方向相反,则可以用来进行搜索,待搜索的数组往往是排好序的

int x;
int * p1 = &x; // 指针可以被修改,值也可以被修改
const int * p2 = &x; // 指针可以被修改,值不可以被修改(const int)
int * const p3 = &x; // 指针不可以被修改(* const),值可以被修改
const int * const p4 = &x; // 指针不可以被修改,值也不可以被修改
// addition是指针函数,一个返回类型是指针的函数
int* addition(int a, int b) {
    
    
int* sum = new int(a + b);
return sum;
}
int subtraction(int a, int b) {
    
    
return a - b;
}
int operation(int x, int y, int (*func)(int, int)) {
    
    
return (*func)(x,y);
}
// minus是函数指针,指向函数的指针
int (*minus)(int, int) = subtraction;
int* m = addition(1, 2);
int n = operation(3, *m, minus);

在一个增序的整数数组里找到两个数,使它们的和为给定值。已知有且只有一对解。
输入是一个数组(numbers)和一个给定值(target)。输出是两个数的位置,从 1 开始计数
Input: numbers = [2,7,11,15], target = 9
Output: [1,2]
因为数组已经排好序,我们可以采用方向相反的双指针来寻找这两个数字,一个初始指向最
小的元素,即数组最左边,向右遍历;一个初始指向最大的元素,即数组最右边,向左遍历。
如果两个指针指向元素的和等于给定值,那么它们就是我们要的结果。如果两个指针指向元
素的和小于给定值,我们把左边的指针右移一位,使得当前的和增加一点。如果两个指针指向元
素的和大于给定值,我们把右边的指针左移一位,使得当前的和减少一点。
可以证明,对于排好序且有解的数组,双指针一定能遍历到最优解。证明方法如下:假设最
优解的两个数的位置分别是 l 和 r。我们假设在左指针在 l 左边的时候,右指针已经移动到了 r;
此时两个指针指向值的和小于给定值,因此左指针会一直右移直到到达 l。同理,如果我们假设
在右指针在 r 右边的时候,左指针已经移动到了 l;此时两个指针指向值的和大于给定值,因此
右指针会一直左移直到到达 r。所以双指针在任何时候都不可能处于 (l,r) 之间,又因为不满足条
件时指针必须移动一个,所以最终一定会收敛在 l 和 r。

vector<int> twoSum(vector<int>& numbers, int target) {
    
    
int l = 0, r = numbers.size() - 1, sum;
while (l < r) {
    
    
sum = numbers[l] + numbers[r];
if (sum == target) break;
if (sum < target) ++l;
else --r;
}
return vector<int>{
    
    l + 1, r + 1};
}

给定两个有序数组,把两个数组合并为一个。
Input: nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
Output: nums1 = [1,2,2,3,5,6]
因为这两个数组已经排好序,我们可以把两个指针分别放在两个数组的末尾,即 nums1 的 m − 1 位和 nums2 的 n − 1 位。每次将较大的那个数字复制到 nums1 的后边,然后向前移动一位。
因为我们也要定位 nums1 的末尾,所以我们还需要第三个指针,以便复制。
在以下的代码里,我们直接利用 m 和 n 当作两个数组的指针,再额外创立一个 pos 指针,起始位置为 m +n−1。每次向前移动 m 或 n 的时候,也要向前移动 pos。这里需要注意,如果 nums1的数字已经复制完,不要忘记把 nums2 的数字继续复制;如果 nums2 的数字已经复制完,剩余nums1 的数字不需要改变,因为它们已经被排好序。
注意 这里我们使用了 ++ 和–的小技巧:a++ 和 ++a 都是将 a 加 1,但是 a++ 返回值为 a,而++a 返回值为 a+1。如果只是希望增加 a 的值,而不需要返回值,则推荐使用 ++a,其运行速度会略快一些。

void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
    
    
int pos = m-- + n-- - 1;
while (m >= 0 && n >= 0) {
    
    
nums1[pos--] = nums1[m] > nums2[n]? nums1[m--]: nums2[n--];
}
while (n >= 0) {
    
    
nums1[pos--] = nums2[n--];
} }

快慢指针
给定一个链表,如果有环路,找出环路的开始点。
输入是一个链表,输出是链表的一个节点。如果没有环路,返回一个空指针。
对于链表找环路的问题,有一个通用的解法——快慢指针(Floyd 判圈法)。给定两个指针,
分别命名为 slow 和 fast,起始位置在链表的开头。每次 fast 前进两步,slow 前进一步。如果 fast可以走到尽头,那么说明没有环路;如果 fast 可以无限走下去,那么说明一定有环路,且一定存在一个时刻 slow 和 fast 相遇。当 slow 和 fast 第一次相遇时,我们将 fast 重新移动到链表开头,并让 slow 和 fast 每次都前进一步。当 slow 和 fast 第二次相遇时,相遇的节点即为环路的开始点。
B站
https://www.bilibili.com/video/BV1rz4y1o7i7?from=search&seid=5267842031685031329&spm_id_from=333.337.0.0

if(head==null||head->next==null)return null;
listnode*slow=head,*fast=head;
while(fast!=null&&fast->next!=null){
    
    
	fast=fast->next->next;
	slow=slow->next;
	if(fast==slow)
	break; 
}
if(fast!=slow)return null;
slow=head;
while(slow!=fast){
    
    
	slow=slow->next;
	fast=fast->next;
}
return slow;

滑动窗口
给定两个字符串 S 和 T,求 S 中包含 T 所有字符的最短连续子字符串的长度,同时要求时间复杂度不得超过 O(n)。
Input: S = “ADOBECODEBANC”, T = “ABC”
Output: “BANC”
本题使用滑动窗口求解,即两个指针 l 和 r 都是从最左端向最右端移动,且 l 的位置一定在r 的左边或重合。注意本题虽然在 for 循环里出现了一个 while 循环,但是因为 while 循环负责移动 l 指针,且 l 只会从左到右移动一次,因此总时间复杂度仍然是 O(n)。本题使用了长度为 128的数组来映射字符,也可以用哈希表替代;其中 chars 表示目前每个字符缺少的数量,flag 表示每个字符是否在 T 中存在

string minWindow(string S, string T) {
    
    
vector<int> chars(128, 0);
vector<bool> flag(128, false);
// 先统计T中的字符情况
for(int i = 0; i < T.size(); ++i) {
    
    
flag[T[i]] = true;
++chars[T[i]];
}
// 移动滑动窗口,不断更改统计数据
int cnt = 0, l = 0, min_l = 0, min_size = S.size() + 1;
for (int r = 0; r < S.size(); ++r) {
    
    
if (flag[S[r]]) {
    
    
if (--chars[S[r]] >= 0) {
    
    
++cnt;
}
// 若目前滑动窗口已包含T中全部字符,
// 则尝试将l右移,在不影响结果的情况下获得最短子字符串
while (cnt == T.size()) {
    
    
if (r - l + 1 < min_size) {
    
    
min_l = l;
min_size = r - l + 1;
}
if (flag[S[l]] && ++chars[S[l]] > 0) {
    
    
--cnt;
}
++l;
} } }
return min_size > S.size()? "": S.substr(min_l, min_size);
}

Guess you like

Origin blog.csdn.net/qq_45598881/article/details/121002734