快速幂
快速幂是一种用于快速计算一个数的整数次幂的算法。该算法通过将指数n拆分成二进制表示形式中的若干项,从而将计算量降至对数级别。
具体来说,假设我们要计算a的n次幂,可以将n写成二进制形式,如n=101101(二进制),则有:
a^n = a^(2^5) * a^(2^3) * a^(2^2) * a^(2^0)
其中,2^5、2^3、2^2、2^0分别表示二进制数101101中为1的位所对应的幂次。上式中每个乘积都可以通过不断平方得到,例如a^(2^5)可以通过不断将a平方5次得到。
使用这种方法,计算a的n次幂的时间复杂度为O(logn),比直接计算a^n的时间复杂度O(n)要快得多。
以下是一个使用Python实现快速幂算法的例子:
def fast_pow(a, n):
result = 1
while n > 0:
if n % 2 == 1:
result *= a
a *= a
n //= 2
return result
在这个实现中,我们使用了while循环和位运算来对指数进行二进制拆分,并使用了一个变量result来记录每个乘积的结果。
优先队列
优先队列是一种数据结构,它可以存储具有优先级的元素,并在需要时根据优先级访问和删除它们。下面是一个用Python实现最小优先队列的示例代码:
import heapq
class PriorityQueue:
def __init__(self):
self._queue = []
self._index = 0
def push(self, item, priority):
heapq.heappush(self._queue, (priority, self._index, item))
self._index += 1
def pop(self):
return heapq.heappop(self._queue)[-1]
这个代码使用了Python的heapq模块,它实现了一个最小堆,也就是说,具有最高优先级的元素是队列中的最小元素。在这个实现中,每个元素都是一个元组(priority, index, item),其中priority是元素的优先级,index用于处理相同优先级元素的顺序,item是元素本身。
push()方法将一个元素和其优先级添加到队列中。在这个实现中,我们使用一个自增的索引来确保相同优先级元素的顺序。pop()方法从队列中弹出具有最高优先级的元素,并返回其item值。这个实现还允许相同优先级元素的存在,它们将按照它们被添加到队列中的顺序弹出。
下面是一个简单的使用优先队列的示例:
q = PriorityQueue()
q.push('task1', 1)
q.push('task2', 3)
q.push('task3', 2)
print(q.pop()) # 输出: 'task1'
print(q.pop()) # 输出: 'task3'
print(q.pop()) # 输出: 'task2'
在这个示例中,我们首先创建了一个优先队列,并将三个任务添加到队列中。由于第一个任务具有最高优先级,因此它将首先从队列中弹出。随后弹出的任务3和任务2分别具有第二和第三高的优先级。
树状数组
树状数组(Fenwick Tree)是一种可以高效地处理动态数组前缀和问题的数据结构。它可以在O(logn)时间复杂度内完成单点更新和前缀查询操作。下面是一个用Python实现树状数组的示例代码:
class FenwickTree:
def __init__(self, n):
self._sums = [0] * (n + 1)
def update(self, i, delta):
while i < len(self._sums):
self._sums[i] += delta
i += i & -i
def query(self, i):
res = 0
while i > 0:
res += self._sums[i]
i -= i & -i
return res
在这个实现中,我们使用了一个长度为$n+1$的数组_sums来存储前缀和,其中_sums[i]存储了前i个元素的和。update()方法用于更新第$i$个元素的值。它从$i$开始,一直到数组末尾,更新中的元素,直到处理完整个前缀。query()方法用于查询前缀和。它从i开始,一直到数组开头,累加_sums中的元素,直到处理完整个前缀。
下面是一个简单的使用树状数组的示例:
arr = [1, 3, 2, 4, 5, 6, 1]
n = len(arr)
tree = FenwickTree(n)
# 构建树状数组
for i in range(1, n + 1):
tree.update(i, arr[i - 1])
# 查询前缀和
print(tree.query(3)) # 输出: 6
# 更新元素
tree.update(3, 1)
print(tree.query(3)) # 输出: 7
在这个示例中,我们首先构建了一个长度为7的数组,并将其存储在一个树状数组中。我们可以使用query()方法查询前缀和,例如查询前三个元素的和为6。随后,我们更新了数组中的第三个元素,并再次查询前三个元素的和,得到了7作为结果。
线段树
线段树(Segment Tree)是一种常用的数据结构,它可以高效地处理区间查询和单点更新问题。它的核心思想是将一个区间划分成若干个小区间,然后将每个小区间的信息合并起来,以表示整个区间的信息。下面是一个用Python实现线段树的示例代码:
class SegmentTree:
def __init__(self, nums):
n = len(nums)
self.tree = [0] * (2 * n)
self.nums = nums
# 构建线段树
for i in range(n):
self.tree[n + i] = nums[i]
for i in range(n - 1, 0, -1):
self.tree[i] = self.tree[2 * i] + self.tree[2 * i + 1]
def update(self, i, val):
n = len(self.nums)
pos = n + i
diff = val - self.nums[i]
self.nums[i] = val
# 更新线段树
while pos > 0:
self.tree[pos] += diff
pos //= 2
def query(self, l, r):
n = len(self.nums)
l += n
r += n
res = 0
# 区间查询
while l <= r:
if l % 2 == 1:
res += self.tree[l]
l += 1
if r % 2 == 0:
res += self.tree[r]
r -= 1
l //= 2
r //= 2
return res
在这个实现中,我们使用一个数组tree来存储线段树。对于每个节点$i$,tree[2i]和tree[2i+1]表示它的左右子节点,tree[i]表示从i到其子节点的所有元素的和。构建线段树的过程可以使用递归或迭代方式完成。在这个实现中,我们使用了迭代的方式。
update()方法用于更新线段树中的一个元素。我们首先计算该元素在tree中的位置pos,并计算出新旧值之间的差diff。然后,我们从pos开始向上更新整个线段树,直到到达根节点。query()方法用于查询区间和。我们首先将区间左端点和右端点都转换为线段树中的下标,然后从下往上遍历线段树,依次累加相应的元素,直到覆盖了整个区间。
下面是一个简单的使用线段树的示例:
nums = [1, 3, 5, 7, 9, 11]
n = len(nums)
tree = SegmentTree(nums)
# 查询区间和
print(tree.query(1, 4)) # 输出: 24
# 更新元素
tree.update(2, 6)
print(tree.query(1, 4)) # 输出: 25
在这个示例中,我们首先使用一个数组构建了一个线段树,然后查询了区间[1,4]的和,结果为24。接着,我们更新了第3个元素的值为6,并查询了区间[1,4]的和,结果为25。
总的来说,线段树是一种非常实用的数据结构,它可以在O(log n)的时间内完成区间查询和单点更新操作,可以应用于很多算法问题中,如RMQ(区间最小值)、区间求和、区间加、区间乘等。
单调栈/队列
单调栈/队列(Monotonic Stack/Queue)是一种常用的数据结构,它可以在O(n)的时间内解决很多与区间最值相关的问题。单调栈/队列有两种类型:单调递增和单调递减。这里我们以单调递减栈为例,来介绍单调栈的基本思想和用法。
单调递减栈的核心思想是:维护一个单调递减的栈,每次将一个元素压入栈中时,先将比它小的元素都弹出栈,然后再将它压入栈。这样,栈顶元素就是当前栈中最小的元素。当需要求解区间最值时,我们可以用单调栈来维护当前区间的最小值或最大值。具体实现如下:
def mono_stack(nums):
stack = []
res = []
for i in range(len(nums)):
while stack and nums[i] <= nums[stack[-1]]:
stack.pop()
res.append(stack[-1] if stack else -1)
stack.append(i)
return res
在这个实现中,我们使用了一个列表stack来存储单调递减栈。对于每个元素nums[i],我们先将比它小的元素都从栈中弹出,直到栈为空或栈顶元素比nums[i]大。然后,我们将i压入栈中,并将当前栈顶元素的下标加入到结果列表res中。
下面是一个简单的使用单调栈求解区间最小值的示例:
nums = [3, 1, 4, 2]
res = mono_stack(nums)
print(res) # 输出: [-1, -1, 1, 1]
在这个示例中,我们输入一个数组[3, 1, 4, 2],并调用mono_stack()函数求解区间最小值。输出结果为[-1, -1, 1, 1],表示每个元素左边第一个比它小的元素的下标。例如,对于元素nums[2]=4,它左边第一个比它小的元素是nums[1]=1,其下标为1。因此,res[2]=1。由此可见,单调栈/队列是一种非常实用的数据结构,它可以在O(n)的时间内完成区间最值的计算。