一、双指针
(一)常见问题
(1) 对于一个序列,用两个指针维护一段区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
(二)基础模板
for (int i = 0, j = 0; i < n; i ++ )
{
while (j < i && check(i, j)) j ++ ;
}
(三)题目一:最长连续不重复子序列
1、分析
- 最长连续不重复子序列中,任意两个数字都是不重复的,因此为了有效追踪数字的出现情况,我们可以利用一个辅助数组
s
来记录每个数字出现的次数,其中s[i]
表示数字i
在序列中出现的次数。 - 双指针:我们采用两个指针 i 和 j 来优化搜索过程。指针 i 用于遍历整个序列,而指针 j 则用于标记当前考察的最长不重复子序列的最左侧边界。通过这种方式,我们可以在遍历过程中动态调整 j 的位置,以确保在 i 和 j 之间的子序列始终保持不重复的特性。
- 长度更新:计算当前子序列的长度i-j+1,并与之前记录的最长长度 res 进行比较,更新 res 为两者之间的较大值。
2、代码
def main():
n=int(input());N=100010
nums=list(map(int,input().split()))
j=0;s=[0]*N;res=0
for i in range(n):
s[nums[i]]+=1
while j<i and s[nums[i]]>1:
s[nums[j]]-=1
j+=1
res=max(res,i-j+1)
print(res)
main()
(四)数组元素的目标和
1、分析
- 序列a的指针i从头开始遍历,序列b的指针j从后开始遍历。
- 如果当前和大于 x,说明当前选择的 a[i] 和 b[j] 中至少有一个值偏大。由于 j 指向 b 数组的较大元素,减少 j 可以快速减小当前和,因为较小的元素加到 a[i] 上,和的减少幅度会更大。
- 如果当前和小于
x
,说明需要增大和。由于i
指向a
数组的较小元素,增加i
可以逐步尝试更大的a[i]
值,同时保持b[j]
的当前值,这样可以避免重新计算已经检查过的b[j]
组合。
2、代码
def main():
n,m,x=map(int,input().split())
a=list(map(int,input().split()))
b=list(map(int,input().split()))
i,j=0,m-1
while i<n and j>=0:
if a[i]+b[j]==x:
print(i,j)
break
elif a[i]+b[j]>x:
j-=1
else:
i+=1
main()
二、位运算
(一)lowbit操作
1、原理
- lowbit公式:x&(-x)或者x&(~x+1),其中-x是补码,~x是反码,其中反码加1得到补码。
- lowbit用途:得到每一个数的二进制表示中从低位开始扫描到的第一个1,比如100100中扫描得到100。
- lowbit获取二进制中1的位数:不断获取从低位开始扫描到的第一个1,用当前的值减掉lowbit得到的数字,直到当前值为0退出循环,循环的次数就是1的个数。比如最开始值为100100,第一次循环中扫描到100,减掉后得到100000;第二次扫描得到100000,减掉后得到0,退出循环,此时二进制中1的位数就等于循环的次数两次。
2、代码
def lowbit(x):
return x&(-x)
def main():
n=int(input())
nums=list(map(int,input().split()))
cnt=[]
for num in nums:
count=0
while num:
num-=lowbit(num)
count+=1
cnt.append(count)
print(" ".join(map(str,cnt)))
main()
(二)Python内置方法bin
1、方法
bin() 是一个内置函数,用于将一个整数转换成一个二进制字符串表示。
2、代码
def main():
n=int(input())
nums=list(map(int,input().split()))
cnt=[bin(x).count('1') for x in nums]
print(" ".join(map(str,cnt)))
main()
三、离散化
(一)定义
连续的数据或较大范围的数据转换为离散的、有限的数据集。
(二)题目:区间和
1、方法
- 数据结构:数组s存放前缀和,数组add存放加法信息,数组query存放查询信息,数组alls存放加法和查询中所有的点,数组a存放去重后的加法结果。
- 去重排序:由于alls中的点可能有重复的,因此需要对alls内点进行去重,利用集合中没有重复元素的特性,将alls转换成集合去重;去重后,转换成列表再排序(利用sorted方法),此时得到一个有序无重复元素的alls列表。
- 关键操作:alls是个有序列表,因此可以采用二分查找的方式,找到每个查找端点l与r在alls中的位置,求l与r之间的区间和也可以转换成求对应位置的区间和。
2、代码
def find(x,alls):
l=0;r=len(alls)-1
while l<r:
mid=(l+r)//2
if alls[mid]>=x:
r=mid
else:
l=mid+1
#因为区间和是从s[1]开始计算的,第一个元素对应的下标是1
#而alls中第一个元素下标是0,所以return l+1
return l+1
def main():
n,m=map(int,input().split())
add=[];query=[];alls=[]
#存储加法信息
for _ in range(n):
x,c=map(int,input().split())
add.append([x,c])
alls.append(x)
#存储查询信息
for _ in range(m):
l,r=map(int,input().split())
query.append([l,r])
alls.append(l);alls.append(r)
#去重排序
alls=sorted(list(set(alls)))
a=[0]*(len(alls)+1)
#加法操作
for x,c in add:
x1=find(x,alls)
a[x1]+=c
s=[0]*(len(alls)+1)
#求前缀和
for i in range(1,len(alls)+1):
s[i]=s[i-1]+a[i]
#求区间和
for l,r in query:
l1=find(l,alls)
r1=find(r,alls)
print(s[r1]-s[l1-1])
main()
四、区间合并
1、思路
- 数据结构:列表a存放每个区间的左右端点。
- 排序:按照每个区间左端点的大小将列表a进行排序。
- 合并:按序比较每个区间的左端点l与上个区间的右端点r1进行比较,如果不重合则区间计数加1,将l1和r1更新为当前区间的左右端点后进入下一轮循环;若存在重合,合并区间后新区间的右端点为当前区间右端点r与上轮区间右端点r1的最大值。
2、代码
def merge(a):
l1=a[0][0];r1=a[0][1]
res=1
for l,r in a[1:]:
if l>r1:
res+=1
l1=l;r1=r
else:
r1=max(r,r1)
return res
def main():
n=int(input())
a=[]
for _ in range(n):
l,r=map(int,input().split())
a.append([l,r])
#将a中的区间按照左端点的大小排序
a=sorted(a,key=lambda x:x[0])
ans=merge(a)
print(ans)
main()