算法思想
公式拆分 :
P9231 [蓝桥杯 2023 省 A] 平方差(拆分问题)-CSDN博客
Note: 得到 x=(y−z)×(y+z),可知 x满足可以拆成 2个奇偶性相同的数的乘积。
可图性判断:
Note: 重复移除度序列中的最大值并减少相应数量的其他度值
若最终序列中所有数值都减至零,则说明该序列是可图的,反之若出现负数则不可图。
def havel_hakimi(sequence):
while True:
# 移除序列中所有的0并排序
if not sequence:
# 所有元素处理完毕且不含负数,序列可图
n = sequence.pop(0) # 移除并获取第一个元素
if n > len(sequence):
# 如果n大于序列的长度,无法继续,序列不可图
# 从序列的剩余部分中减去1
for i in range(n):
sequence[i] -= 1
if sequence[i] < 0:
# 如果出现负数,序列不可图
双指针
反向指针(碰撞指针):
反向指针
反向指针是指,双指针(i,j)相向而行,也可记为left与right指针,按照不同的策略进行移动,形成相对应的解题区域。解题思路如下:
初始化两个指针,i=0,j=size-1
While i<=j:
决定该基于nums[i]与nums[j]做出何等操作
在决定后至少移动一个指针(避免死循环)
模板题:
局部最优解导向全局最优解: 每次移动较短的板,尝试找到一个更高的板,这样在减少宽度的同时,有机会增加高度,从而找到一个可能更大的容器。因为如果移动较长的板,高度不会增加,而宽度减少
双指针法移除数组中的指定元素:左指针遍历数组,当遇到要删除的值时,用右指针指向的值替换,然后右指针左移;否则左指针继续前进,最终返回数组中剩余元素的数量。
降维打击(循环定数)
所有和为 0 的不重复三元组:先排序数组,固定一个数,用双指针在剩余部分寻找两个数,使三数之和为 0,跳过重复元素,调整指针确保和为 0 时保存三元组。
动态维护左右两侧最大高度
双指针从数组两端向中间移动,动态维护左右两侧的最大高度。每次根据较小的最大高度计算对应指针位置的接水量,并移动该指针,直到左右指针相遇
同向指针(快慢指针)
同向指针是指,双指针(i,j)从相同侧出发,按照不同的策略移动,形成相对应的解题区域。解题思路如下
初始化两个指针,通常均为0
While j<nums.size():
if need nums[j],令nums[i] = nums[j],i++
else j++
模板题:
删除有序数组中的重复项(同向指针(快慢指针))-CSDN博客
快慢指针来移除数组中的重复元素:快指针遍历数组,慢指针记录不重复元素的位置。当快指针指向的值与慢指针不同,慢指针先移动一位并将快指针的值复制过去。最终,慢指针加1即为不重复元素的个数。
快慢指针移动零到末尾:如果数组没有0,快慢指针同步移动,元素会被自己复制;如果有0,快指针找到非零元素,将其复制到慢指针位置,最终将剩余位置填充为0。
前缀和:
1.前缀和 + 滑动窗口
P8649 [蓝桥杯 2017 省 B] k 倍区间(前缀和+优化(桶分类))_p8649 [蓝桥杯 2017 省 b] k 倍区间 题解-CSDN博客
Note: 首先滑动窗口计算各前缀和取模K,计算同余两前缀和之差(为K的倍数)的个数
main(){
while(temp)
sum = (sum + temp) % k;
count1[sum]++;
// 单一前缀和为k的倍数(特判)
ans += (count1[0] * (count1[0] + 1) / 2)
for (int i = 1; i < k; i++)
// 单一前缀和,不为k的倍数
ans += (count1[i] * (count1[i] - 1) / 2);
}
2.前缀和+哈希表
Note: 使用哈希表记录每个前缀和出现的次数。在遍历数组过程中,查询当前前缀和减去目标值 k 是否存在于哈希表中,若存在,则增加计数。
prevs = Hashmap<int , int>
prevs[0] = 1
sum_ = 0
count = 0
for num in nums:
sum_ += num
if (sum_ - k) in prevs:
count += prevs[sum_ - k]
prevs[sum_] += 1
素数判断:
Note: 利用定理筛选:素数只存在6的倍数附近(即6k+1,6k-1)
再用遍历范围5~sqrt(num),+=6验证
二分查找:
求方程单解:
Note: 二分搜索逼近给定方程的解 x ,直到得到的解与目标值的差值满足特定的精度要求
# 初始化 x, (min, max)为解的范围 eg.(0,100)
x = (min_val + max_val) / 2
sum_val = x * (3 + x * (2 + x * (7 + 8 * x))) + 6
# 二分查找逼近精度
while abs(sum_val - y) > 1e-3:
# 如果 sum 小于 y
if sum_val < y:
min_val = x
x = (max_val + x) / 2
else:
max_val = x
x = (min_val + x) / 2
# 重新计算 sum
sum_val = x * (3 + x * (2 + x * (7 + 8 * x))) + 6
方程多解:
Note: 循环遍历可能的区间,判断区间内是否存在解,二分法逼近解
def f(n):
# 定义多项式函数
return a * n**3 + b * n**2 + c * n + d
def find_roots():
s = 0 # 记录找到的解的个数
for i in range(-100, 100):
min_val = i
max_val = i + 1
# 判断左端点是否为零点,若是解加一
if f(min_val) == 0:
continue
# 判断区间内是否存在解,若有解加一
if f(min_val) * f(max_val) < 0:
# 逼近于实际值
while max_val - min_val > 1e-5:
mid = (min_val + max_val) / 2
# 若端点积大于0
if f(mid) * f(max_val) > 0:
max_val = mid
else:
min_val = mid
s += 1
if (s == 3)
break;
枚举:
Note: 通过三层升序嵌套循环(其中 j 从 i 开始,k 从 j 开始),当前值的平方和小于输入的 n
函数 寻找四个数(n):
对于 i 从 0 到 满足 sq(i) < n 执行循环:
对于 j 从 i 到 满足 sq(i) + sq(j) < n 执行循环:
对于 k 从 j 到 满足 sq(i) + sq(j) + sq(k) < n 执行循环:
计算 l = sqrt(n - sq(i) - sq(j) - sq(k))
如果 l 是整数:
如果 sq(i) + sq(j) + sq(k) + sq(l) 等于 n:
输出结果 (i, j, k, l)
返回
输出未找到结果
排序:
字典序:
P1012 [NOIP1998 提高组] 拼数( 字典序 )-CSDN博客
Note: 转数字为string,利用字典序判断最大拼数
cmp(){
// 规定字典序顺序
return (a+b)>(b+a)
}
main(){
利用cmp排序数字(转换为string)
返回的拼接后所有数字
}
快排:
Note:选择基准元素,分区左为小于基准,右为大于基准,并递归对子数组进行排序。
分区操作结束时,left
指针的位置实际上是在或右侧的第一个大于或等于基准值的位置
def quicksort(arr, low, high):
if low < high:
# 执行分区操作,找到基准元素的正确位置
pivot_index = partition(arr, low, high)
# 对基准左侧的子数组进行快速排序
quicksort(arr, low, pivot_index - 1)
# 对基准右侧的子数组进行快速排序
quicksort(arr, pivot_index + 1, high)
def partition(arr, low, high):
# 选择最后一个元素作为基准
left = low
right = high - 1
while True:
# 左指针向右移动,直到找到大于等于pivot的元素
# 右指针向左移动,直到找到小于等于pivot的元素
if left < right:
# 如果左指针仍在右指针左侧,交换两个元素
else:
# 否则,分区操作完成
break
# 将基准元素移至正确位置
arr[left], arr[high] = arr[high], arr[left]
return left
桶排序:
Note:
- 数据分布均匀时最为有效。
- 当有合理的方法来选择桶的数量和范围时。
- 对于小范围的整数排序,桶排序非常有效
std::vector<double> bucket_sort(std::vector<double>& arr) {
std::vector<std::vector<double>> buckets(n);
// 将数组中的数分配到各个桶中
// 对每个桶进行排序
// 合并桶中的元素
std::vector<double> result;
return result;
}
计算:
快速幂算法:
Note: 指数奇数时乘底数,指数偶数时乘底数的一半幂
def recursive_quick_power(a, b):
if b == 0:
return 1
if b % 2 == 1:
return (a * recursive_quick_power(a, b - 1))
else:
half = recursive_quick_power(a, b // 2)
return (half * half)
累计加法:
Note: 排序后,计算每个点突出的部分,最后计算下面共有的部分
def calculate_histogram_area(heights):
# 1. 排序柱子
heights.sort()
# 2. 逐步计算突出的部分
for height in heights:
# 计算当前柱子突出的部分并累加到总面积中
total_area += (height - previous_height) * (len(heights) - 1)
previous_height = height
# 3. 计算下方共有的矩形面积
bottom_area = sum(heights) * (heights[-1] - heights[0]) # 下方共有矩形面积
total_area += bottom_area # 加上下方共有的矩形面积
return total_area
倍数判断:
Note: 二维数组 s[len][k] = {0} (用于存储余数出现的次数(代替哈希表))
// len为前数扩展的位数,k为扩展后的取模K
对于 i 从 0 到 n-1:
// 扩展arr[i]数位(0~11)
对于 len 从 0 到 11:
// 更新K
// 更新s[len][K]
对于 i 从 0 到 n-1:
t = arr[i] % k
len = 数字 arr[i] 的位数 (也就是前数需要扩展的位数)
// count加上相补的余数出现次数
count += s[len][(k - t) % k]
// 如果与本身匹配,count减一
输出 count
数学规律:
大数运算:
计算n的n次方的个位数(利用规律图)_求n的n次方的个位数,-CSDN博客
Note: 利用周期为4的规律和N的个位数,计算幂的个位数
def last_digit_of_power(num):
# 计算次方数的周期
# 获取num的个位数
# 如果n的n次方可以被4整除,将n设为4(特殊情况)
# 初始化结果为1
result = 1
# 按周期进行乘法,模拟次方计算
for _ in range(n):
result *= last_digit
# 保证结果为个位数
result %= 10
return result