双指针经典例题

日志统计

小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有 N 行。

其中每一行的格式是:

ts id
表示在 ts 时刻编号 id 的帖子收到一个”赞”。

现在小明想统计有哪些帖子曾经是”热帖”。

如果一个帖子曾在任意一个长度为 D 的时间段内收到不少于 K 个赞,小明就认为这个帖子曾是”热帖”。

具体来说,如果存在某个时刻 T 满足该帖在 [T,T+D) 这段时间内(注意是左闭右开区间)收到不少于 K 个赞,该帖就曾是”热帖”。

给定日志,请你帮助小明统计出所有曾是”热帖”的帖子编号。

输入格式
第一行包含三个整数 N,D,K。

以下 N 行每行一条日志,包含两个整数 ts 和 id。

输出格式
按从小到大的顺序输出热帖 id。

每个 id 占一行。

数据范围
1≤K≤N≤105,
0≤ts,id≤105,
1≤D≤10000

输入样例:
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
输出样例:
1
3

把所有日志按照时间先后顺序排序,然后利用双指针法,维护一个长度为 D 的区间,当存在点赞数大于等于 K 的帖子时,就将它记录下来

IA = lambda:map(int, input().split())
N = 1000010
n, d, k = IA()
a = [[0,0] for i in range(n)]
for i in range(n):
    a[i][0], a[i][1] = IA()
a.sort()

l = 0
r = 0
cnt = [0 for i in range(N)]
st = [0 for i in range(N)]

while r < n:
    cnt[a[r][1]] += 1
    
    while a[r][0] - a[l][0] >= d:
        cnt[a[l][1]] -= 1
        l += 1
    
    if cnt[a[r][1]] >= k:
        st[a[r][1]] = 1
    r += 1
        
for i in range(N):
    if st[i] == 1:
        print(i)

合法三元组的个数

image

输入格式

第一行包含四个整数 lA,lB,lC,dlA,lB,lC,d,分别表示数组 A,B,CA,B,C 的长度以及给定非负整数。

第二行包含 lAlA 个整数 A0,A1,…,AlA−1A0,A1,…,AlA−1,用来描述数组 AA。

第三行包含 lBlB 个整数 B0,B1,…,BlB−1B0,B1,…,BlB−1,用来描述数组 BB。

第四行包含 lClC 个整数 C0,C1,…,ClC−1C0,C1,…,ClC−1,用来描述数组 CC。

输出格式

输出一个整数,用来表示满足条件的三元组的个数。

数据范围

1≤lA,lB,lC≤1051≤lA,lB,lC≤105,
0≤d≤1090≤d≤109,
三个数组中的元素的取值范围为 [−109,109][−109,109]。

输入样例:

3 3 3 2
1 3 5
2 4 6
3 5 7

输出样例:

8

根据题意可知 三元组的最大元素和最小元素之差为d,可以将所有元素放到一个数组里。
维护一个窗口大小为d的窗口,每次答案增加 窗口中属于a的个属于b的个数属于c的个数
需要注意在窗口滑动过程中会出现重复计算,例如第一次窗口为[0,6] 第二次窗口为[3,7],那么[3,6]这段会被重复计算,需要减去。

IA = lambda:map(int, input().split())

n, m, k, d = IA()

a = list(IA())
b = list(IA())
c = list(IA())

x = []
for i in range(n):
    x.append([a[i], 1])
    
for i in range(m):
    x.append([b[i], 2])
    
for i in range(k):
    x.append([c[i], 3])

x.sort()

l = 0
r = 0
N = max(max(a), max(b), max(c))

cnt = [0, 0, 0, 0]
res = 0
while r < n + m+ k:
    
    
    while  x[r][0] - x[l][0] > d:
        cnt[x[l][1]] -= 1
        l += 1
        
    res -= cnt[1] * cnt[2] * cnt[3]
        
    cnt[x[r][1]] += 1
    
    res += cnt[1] * cnt[2] * cnt[3]
    r += 1
    
print(res)

递增三元组

给定三个整数数组

A=[A1,A2,…AN],
B=[B1,B2,…BN],
C=[C1,C2,…CN],

请你统计有多少个三元组 (i,j,k) 满足:

1≤i,j,k≤N
Ai

输入格式
第一行包含一个整数 N。

第二行包含 N 个整数 A1,A2,…AN。

第三行包含 N 个整数 B1,B2,…BN。

第四行包含 N 个整数 C1,C2,…CN。

输出格式
一个整数表示答案。

数据范围
1≤N≤105,
0≤Ai,Bi,Ci≤105

样例
输入

3
1 1 1
2 2 2
3 3 3
输出

27

(二分查找) O(nlogn)

要求满足Ai

IA = lambda:map(int, input().split())

n = int(input())

a = list(IA())
b = list(IA())
c = list(IA())

a.sort()
c.sort()

def get_min(x):
    l = 0
    r = n-1
    while l < r:
        # print(l, r)
        mid = (l + r + 1 ) >> 1
        if a[mid] >= x:
            r = mid - 1
        else:
            l = mid
    if a[l] < x:
        return l + 1
    else:
        return 0

def get_max(x):
    l = 0
    r = n-1
    while l < r:
        mid = (l + r ) >> 1
        if c[mid] <= x:
            l = mid +1
        else:
            r = mid
   
    if c[l] > x:
        return len(c) - l
    else:
        return 0

res = 0

for i in range(n):
    n1 = get_min(b[i])
    n2 = get_max(b[i])
    
    res += n1 * n2 
print(res)

前缀和(0(nlog(n)))

要求满足Ai

#include 
#include 
#include 
#include 

using namespace std;

typedef long long LL;

const int N = 100010;

int n;
int a[N], b[N], c[N];
int as[N];  // as[i]表示在A[]中有多少个数小于b[i]
int cs[N];  // cs[i]表示在C[]中有多少个数大于b[i]
int cnt[N], s[N];

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]), a[i] ++ ;
    for (int i = 0; i < n; i ++ ) scanf("%d", &b[i]), b[i] ++ ;
    for (int i = 0; i < n; i ++ ) scanf("%d", &c[i]), c[i] ++ ;

    // 求as[]
    for (int i = 0; i < n; i ++ ) cnt[a[i]] ++ ;
    for (int i = 1; i < N; i ++ ) s[i] = s[i - 1] + cnt[i];   // 求cnt[]的前缀和
    for (int i = 0; i < n; i ++ ) as[i] = s[b[i] - 1];

    // 求cs[]
    memset(cnt, 0, sizeof cnt);
    memset(s, 0, sizeof s);
    for (int i = 0; i < n; i ++ ) cnt[c[i]] ++ ;
    for (int i = 1; i < N; i ++ ) s[i] = s[i - 1] + cnt[i];
    for (int i = 0; i < n; i ++ ) cs[i] = s[N - 1] - s[b[i]];

    // 枚举每个b[i]
    LL res = 0;
    for (int i = 0; i < n; i ++ ) res += (LL)as[i] * cs[i];

    cout << res << endl;

    return 0;
}

双指针

进一步对查找进行优化,对于排过序的数组A和B,寻找A中小于B[i]的元素的个数可以考虑双指针算法,因为每个指针最多移动n次,故查找的时间复杂度降到O(n),查找C与查找A同理,只是找第一个大于B的位置。

IA = lambda:map(int, input().split())

n = int(input())

a = list(IA())
b = list(IA())
c = list(IA())

a.sort()
c.sort()
b.sort()


res = 0
n1 = 0
n2 = 0
l1 = 0
l2 = 0


for i in range(n):

    while l1 < n and a[l1] < b[i]:
        n1 += 1
        l1 += 1
    while l2 < n and c[l2] <= b[i]:
        n2 += 1
        l2 += 1
    
    res += n1 * (n - n2) 
print(res)

你可能感兴趣的:(双指针经典例题)