第一章 基础算法
快速排序
给定你一个长度为 n 的整数数列。
请你使用快速排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。
输入格式
输入共两行,第一行包含整数 n。
第二行包含 n 个整数(所有整数均在 1∼ 1 0 9 10^9 109 范围内),表示整个数列。
输出格式
输出共一行,包含 n 个整数,表示排好序的数列。
数据范围:1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5
解析:
- 找到一个值x作为划分点,这个值可以是
q[l],q[r], q[l+r/2],也可以是区间的随机值
- 定义两个指针
i,j
,分别指向区间的两侧 - 当指针i指向的值q[i] > x时,指针i向右移动一位;指针j指向的值q[j] > x,指针j向左移动一位
- 对于任意时刻,都存在q[l,i] 的值小于等于x,区间q[j,r]的值大于等于x
- 当i和j各找到一个不符合条件的值的时候,交换i,j指向的值
- 递归对两个子区间进行排序
代码:
class Quick_sort:
def qs(self,nums,l,r):
if l == r:return # 当区间为1时,退出递归
x = nums[l + r >> 1] # 选择一个用于划分区间的值
i = l - 1 # 将左指针指向l的左侧
j = r + 1 # 将右指针指向r的右侧
while i < j:
i += 1 # 先向左移动一次i;目的:交换i和j的值后不需要单独移动一次i和j
while nums[i] < x: i += 1 # 当i指向的值不小于x的时候,停止循环
j -= 1 # 先向右移动一次j
while nums[j] > x: j -= 1 # 当j指向的值不大于x的时候,停止循环
if i < j: # i小于j的时候才进行交换,此时找到的值就满足nums[i] >= x和nums[j] <= x
nums[i], nums[j] = nums[j], nums[i]
# 递归处理子区间
# 此处选择区间存在边界情况:
# 1.如果选择[l,j][j+1,r]时,x的取值就不能包含右端点;x = nums[l + r >> 1]
# 2.如果选择[l,i-1][i,r]时,x的取值就不能包含左端点;x = nums[l + r + 1 >> 1]
self.qs(nums,l,j)
self.qs(nums,j+1,r)
def main(self):
n = int(input())
self.line = list(map(int,input().split()))
self.qs(self.line,0,n-1)
for i in self.line:
print(i, end=' ')
if __name__ == '__main__':
quicksort = Quick_sort()
quicksort.main()
给定一个长度为 n 的整数数列,以及一个整数 k,请用快速选择算法求出数列从小到大排序后的第 k 个数。
输入格式
第一行包含两个整数 n 和 k。
第二行包含 n 个整数(所有整数均在 1∼ 1 0 9 10^9 109 范围内),表示整数数列。
输出格式
输出一个整数,表示数列的第 k 小数。
数据范围
1≤n≤100000,
1≤k≤n
输入样例:
5 3
2 4 1 5 3
输出样例:
3
解析:
- 找到一个值x作为划分点,这个值可以是
q[l],q[r], q[l+r/2],也可以是区间的随机值
- 定义两个指针
i,j
,分别指向区间的两侧 - 当指针i指向的值q[i] > x时,指针i向右移动一位;指针j指向的值q[j] > x,指针j向左移动一位
- 对于任意时刻,都存在q[l,i] 的值小于等于x,区间q[j,r]的值大于等于x
- 当i和j各找到一个不符合条件的值的时候,交换i,j指向的值
- 计算左区间的长度:
sl = j - l + 1
(或者:l = i - l
) - 如果k小于等于sl,则第k小的数在左区间,递归处理左区间即可;反之递归处理右区间
class Quickcheck:
def quickcheck(self,l,r,k):
if l == r:return self.nums[l] # 当区间只剩下一个数的时候,这个数就是第k小的数
x = self.nums[l + r >> 1] # 选取任意一个值作为分界点,小于这个值的点划分到左区间,大于这个值的点划分到右区间
i,j = l-1,r+1
while i < j:
i += 1
while self.nums[i] < x:i += 1
j -= 1
while self.nums[j] > x:j -= 1
if i < j:self.nums[i],self.nums[j] = self.nums[j],self.nums[i]
# 左区间长度为j - l + 1,如果选则[l,i-1][i,r]作为划分点的话,左区间的长度就为sl = i - l(此时x不能取到左端点)
sl = j - l + 1
if sl >= k: return self.quickcheck(l,j,k)
else:return self.quickcheck(j+1,r,k-sl)
def main(self):
n,k = map(int,input().split())
self.nums = list(map(int,input().split()))
print(self.quickcheck(0,n-1,k))
if __name__ == '__main__':
quickcheck = Quickcheck()
quickcheck.main()
归并排序
给定你一个长度为 n 的整数数列。请你使用归并排序对这个数列按照从小到大进行排序。并将排好序的数列按顺序输出。
输入格式
输入共两行,第一行包含整数 n。
第二行包含 n 个整数(所有整数均在 1 ∼ 1 0 9 1∼10^9 1∼109 范围内),表示整个数列。
输出格式
输出共一行,包含 n 个整数,表示排好序的数列。
数据范围
1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5
解析:
- 找到区间的中点mid = l + r >> 1
- 对左右区间进行递归排序
- 定义两个指针i,j,分别指向左右区间的起点
- 对两个区间的每个值进行比较大小,需要定义一个额外的结果数组,用来存放排序后的值
- 当i指向的值小于j指向的值,就将i指向的值加入到结果数组中,并将指针i向后移动一位
- 当j指向的值小于等于i指向的值,就将j指向的值加入到结果数组中,并将指针j想左移动一位
- 对于任意时刻,指针j指向的值都满足大于等于i指向的值
- 判断i和j是否移动到子数组的末尾,如果没有移动到末尾,则说明子数组中存在剩余元素,直接加入到结果列表中即可
class Mergesort:
def mergesort(self,nums):
if len(nums) == 1:return # 当区间长度为1时直接返回
mid = len(nums) >> 1 # 获取区间的中点
L = nums[:mid] # 将当前区间以中点划分左右两个区间
R = nums[mid:]
self.mergesort(L) # 对左右区间进行递归排序
self.mergesort(R)
i = j = k = 0 # 定义三个指针,分别指向左区间、右区间、结果数组的起点
while i < len(L) and j < len(R): # 边界条件
if L[i] < R[j]: # 如果左区间的值小于右区间的值,就将左区间的值加入到结果数组中
nums[k] = L[i]
i += 1
else: # 反之将右区间的值加入到结果数组中
nums[k] = R[j]
j += 1
k += 1
while i < len(L): # 判断左右区间是否还有剩余元素,处理剩余元素
nums[k] = L[i]
k += 1
i += 1
while j < len(R):
nums[k] = R[j]
k += 1
j += 1
return nums
def main(self):
int(input())
nums = list(map(int,input().split()))
res = self.mergesort(nums)
for i in res:
print(i, end=' ')
if __name__ == '__main__':
mergesort = Mergesort()
mergesort.main()
给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。
逆序对的定义如下:对于数列的第 i 个和第 j 个元素,
如果满足 i<j 且 a[i]>a[j],则其为一个逆序对;否则不是。
输入格式
第一行包含整数 n,表示数列的长度。
第二行包含 n 个整数,表示整个数列。
输出格式
输出一个整数,表示逆序对的个数。
数据范围
1≤n≤100000,
数列中的元素的取值范围 [1, 1 0 9 10^9 109]。
输入样例:
6
2 3 4 5 6 1
输出样例:
5
解析:
- 找到区间的中点mid,mid将整个区间划分为
[:mid][mid:]
两个区间 - 分别递归处理两个区间
- 当逆序对a,b同时出现在左区间或者右区间,直接在递归处理的时候加上返回值即可
- 当逆序对a,b一个在左区间一个在右区间,需要分情况讨论
- 当q[i] > q[j] 时,q[i]之后的所有数都对q[j]构成逆序对
class Reverse_order:
def mergesort(self,nums):
if len(nums) == 1:return 0 # 区间长度为1,这个区间就不存在逆序对
mid = len(nums) >> 1
left = nums[:mid] # mid将原始区间划分为左右两个子区间
right = nums[mid:]
res = 0
res += self.mergesort(left) # 递归处理左右区间
res += self.mergesort(right)
i = j = k = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:nums[k] = left[i]; k += 1; i += 1
# 当left[i] > right[j]:说明left区间中[i:mid)的数都与right[j]构成逆序对
else:nums[k] = right[j];k += 1;j += 1;res += mid - i
while i < len(left):nums[k] = left[i]; i += 1;k += 1
while j < len(right):nums[k] = right[j]; j += 1;k += 1
return res
def main(self):
n = int(input())
nums = list(map(int,input().split()))
res = self.mergesort(nums)
print(res)
if __name__ == '__main__':
reverseorder = Reverse_order()
reverseorder.main()
二分查找
给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
如果数组中不存在该元素,则返回 -1 -1。
输入格式
第一行包含整数 n 和 q,表示数组长度和询问个数。
第二行包含 n 个整数(均在 1∼10000 范围内),表示完整数组。
接下来 q 行,每行包含一个整数 k,表示一个询问元素。
输出格式
共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回 -1 -1。
数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1
思路:
能够进行二分的充要条件是,区间存在二段性(不一定是单调性)
- 找到区间的中点mid
- 判断目标值落在哪个区间,更新区间的端点为目标区间,这样每次缩小区间长度为原来的一半
class Binary_search:
def binarysearch(self,nums,target):
"""
找到第一个大于等于目标值的索引值
:param nums:
:param target:
:return:
"""
l,r = 0,len(nums) - 1
while l < r:
mid = l + r>> 1
if nums[mid] >= target:r = mid
else:l = mid + 1
return l
def binarysearch2(self,nums,target):
"""
找到第一个小于等于目标值的索引值
:param nums:
:param target:
:return:
"""
l,r = 0,len(nums) - 1
while l < r:
mid = l + r + 1>> 1
if nums[mid] <= target: l = mid # 如果这里取的是l,mid计算就需要取l+r+1>>1
else:r = mid - 1
return r
def main(self):
n,q = map(int,input().split())
nums = list(map(int,input().split()))
for i in range(q):
k = int(input())
left = self.binarysearch(nums,k)
right = self.binarysearch2(nums,k)
if nums[left] != k:print(-1,-1)
else:print(left,right)
if __name__ == '__main__':
binarysearch = Binary_search()
binarysearch.main()
前缀和与差分
输入一个长度为 n 的整数序列。
接下来再输入 m 个询问,每个询问输入一对 l,r。
对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。
输入格式
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数数列。
接下来 m 行,每行包含两个整数 l 和 r,表示一个询问的区间范围。
输出格式
共 m 行,每行输出一个询问的结果。
数据范围
1≤l≤r≤n,
1≤n,m≤100000,
−1000≤数列中元素的值≤1000
输入样例:
5 3
2 1 3 6 4
1 2
1 3
2 4
输出样例:
3
6
10
class Prefixsum:
def main(self):
n,m = map(int,input().split())
nums = list(map(int,input().split()))
nums = [0] + nums
for i in range(1,n+1):
nums[i] += nums[i-1]
for j in range(m):
l,r = map(int,input().split())
print(nums[r] - nums[l-1])
if __name__ == '__main__':
perfixsum = Prefixsum()
perfixsum.main()
输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,
每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。
输入格式
第一行包含三个整数 n,m,q。
接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含四个整数 x1,y1,x2,y2,表示一组询问。
输出格式
共 q 行,每行输出一个询问的结果。
数据范围
1≤n,m≤1000,
1≤q≤200000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤矩阵内元素的值≤1000
输入样例:
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4
输出样例:
17
27
21
解析:
- 二维前缀和: p r e f i x [ x 3 , y 3 ] = p r e f i x [ x 2 , y 3 ] + p r e f i x [ x 3 , y 2 ] − p r e f i x [ x 2 , y 2 ] + n u m s [ x 3 , y 3 ] prefix[x3,y3] = prefix[x2,y3] + prefix[x3,y2] - prefix[x2,y2] + nums[x3,y3] prefix[x3,y3]=prefix[x2,y3]+prefix[x3,y2]−prefix[x2,y2]+nums[x3,y3]
- 二维区间和: p r e f i x [ x 2 , y 2 , x 4 , y 4 ] = p r e f i x [ x 4 , y 4 ] − p r e f i x [ x 4 , y 1 ] − p r e f i x [ x 1 ] [ y 4 ] + p r e f i x [ x 1 , y 1 ] prefix[x2,y2,x4,y4] = prefix[x4,y4] - prefix[x4,y1] - prefix[x1][y4] + prefix[x1,y1] prefix[x2,y2,x4,y4]=prefix[x4,y4]−prefix[x4,y1]−prefix[x1][y4]+prefix[x1,y1]
class Prefixsum_arr:
def __init__(self):
N = 1010
self.prefix = [[0] * N for _ in range(N)]
def prefixsum(self,nums,n,m):
for i in range(1,n+1):
for j in range(1,m+1):
self.prefix[i][j] = self.prefix[i-1][j] + self.prefix[i][j-1] - self.prefix[i-1][j-1] + nums[i-1][j-1]
def main(self):
n,m,q = map(int,input().split())
nums = []
for i in range(n):
line = list(map(int,input().split()))
nums.append(line)
self.prefixsum(nums,n,m)
for j in range(q):
x1,y1,x2,y2 = map(int,input().split())
res = self.prefix[x2][y2] - self.prefix[x2][y1-1] - self.prefix[x1-1][y2] + self.prefix[x1-1][y1-1]
print(res)
if __name__ == '__main__':
prefixsum = Prefixsum_arr()
prefixsum.main()
差分
输入一个长度为 n 的整数序列。
接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。
请你输出进行完所有操作后的序列。
输入格式
第一行包含两个整数 n 和 m。
第二行包含 n 个整数,表示整数序列。
接下来 m 行,每行包含三个整数 l,r,c,表示一个操作。
输出格式
共一行,包含 n 个整数,表示最终序列。
数据范围
1≤n,m≤100000,
1≤l≤r≤n,
−1000≤c≤1000,
−1000≤整数序列中元素的值≤1000
输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例:
3 4 5 3 4 2
解析:
差分:可以看做是前缀和的逆运算,
给定一个原数组a:a[1],a[2],a[3]...a[n];
构造一个数组b:b[1],b[2],b[3]...b[i];
使得 a[i] = b[1] + b[2]+ b[3] +...+ b[i]
a数组就是b数组的前缀和,b数组就是a数组的差分数组
b数组构造方式如下:
a[0] = 0;
b[1] = a[1] - a[0];
b[2] = a[2] - a[1];
b[3] = a[3] - a[2];
...
b[n] = a[n] - a[n-1];
- 差分数组的作用:
- 给定区间
[l,r]
,让我们把a数组中的[l,r]
区间中的每一个数都加上c
,暴力做法需要对区间进行遍历,再对区间[l,r]
增加一个c - 差分数组,只需要在
s[l] += c,s[r+1] -= c
,就可使得原数组在区间[l,r]
上增加c
class Differenc:
def __init__(self):
N = 100010
self.nums = [0] * N # 差分数组
def insert(self,l,r,c):
# 在差分数组区间l加上c,区间r+1减去c,使得原数组在区间[l,r]内增加c
self.nums[l] += c
self.nums[r+1] -= c
def main(self):
n,m = map(int,input().split())
nums = list(map(int,input().split()))
for i in range(1,n+1):
self.insert(i,i,nums[i-1]) # 求差分数组
for i in range(m):
l,r,c = map(int,input().split())
# 将差分数组的l位置增加c,r+1位置减去c
self.nums[l] += c
self.nums[r+1] -= c
for i in range(1,n+1):
# 对差分数组求一遍前缀和,得到原数组
self.nums[i] += self.nums[i-1]
print(self.nums[i],end=' ')
if __name__ == '__main__':
difference = Differenc()
difference.main()
AcWing 798. 差分矩阵(二维差分)
输入一个 n 行 m 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c,其中 (x1,y1) 和 (x2,y2) 表示一个子矩阵的左上角坐标和右下角坐标。每个操作都要将选中的子矩阵中的每个元素的值加上 c。
请你将进行完所有操作后的矩阵输出。
输入格式
第一行包含整数 n,m,q。
接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含 5 个整数 x1,y1,x2,y2,c,表示一个操作。
输出格式
共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。
数据范围
1≤n,m≤1000,
1≤q≤100000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤c≤1000,
−1000≤矩阵内元素的值≤1000
输入样例:
3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1
输出样例:
2 3 4 1
4 3 4 1
2 2 2 2
解析:
与一维差分数组类似,要使得特定区间a[x1,y1,x2,y2]
内的值增加c,我们只需要在差分数组b[x1,y1]
加上c,b[x1,y2+1]
减去c,b[x2+1,y1]
减去c,b[x2+1,y2+1]
加上c,再对差分数组求一遍前缀和,就可得到原数组的区间a[x1,y1,x2,y2]
内的值增加c
b[x1][y1] += c;
b[x1][y2+1] -= c;
b[x2+1][y1] -= c;
b[x2+1][y2+1] += c;
class Difference2:
def __init__(self):
N = 1010
self.arr = [[0] * N for _ in range(N)] # 差分数组
def insert(self,x1,y1,x2,y2,c):
self.arr[x1][y1] += c
self.arr[x1][y2+1] -= c
self.arr[x2+1][y1] -= c
self.arr[x2+1][y2+1] += c
def main(self):
n, m, q = map(int,input().split())
for i in range(1,n+1):
nums = list(map(int,input().split()))
nums = [0] + nums
for j in range(1,m+1):
self.insert(i,j,i,j,nums[j])
for i in range(q):
x1, y1, x2, y2, c = map(int,input().split())
self.insert(x1,y1,x2,y2,c)
for i in range(1,n+1):
for j in range(1,m+1):
# 求二维数组的前缀和
self.arr[i][j] += (self.arr[i-1][j] + self.arr[i][j-1] - self.arr[i-1][j-1])
print(self.arr[i][j],end=' ')
print()
if __name__ == '__main__':
difference2 = Difference2()
difference2.main()
给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
输入格式
第一行包含整数 n。
第二行包含 n 个整数(均在 0∼105 范围内),表示整数序列。
输出格式
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。
数据范围
1 ≤ n ≤ 1 0 5 1≤n≤10^5 1≤n≤105
输入样例:
5
1 2 2 3 5
输出样例:
3
思路:
- 定义两个指针i,j,i指针往右移动
- 开一个数组
count
用于存放每个数出现的频次,以数组中每个数的值为索引,数组count
中的值为这个数出现的频次 - 当这个数的频次大于1,就说明当前数出现了重复次数。并且出现重复次数是因为当前i指向的元素的加入
- 向右移动指针j,直到频次大于1的元素的频次降到1
class Double_pointer:
def __init__(self):
N = 100010
self.nums = [0] * N
def main(self):
n = int(input())
nums = list(map(int,input().split()))
j = res = 0
for i in range(n):
self.nums[nums[i]] += 1
while self.nums[nums[i]] > 1:
self.nums[nums[j]] -= 1
j += 1
res = max(res,i - j + 1)
print(res)
if __name__ == '__main__':
double = Double_pointer()
double.main()
给定两个升序排序的有序数组 A 和 B,以及一个目标值 x。数组下标从 0 开始。
请你求出满足 A[i]+B[j]=x 的数对 (i,j)。
数据保证有唯一解。
输入格式
第一行包含三个整数 n,m,x,分别表示 A 的长度,B 的长度以及目标值 x。
第二行包含 n 个整数,表示数组 A。
第三行包含 m 个整数,表示数组 B。
输出格式
共一行,包含两个整数 i 和 j。
数据范围
数组长度不超过 1 0 5 10^5 105。
同一数组内元素各不相同。
1 ≤ 数组元素 ≤ 1 0 9 1≤数组元素≤10^9 1≤数组元素≤109
输入样例:
4 5 6
1 2 4 7
3 4 6 8 9
输出样例:
1 1
解析:
- 由于两个数组都是升序排列,可以定义两个指针i,j,指针i指向第一个数组的首位,j指向第二个数组的末尾
- 当i和j指向的两个数的和大于目标值,就将j向左移动一位
- 当i和j指向的两个数的和小于目标值,就将i向右移动一位
- 当i和j指向的两个数的和等于目标值,返回当前的i和j的值并结束循环
class Double_pointer2:
def main(self):
n, m, target = map(int,input().split())
nums1 = list(map(int,input().split()))
nums2 = list(map(int,input().split()))
i = 0
j = m - 1
while i < n and j >= 0:
if nums1[i] + nums2[j] == target:
print(i,j)
break
elif nums1[i] + nums2[j] > target:
j -= 1
else:i += 1
if __name__ == '__main__':
double2 = Double_pointer2()
double2.main()
给定一个长度为 n 的整数序列 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,…,an以及一个长度为 m 的整数序列 b 1 , b 2 , … , b m b_1,b_2,…,b_m b1,b2,…,bm。
请你判断 a 序列是否为 b 序列的子序列。
子序列指序列的一部分项按原有次序排列而得的序列,例如序列 a 1 , a 3 , a 5 {a_1,a_3,a_5} a1,a3,a5 是序列 a 1 , a 2 , a 3 , a 4 , a 5 {a_1,a_2,a_3,a_4,a_5} a1,a2,a3,a4,a5的一个子序列。
输入格式
第一行包含两个整数 n,m。
第二行包含 n 个整数,表示 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,…,an。
第三行包含 m 个整数,表示 b 1 , b 2 , … , b m b_1,b_2,…,b_m b1,b2,…,bm。
输出格式
如果 a 序列是 b 序列的子序列,输出一行 Yes。
否则,输出 No。
数据范围
1 ≤ n ≤ m ≤ 1 0 5 , 1≤n≤m≤10^5, 1≤n≤m≤105,
− 1 0 9 ≤ a i , b i ≤ 1 0 9 −10^9≤a_i,b_i≤10^9 −109≤ai,bi≤109
输入样例:
3 5
1 3 5
1 2 3 4 5
输出样例:
Yes
解析:
- 定义两个指针i,j,分别指向两个数组的首位置
- 如果指针j指向的值与指针i指向的值相等,就将指针i向后移动一位
- 当j移动到数组末尾时,i的值等于子串长度时,匹配成功,否则匹配失败
class Double_pointer:
def main(self):
n, m = map(int,input().split())
nums1 = list(map(int,input().split()))
nums2 = list(map(int,input().split()))
i = 0
for j in range(m):
while i < n and nums2[j] == nums1[i]:
i+= 1
if i == n:print("Yes")
else:print("No")
if __name__ == '__main__':
dounle3 = Double_pointer()
dounle3.main()
位运算
给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数。
输入格式
第一行包含整数 n。
第二行包含 n 个整数,表示整个数列。
输出格式
共一行,包含 n 个整数,其中的第 i 个数表示数列中的第 i 个数的二进制表示中 1 的个数。
数据范围
1≤n≤100000,
0 ≤ 数列中元素的值 ≤ 1 0 9 0≤数列中元素的值≤10^9 0≤数列中元素的值≤109
输入样例:
5
1 2 3 4 5
输出样例:
1 1 2 1 2
解析:
算法1:lowbit()
原码(x): 十进制数据的二进制表现形式就是原码,原码最左边的一个数字就是符号位,0为正,1为负。
反码(~x):正数的反码是其本身(等于原码),负数的反码是符号位保持不变,其余位取反。
补码(-x):正数的补码是其本身,负数的补码等于其反码 +1。因为反码不能解决负数跨零(类似于 -6 + 7)的问题,
所以补码出现了。
- lowbit计算二进制表示中的最低位的1的位置
原理:原码 & 补码 ,例如十进制12,原码为:0b1100;反码为:0b0100(即0b0011 + 1)
0b1100 & 0b0100 = 0b0100 - 计算x中1的个数,当x不为0时,每次循环减去一个lowbit(x),循环的次数就是x的二进制表示中1的个数
class Count1:
def lowbit(self,x):
"""
获取x的二进制中最低位1
:param x:
:return:
"""
return x & -x
def main(self):
n = int(input())
nums = list(map(int,input().split()))
for i in range(n):
res = 0
k = nums[i]
while k:
k -= self.lowbit(k)
res += 1
print(res,end=' ')
if __name__ == '__main__':
count1 = Count1()
count1.main()
class Count2:
def main(self):
n = int(input())
nums = list(map(int,input().split()))
for i in range(n):
res = 0
k = nums[i]
while k:
res += k & 1
k >>= 1
print(res,end=' ')
if __name__ == '__main__':
count2 = Count2()
count2.main()
离散化
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。
接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] 之间的所有数的和。
输入格式
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含两个整数 x 和 c。
再接下来 m 行,每行包含两个整数 l 和 r。
输出格式
共 m 行,每行输出一个询问中所求的区间内数字和。
数据范围
− 1 0 9 ≤ x ≤ 1 0 9 , −10^9≤x≤10^9, −109≤x≤109,
1 ≤ n , m ≤ 1 0 5 , 1≤n,m≤10^5, 1≤n,m≤105,
− 1 0 9 ≤ l ≤ r ≤ 1 0 9 , −10^9≤l≤r≤10^9, −109≤l≤r≤109,
− 10000 ≤ c ≤ 10000 −10000≤c≤10000 −10000≤c≤10000
输入样例:
3 3
1 2
3 6
7 5
1 3
4 6
7 8
输出样例:
8
0
5
解析:
- 将所有的插入和查询操作的索引值全部加入到一个索引数组中
- 对索引数组进行排序并去重,得到的新数组就是离散化后的数组
- 将插入操作和查询操作用一个二维数组存储,add数组存储的是每个插入操作的原始索引值和插入的数值,query数组存储的是每个查询操作的原始[l,r]值
- 开一个新的数组用来存放离散化后的值,这个值插入的位置就是离散化后的索引值对应的位置
- 查询操作处理类似,将查询操作的原始索引值转换为新的索引值,利用新的索引值在新的数组中去查找对应区间的值
- 离散化操作:由于离散化数组是排序去重的数组,存在单调性,可利用二分去查找大于等于当前值的第一个位置,查找出来的值可根据实际情况去确定是否需要做加1处理,这里由于是计算前缀和,前缀和的下标从1开始,所以这里将查询出来的下标直接加1
- 利用前缀和去计算区间和
class Discretization:
def __init__(self):
N = 300010 # 这里需要设置3倍空间,n次插入和m次查询的上界
self.add = []
self.query = []
self.alls_index = []
self.nums = [0] * N
self.prefix = [0] * N
def find(self,nums,x):
"""
查找离散化后,新的索引值
:param nums:离散化后的数组
:param x:需要查询的索引值
:return: 离散化后对应的新的索引值
"""
l = 0
r = len(nums) - 1
while l < r:
mid = l + r >> 1
if nums[mid] >= x:r = mid
else: l = mid + 1
return l + 1 # 由于计算的前缀和,所以索引需要从1开始,这里返回的索引值加上了1
def unique(self,nums):
"""
对数组进行排序并去重
:param nums: 待排序去重的数组
:return: 排序去重后的数组
"""
nums.sort()
i = 1
for j in range(1,len(nums)):
if nums[j] != nums[j-1]:
nums[i] = nums[j]
i += 1
return nums[:i]
def main(self):
n,m = map(int,input().split())
for i in range(n):
x,c = map(int,input().split())
self.add.append([x,c])
self.alls_index.append(x)
for j in range(m):
l,r = map(int,input().split())
self.query.append([l,r])
self.alls_index.append(l)
self.alls_index.append(r)
# 对所有的索引数组进行排序并去重
index = self.unique(self.alls_index)
# 处理插入操作,并将离散化后的值插入到新的数组nums中
for x,c in self.add:
new_x = self.find(index,x)
self.nums[new_x] += c
# 求一遍前缀和
for i in range(1,len(index)+1):
self.prefix[i] = self.prefix[i-1] + self.nums[i]
# 处理离散化后的区间和查询操作
for l,r in self.query:
new_l = self.find(index,l)
new_r = self.find(index,r)
print(self.prefix[new_r] - self.prefix[new_l-1])
if __name__ == '__main__':
discretization = Discretization()
discretization.main()
给定 n 个区间 [ l i , r i ] [l_i,r_i] [li,ri],要求合并所有有交集的区间。
注意如果在端点处相交,也算有交集。
输出合并完成后的区间个数。
例如:
[1,3] 和 [2,6] 可以合并为一个区间 [1,6]。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含两个整数 l 和 r。
输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数。
数据范围
1 ≤ n ≤ 100000 , 1≤n≤100000, 1≤n≤100000,
− 1 0 9 ≤ l i ≤ r i ≤ 1 0 9 −10^9≤l_i≤r_i≤10^9 −109≤li≤ri≤109
输入样例:
5
1 2
2 4
5 6
7 8
7 9
输出样例:
3
解析:
- 读取所有区间,对所有区间按左边界值进行从大到小排序
- 定义一个最小的边界值,来作为初始边界( e d = − 2 e 9 ed = -2e9 ed=−2e9)
- 依次遍历所有区间,当当前区间的左边界大于上一个区间的右边界时,说明两个区间不存在交点,就将当前区间加入到结果列表中
- 特判:如果ed未被更新过,说明区间为空
class Solution:
def main(self):
n = int(input())
zone = list()
for i in range(n):
line = list(map(int,input().split()))
zone.append(line)
zone.sort(key=lambda x:x[0])
# 定义一个初始右边界
ed = -2e9
res = []
for l,r in zone:
if l > ed:
# 如果当前区间的左端点小于上一个区间的右边界,
# 则说明两个区间不存在相交,将当前区间加入到结果列表中
ed = r
res.append([l,ed])
else:
# 如果两个区间相交,就取较大的右边界值取更新新的右边界
ed = max(ed,r)
if ed == -2e9:print(0) # 如果n=0,说明ed没有被更新过,直接返回0
else:print(len(res))
if __name__ == '__main__':
solution = Solution()
solution.main()
第二章 数据结构
单链表
实现一个单链表,链表初始为空,支持三种操作:
向链表头插入一个数;删除第 k 个插入的数后面的数;在第 k 个插入的数后插入一个数。现在要对该链表进行 M 次操作,进行完所有操作后,从头到尾输出整个链表。
注意:
题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,…第 n 个插入的数。
输入格式
第一行包含整数 M,表示操作次数。
接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:
H x,表示向链表头插入一个数 x。
D k,表示删除第 k 个插入的数后面的数(当 k 为 0 时,表示删除头结点)。
I k x,表示在第 k 个插入的数后面插入一个数 x(此操作中 k 均大于 0)。
输出格式
共一行,将整个链表从头到尾输出。
数据范围
1≤M≤100000
所有操作保证合法。
输入样例:
10
H 9
I 1 1
D 1
D 0
H 6
I 3 6
I 4 5
I 4 5
I 3 4
D 6
输出样例:
6 4 6 5
- 模板(C++)
// head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点
int head, e[N], ne[N], idx;
// 初始化
void init()
{
head = -1;
idx = 0;
}
// 在链表头插入一个数a
void insert(int a)
{
e[idx] = a, ne[idx] = head, head = idx ++ ;
}
// 将头结点删除,需要保证头结点存在
void remove()
{
head = ne[head];
}
- Python代码
class Solution:
def __init__(self):
N = 100010
self.e = [0] * N
self.ne = [0] * N
self.head = -1
self.idx = 1
def add_to_head(self,x):
"""
在头结点后面插入一个数
head --> -1
head --> x --> -1
:param x:
:return:
"""
self.e[self.idx] = x # 先存储需要插入的值
self.ne[self.idx] = self.head # 将当前节点的next指针指向头结点指向的节点
self.head = self.idx # 将头结点改向
self.idx += 1 # 将idx+1,说明当前节点已经被使用
def add_behind_k(self,k,x):
"""
在第k个节点后插入
head --> x1 --> k --> -1
head --> x1 --> k --> x --> -1
:param k:
:param x:
:return:
"""
self.e[self.idx] = x # 先存储需要插入的节点的值
self.ne[self.idx] = self.ne[k] # 将当前节点的next指针指向节点k指向的节点
self.ne[k] = self.idx # 将节点k的指向改向
self.idx += 1
def remove_k(self,k):
"""
head --> x1 --> k --> x2 --> x3 --> -1
head --> x1 --> k --> x3 --> -1
:param k:
:return:
"""
if k == 0:self.head = self.ne[self.head] # 如果k等于0,说明需要删除头结点
else:self.ne[k] = self.ne[self.ne[k]] # 删除节点k后面的节点就是将节点k的next指针指向next的next
def main(self):
M = int(input())
for i in range(M):
line = input().split()
if line[0] == "H":
x = int(line[1])
self.add_to_head(x)
elif line[0] == "I":
k,x = int(line[1]),int(line[2])
self.add_behind_k(k,x)
else:
k = int(line[1])
self.remove_k(k)
# 遍历链表,从链表的头结点开始遍历
idx = self.head
while ~idx: # 如果idx等于-1,说明已经遍历到节点末尾,则跳出循环
print(self.e[idx],end=' ')
idx = self.ne[idx] # 逐个移动节点
if __name__ == '__main__':
solution = Solution()
solution.main()
双链表
实现一个双链表,双链表初始为空,支持 5 种操作:
在最左侧插入一个数;
在最右侧插入一个数;
将第 k 个插入的数删除;
在第 k 个插入的数左侧插入一个数;
在第 k 个插入的数右侧插入一个数
现在要对该链表进行 M 次操作,进行完所有操作后,从左到右输出整个链表。
注意:
题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,…第 n 个插入的数。
输入格式
第一行包含整数 M,表示操作次数。
接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:
L x,表示在链表的最左端插入数 x。
R x,表示在链表的最右端插入数 x。
D k,表示将第 k 个插入的数删除。
IL k x,表示在第 k 个插入的数左侧插入一个数。
IR k x,表示在第 k 个插入的数右侧插入一个数。
输出格式
共一行,将整个链表从左到右输出。
数据范围
1≤M≤100000
所有操作保证合法。
输入样例:
10
R 7
D 1
L 3
IL 2 10
D 3
IL 2 7
L 8
R 9
IL 4 7
IR 2 2
输出样例:
8 7 7 3 2 9
- 模板(C++)
// e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点
int e[N], l[N], r[N], idx;
// 初始化
void init()
{
//0是左端点,1是右端点
r[0] = 1, l[1] = 0;
idx = 2;
}
// 在节点a的右边插入一个数x
void insert(int a, int x)
{
e[idx] = x;
l[idx] = a, r[idx] = r[a];
l[r[a]] = idx, r[a] = idx ++ ;
}
// 删除节点a
void remove(int a)
{
l[r[a]] = l[a];
r[l[a]] = r[a];
}
- Python代码
class Solution:
def __init__(self):
N = 100010
self.e = [0] * N
self.left = [0] * N
self.right = [0] * N
self.left[1] = 0 # 初始化节点,节点0-->1; 0<--1;节点0表示头结点,节点1表示尾节点
self.right[0] = 1
self.idx = 2 # 0和1已经占用两个节点,所以idx从2开始
def add_right_behind_k(self,k,x):
"""
在节点k的右边插入节点
0 --> x1 --> k --> 1
0 <-- x1 <-- k <-- 1
0 --> x1 --> k --> x --> 1
0 <-- x1 <-- k <-- x <-- 1
:param k: 在节点k后面插入节点
:param x: 插入的节点的值
:return:
"""
self.e[self.idx] = x # 先将当前节点的值存储
self.right[self.idx] = self.right[k] # 当前节点的右指针指向节点k的右节点
self.left[self.idx] = k # 当前节点的左指针指向节点k
self.left[self.right[k]] = self.idx # 节点k的右节点的左指针指向当前节点
self.right[k] = self.idx #节点k的右指针指向当前节点
self<