目录
双指针法
(对撞指针、快慢指针)双指针法,指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个相同方向(快慢指针)或者相反方向(对撞指针)的指针进行扫描,从而达到相应的目的。换言之,双指针法充分使用了数组有序这一特征,从而在某些情况下能够简化一些运算。
双指针法什么时候能用:当右指针单调递增时,左指针能单调不减或不增。
双指针法没有很多的套路或者模型,往往需要不断地做题来加深对这个方法地认识。下面介绍一道例题来讲解双指针法的具体实现。
几个经典的案例
一,数组
数组的遍历:当需要同时访问数组中的两个元素时,可以使用双指针法来遍历数组。一个指针可以指向数组的起始位置,另一个指针可以指向数组的末尾位置。通过这种方式,我们可以同时从两个方向遍历数组,从而提高算法的效率。
以下是一个示例代码,展示了如何使用双指针法来判断一个数组是否是回文数组(即正序和逆序读取元素都相同):
#include <iostream>
using namespace std;
bool isPalindrome(int arr[], int n) {
int left = 0; // 左指针
int right = n - 1; // 右指针
while (left < right) {
if (arr[left] != arr[right]) {
return false;
}
left++;
right--;
}
return true;
}
int main() {
int arr[] = {1, 2, 3, 2, 1};
int n = sizeof(arr) / sizeof(arr[0]);
if (isPalindrome(arr, n)) {
cout << "The array is a palindrome." << endl;
} else {
cout << "The array is not a palindrome." << endl;
}
return 0;
}
在上述示例中,我们使用了两个指针 left
和 right
来同时遍历数组的两端。如果左右指针所指向的元素不相等,说明数组不是回文数组,直接返回 false
。如果左右指针所指向的元素相等,继续向中间移动,直到左指针超过右指针,此时数组是回文数组,返回 true
。
二,链表
链表问题:双指针法也可以用于解决链表相关的问题。在链表中,我们可以使用两个指针分别指向两个不同的节点,从而实现一些特定的操作。
以下是一个示例代码,展示了如何使用双指针法来找到链表的中间节点:
#include <iostream>
using namespace std;
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(NULL) {}
};
ListNode* findMiddle(ListNode* head) {
ListNode* slow = head; // 慢指针
ListNode* fast = head; // 快指针
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
int main() {
ListNode* head = new ListNode(1);
head->next = new ListNode(2);
head->next->next = new ListNode(3);
head->next->next->next = new ListNode(4);
head->next->next->next->next = new ListNode(5);
ListNode* middle = findMiddle(head);
cout << "The middle node value is: " << middle->val << endl;
return 0;
}
在上述示例中,我们使用了两个指针 slow
和 fast
来寻找链表的中间节点。其中,慢指针每次移动一步,快指针每次移动两步。当快指针到达链表的末尾时,慢指针正好到达链表的中间位置。
一个稍有难度的题目
题目描述
小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有 N� 行。
其中每一行的格式是:
ts id
表示在 ts时刻编号 id的帖子收到一个”赞”。
现在小明想统计有哪些帖子曾经是”热帖”。 如果一个帖子曾在任意一个长度为 D 的时间段内收到不少于 K个赞,小明就认为这个帖子曾是”热帖”。具体来说,如果存在某个时刻 T 满足该帖在 [T,T+D)这段时间内(注意是左闭右开区间)收到不少于 K个赞,该帖就曾是”热帖”。
给定日志,请你帮助小明统计出所有曾是”热帖”的帖子编号。
输入输出格式
输入格式
第一行包含三个整数 N,D,K。
以下 N行每行一条日志,包含两个整数 ts和 id。
输出格式
按从小到大的顺序输出热帖 id。
每个 id占一行。
数据范围
1≤K≤N≤10^5
0≤ts,id≤10^5
1≤D≤10000
输入样例
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
输出样例
1
3
题解
用双指针法。
- 定义结构体数组logs[N]。可以用pair库来实现。其中,logs[i].first表示ts,logs[i].second表示id。
- 对logs数组进行排序。可以用algorthm库中的sort函数进行快速排序。时间复杂度在O(n * log n )之内。
- 定义指针int i = 0, j = 0。一开始 i , j 都指向logs[0]。每次循环,j++(j是快指针)。统计[ i , j ]之间每个id的赞数,可以用cnt数组来存。其中cnt[i]表示id为i的帖子在[ i , j ]时间段内收到的点赞数之和。
- 一旦发现logs[j].first -logs[i].first >= d(时间跨度大于d了,要扔掉 i 的数值)。让i ++,同时cnt[logs[ i ].second] -- 。
- 如果某个id点赞数大于等于k,标记这个id。
- 最后遍历所有的id,输出被标记的那些。
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
#define x first
#define y second
const int N = 100010;
int n, d, k;
PII logs[N];
bool st[N];
int cnt[N];
int main(){
scanf("%d%d%d", &n, &d, &k);
for (int i = 0; i < n; i ++ ) scanf("%d%d", &logs[i].x, &logs[i].y);
sort(logs, logs + n);
for (int i = 0, j = 0; i < n; i ++ ){
int t = logs[i].y;
cnt[t] ++;
while (logs[i].x - logs[j].x >= d){
cnt[logs[j].y] --;
j ++;
}
if (cnt[t] >= k) st[t] = true;
}
for (int i = 0; i <= 100000; i ++ ) if (st[i]) cout << i << endl;
return 0;
}