基础算法
快速排序
思路:
- 首先设定一个分界值(基准值),通过该分界值将数组分成左右两部分。
- 将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于分界值,而右边部分中各元素都大于或等于分界值。
- 左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
- 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
注意⚠️:要注意边界问题!!
快排不一定准确!
# 暴力方法 看似暴力但是时间复杂度是一样的
# 第一种
n =int(input())
nums =list(map(int, input().split()))
def quick_sort(nums):
if(len(nums) <=1): return nums;
left = []
mid = []
right = []
pivot =nums[len(nums)//2]
for x in nums:
if x < pivot:
left.append(x)
elif x > pivot:
right.append(x)
else:
mid.append(x)
return quick_sort(left) +mid + quick_sort(right)
if __name__ =="__main__":
nums =quick_sort(nums)
print(" ".join(list(map(str, nums))))
# 第二种
n =int(input())
nums =list(map(int, input().split()))
def quick_sort(nums):
if(len(nums) <=1): return nums;
privot =nums[len(nums)//2]
left =[x for x in nums if x < privot]
mid =[x for x in nums if x == privot]
right =[x for x in nums if x> privot]
#print (" ".join(list(map(str, left))) +","+ str(mid[0]) +","+" ".join(list(map(str, right))) )
return quick_sort(left) +mid + quick_sort(right)
if __name__ =="__main__":
nums =quick_sort(nums)
print(" ".join(list(map(str, nums))))
#双指针方法
def quick_sort(l,r,data):
if l >= r:
return
i = l - 1 # 左端指针
j = r + 1 # 右端指针
pivot = data[(i+j) // 2] #中间值,随意取;但是一般选择中间值
while i < j:
while 1:
i += 1 # 左边指针向右移
if data[i] >= pivot: #找到不小于privot的然后break
break
while 1: # 右边指针向左移
j -= 1
if data[j] <= pivot: # 找到不大于privot的然后break
break
if i < j:
data[i],data[j] = data[j],data[i] # 如果此时i小于j,则交换
quick_sort(l,j,data) #对左边子区间进行排序
quick_sort(j+1,r,data) #对右半子区间进行排序
def main():
l = 0
r = n-1
quick_sort(l,r,data)
if __name__ == "__main__":
n = int(input()) #输入数组长度
data = [int(x) for x in input().split()] #输入数组
main()
print(' '.join(list(map(str, data))))
找到第k个数:
答案:
n, k = map(int,input().split())
nums = list(map(int,input().split()))
def quick_sort(nums):
if (len(nums) <= 1): return nums;
privot = nums[len(nums) // 2]
left = [x for x in nums if x < privot]
mid = [x for x in nums if x == privot]
right = [x for x in nums if x > privot]
return quick_sort(left) + mid + quick_sort(right)
if __name__ == "__main__":
nums = quick_sort(nums)
print(nums[k -1])
归并排序
区别于快排:快排的分界值可以是任意的
但是归并排序是下标的中间值
核心:
一个是将数组一分为二,一个无序的数组成为两个数组.
另外一个操作就是,合二为一,将两个有序数组合并成为一个有序数组.
方法:
- 先一分为二
- 递归排序
- 归并,将左右两个有序序列合并为一个序列
def merge_sort(data, l, r): # 数组的两个边界是l\r
if l >= r:
return
mid = (l + r) // 2 # 划分数据
merge_sort(data, l, mid)
merge_sort(data, mid + 1, r)
tmp = [] # 创建一个临时列表
i = l
j = mid + 1
while (i <= mid) and (j <= r): # 两指针i/j向中间移动,比较数值,添加到tmp中
if data[i] <= data[j]:
tmp.append(data[i])
i += 1
else:
tmp.append(data[j])
j += 1
# 两数组合并
tmp += data[i:mid + 1] # 将左侧分区的所有元素添加到tmp列表中
tmp += data[j:r + 1] # 将右侧分区的所有元素添加到tmp列表中
data[l:r + 1] = tmp # tmp替换原来的data
if __name__ == "__main__":
n = int(input())
data = list(map(int,input().split()))
merge_sort(data, l, r)
print(merge_sort(0,n-1))
逆序队数量:
三类:
- 逆序对都在左半边
- 逆序对都在右半边
- 逆序对都在两边都有
def merge_sort(l,r):
if l >= r:
return 0
mid = (l + r) // 2
i = l
j = mid + 1
tmp = []
num = merge_sort(l,mid) + merge_sort(mid+1,r)
while i <= mid and j <= r:
if data[i] <= data[j]:
tmp.append(data[i])
i += 1
else:
tmp.append(data[j])
j += 1
num += mid - i + 1 # 如果不满足,那么第一个数组中剩余的所有数,都会和它构成逆序对
tmp.extend(data[i:mid+1])
tmp.extend(data[j:r+1])
data[l:r+1] = tmp
return num
if __name__ == "__main__":
n = int(input())
data = list(map(int,input().split()))
print(merge_sort(0,n-1))
二分
-
整数二分:
有单调性一定可以二分,可以二分的题不一定二分
找到某个性质,可以将区间一分为二,二分可以寻找边界-
二分前,先将搜索范围分为两个部分并定义check函数以检验是否满足其中一个区间的性质
-
l和r的范围内一定包含了最终的边界点
-
此模板更像是边界二分法
右边界:True: [mid,r];False:[l,mid-1]
左边界:True:[l,mid];False:[mid+1,r]
-
为什么寻找右边界(x⩽c)时,mid = (l+r+1) // 2,而寻找左边界(x⩾c)时,mid = (l+r) // 2
因为寻找右边界…],需要不断往后移动,所以必须得向后多一位
因为寻找左边界[…,需要不断迁移,所以必须向前多一位
-
-
注意⚠️:所谓的二分算法,就是我们知道当前的候选区间中,一定存在我们要找到的答案,而且我们发现这个区间拥有单调性质此类的性质,那么我们可以不停地缩减候选区间的范围,达到排除无用答案的效果.
数的范围:
n,m=map(int,input().split())
data=list(map(int,input().split()))
def dichotomous(data,n,m):
while m:
target=int(input())
l=0
r=n-1
# 先寻找左边界,即>=target的分界点
while l<r:
mid=(l+r)//2 # 左边界
if data[mid]>=target:
r=mid
else:
l=mid+1
if data[l]!=target:
print("-1-1")
else:
print(l,end=" ")
l=0
r=n-1
# 寻找右边界,即<=target的分界点
while l<r:
mid=(l+r+1)//2 # 右边界
if data[mid]<=target:
l=mid
else:
r=mid-1
print(l)
if __name__ == "__main__":
for k in range(m):
dichotomous(data,n,m)
数的三次方根
# 给定一个浮点数n,求它的三次方根
# 方法一 :没有用到二分法
def cubic_root(n):
if n>=0:
return "{:.6f}".format(n ** (1/3))
else:
n = -n
root=format(n ** (1/3))
return "{:.6f}".format(-float(root))
if __name__== '__main__':
n = float(input())
print(cubic_root(n))
除了上面有提到的整数二分法,这个题就是很典型的浮点数二分
⏰整数二分和浮点数二分的区别:
唯一区别是浮点数没有整除, 区间长度可以严格的缩小一半
l=mid;
r=mid;
过程:
- 首先输入x在区间-10000~10000
- 寻找p点(也就是x的三次方根)
- 二分找边界值
if __name__ == '__main__':
n = float(input())
st = 0 # 标记位,用来标记n是否是负数
if n < 0:
st = 1
n = -n
l, r = 0, 100
while ((r - l) > 1e-8):
mid = (l + r) / 2.0
if (mid**3 >= n):
r = mid # 边界移动
else:
l = mid
if st:
l = -l
print("{:.6f}".format(l)) # 保留6位小数
高精度
注意⚠️:其实python自带高精度哈哈哈!
print(int(input())-int(input()))
print(int(input())+int(input()))
print(int(input())*int(input()))
print(int(input())/int(input()))
# 这样输出也是高精度
高精度加法
思路:
模拟手动加法计算
例如计算:567 + 28
先个位相加: 7 + 8 = 15,所以结果的个位是5,向十位进 1
再十位相加: 6 + 2 + 1(进位)= 9, 所以十位是 9,向百位进 0
再百位相加: 5 + 0 = 5, 所以结果的百位是 5
综上,计算结果为 595
算法:
计算 567 + 28
用 a, b 两个字符串存储输入。a = 567, b = 28
为了方便计算,将两个数分别 倒序 存放在 A, B 两个整数数组中。 A = [7, 6, 5], B = [8, 2]
新建整数数组 C 保存结果,整型变量 t 保存进位,初始 t = 0.
将各个位上的数字相加,求出结果对应位上的数字和进位。
例如对个位计算: A[0] + B[0] = 7 + 8 = 15, 结果个位上是 5, 进位是 1. 所以 C[0] = 5, 进位 t = 1
最后把结果数组 C 中就保存了计算倒序结果,倒序输出就是答案
模版:
# 高精度加法
a=list(input())
b=list(input())
def add(a,b):
a.reverse() # 倒叙相加嘛
b.reverse()
c = [0]*max(len(a),len(b)+1) # 生成一个长度为a和b中最长的数+1的数组
for i in range(len(a)):
c[i] = int(a[i])
for i in range(len(b)):
c[i] += int(b[i])
for i in range(len(c)-1):
if c[i] >= 10: # 如果c[i]大于10,则进位
c[i] -= 10
c[i+1] += 1
while len(c) > 1 and c[-1] == 0:
c.pop() # 如果最后一位是0,则删除(也就是最高位)
c.reverse()
combined = int(''.join(map(str, c))) # 将数组转化为字符串,再转化为整数
print(combined)
if __name__ == '__main__':
add(a,b)
高精度减法
思路:
- 减法的借位处理
- 相减为负数处理
- 高位为0处理
模版:
def subtract(a, b):
a.reverse()
b.reverse()
c = [0]*max(len(a),len(b)+1)
for i in range(len(a)):
c[i] = int(a[i])
for i in range(len(b)):
c[i] -= int(b[i])
for i in range(len(c)-1):
if c[i] < 0:
c[i] += 10
c[i+1] -= 1
while len(c) > 1 and c[-1] == 0:
c.pop()
c.reverse()
if c[0] == 0 and len(c) > 1:
c[0] = '-'
combined = ''.join(map(str, c))
else:
combined = int(''.join(map(str, c)))
return combined
if __name__ == '__main__':
a = list(input())
b = list(input())
combined2 = ''.join(map(str, a))
combined3 = ''.join(map(str, b))
if int(combined2) < int(combined3): #判断a和b的大小,如果a<b,则输出负数
num=0-subtract(b, a)
print(num)
else:
num=subtract(a, b)
print(num)
高精度乘法
思路:
- a/b循环相乘
- 注意进位
- 注意高位为0处理
模版:
def multiply(a, b):
a.reverse()
b.reverse()
c = [0]*(len(a)+len(b))
for i in range(len(a)): # 循环a
for j in range(len(b)): # 循环b
c[i+j] += int(a[i]) * int(b[j])
for i in range(len(c)-1):
c[i+1] += c[i] // 10 # 除以10的进位,这样直接取整就是进位,不用和10比较
c[i] %= 10 # 取余
while len(c) > 1 and c[-1] == 0:
c.pop()
c.reverse()
return ''.join(map(str, c))
if __name__ == '__main__':
a = list(input())
b = list(input())
print(multiply(a, b))
高精度除法
思路:
- a变为数组,b不变
- 不需要倒置
- 余数需要注意
- 高位处理
模版:
def divide(a, b):
r = 0 # 余数
res = []
for i in range(len(a)):
r = r * 10 + int(a[i])
res.append(str(r // b)) # 商
r = r % b # 余数
while len(res) > 1 and res[0] == '0':
res.pop(0)
return ''.join(res)
if __name__ == '__main__':
a = list(input())
b = int(input())
print(divide(a, b))
前缀与差分
前缀和:
什么是前缀和
原数组: a[1], a[2], a[3], a[4], a[5], …, a[n]
前缀和 Si为数组的前 i项和
前缀和: S[i] = a[1] + a[2] + a[3] + … + a[i]
注意: 前缀和的下标一定要从 1开始, 避免进行下标的转换
s[0] = 0
s[1] = a[1]
s[2] = a[1] + a[2]
作用:
快速求出元素组中某段的和
一维例题:
模版:
n,m=map(int,input().split())
a=list(map(int,input().split()))
# 计算前缀和
prefix_sum = [0]*(n+1)
for i in range(1, n+1):
prefix_sum[i] = prefix_sum[i-1] + a[i-1]
def query(l, r):
return prefix_sum[r] - prefix_sum[l-1] # 返回区间和
if __name__ == '__main__':
for _ in range(m): # 这里_表示一个临时变量,不会被使用
l,r=map(int,input().split())
print(query(l,r))
二维例题:
思路:
- S [ i , j ] S[i, j] S[i,j] 即为图1红框中所有数的的和为:
S [ i , j ] = S [ i , j − 1 ] + S [ i − 1 , j ] − S [ i − 1 , j − 1 ] + a [ i , j ] S[i, j]=S[i, j-1]+S[i-1, j]-S[i-1, j-1]+a[i, j] S[i,j]=S[i,j−1]+S[i−1,j]−S[i−1,j−1]+a[i,j]
- ( x 1 , y 1 ) , ( x 2 , y 2 ) \left(x_1, y_1\right),\left(x_2, y_2\right) (x1,y1),(x2,y2) 这一子矩阵中的所有数之和为:
S [ x 2 , y 2 ] − S [ x 1 − 1 , y 2 ] − S [ x 2 , y 1 − 1 ] + S [ x 1 − 1 , y 1 − 1 ] S\left[x_2, y_2\right]-S\left[x_1-1, y_2\right]-S\left[x_2, y_1-1\right]+S\left[x_1-1, y_1-1\right] S[x2,y2]−S[x1−1,y2]−S[x2,y1−1]+S[x1−1,y1−1]
模版:
# 二维差分
if __name__ == "__main__":
n, m, q = map(int, input().split())
N = 1010 # 范围要大一些
a = [[0] * N for i in range(N)] # 二维差分数组
s = [[0] * N for i in range(N)] # 前缀和数组
for i in range(1, n + 1):
a[i] = [0] + list(map(int, input().split())) + a[m + 1:] #这里是将输入的数据放到a数组中,其中a[m+1:]是为了保证a[i]的长度为m+1
for i in range(1, n + 1):
for j in range(1, m + 1):
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j]
while q:
x1, y1, x2, y2 = map(int, input().split())
print(s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1])
q -= 1
差分:
差分数组:
首先给定一个原数组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数组的差分数组。换句话说,每一个a[i]都是b数组中从头开始的一段区间和。
构造差分数组:
一维差分:
模版:
# 方法一 (但是这个会超时)因为他的时间复杂度query(l−r)
from typing import List
n, m = map(int, input().split())
a = list(map(int, input().split()))
def differnance(n: int, m: int, a: List[int]) -> List[int]:
for i in range(m):
l, r, c = map(int, input().split())
for j in range(l-1,r):
a[j] += c
print(' '.join(map(str,a[:])))
if __name__ == '__main__':
differnance(n, m, a)
# 方法二 这个时间复杂度低一些
n, m = map(int, input().split())
a = list(map(int, input().split()))
diff_data = [0] * (n + 1) # 差分数组
diff_data[0] = a[0] # 注意:差分数组的第一个元素等于原数组的第一个元素
for i in range(1, n):
diff_data[i] = a[i] - a[i - 1]
def differnance(n, m, a):
data=[0]*(n+1) # 新数组
for i in range(m):
l, r, c = map(int, input().split())
diff_data[l - 1] += c # 差分数组的第l个元素加c
diff_data[r] -= c # 差分数组的第r+1个元素减c,这样避免影响到r+1之后的元素
for i in range(1, n + 1):
data[i] = diff_data[i-1] + data[i - 1] # 通过差分数组还原原数组,这里i-1是因为差分数组的第一个元素等于原数组的第一个元素
print(' '.join(map(str, data[1:])))
if __name__ == '__main__':
differnance(n, m, a)
二维差分:
- b
[x1][ y1 ]
+=c ; 对应图1 ,让整个a数组中蓝色矩形面积的元素都加上了c。 - b
[x1,][y2+1]
-=c ; 对应图2 ,让整个a数组中绿色矩形面积的元素再减去c,使其内元素不发生改变。 - b
[x2+1][y1]
- =c ; 对应图3 ,让整个a数组中紫色矩形面积的元素再减去c,使其内元素不发生改变。 - b
[x2+1][y2+1]
+=c; 对应图4,让整个a数组中红色矩形面积的元素再加上c,红色内的相当于被减了两次,再加上一次c,才能使其恢复。
例题:
模版:
# 二维差分
def insert(b ,x1, y1, x2, y2, c):
b[x1][y1] += c
b[x2 + 1][y1] -= c
b[x1][y2 + 1] -= c
b[x2 + 1][y2 + 1] += c
n,m,q=map(int,input().split())
a=[[0]*(m+2) for i in range(n+2)]
b=[[0]*(m+2) for i in range(n+2)]
for i in range(1,n+1):
row=list(map(int,input().split())) # 读入一行
for j in range(1,m+1):
a[i][j]=row[j-1] # 将元素放入矩阵中
insert(b,i,j,i,j,a[i][j]) # 原始矩阵插入差分矩阵
while q:
q-=1
x1,y1,x2,y2,c=map(int,input().split())
insert(b,x1,y1,x2,y2,c) # 插入差分矩阵
for i in range(1, n+1):
for j in range(1, m+1):
b[i][j] += b[i-1][j] + b[i][j-1] - b[i-1][j-1] # 差分矩阵前缀和
print(b[i][j], end=' ')
print()