def_compute(self, numbers: List[int], ops: List[str]) -> int: """辅助函数,计算只有加减法的最终结果""" ans = numbers[0] for i inrange(1, len(numbers)): ans += numbers[i] if ops[i - 1] == '+'else -numbers[i] return ans
算法分析
时间复杂度:O(n)
空间复杂度:O(n)
核心技巧:递归处理嵌套 + 分离处理优先级
题目二:含有嵌套的字符串解码
问题描述
给定一个经过编码的字符串,返回它解码后的字符串。 编码规则为:k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。
classSolution: defsort_stack(self, stack: List[int]): deep = self._deep(stack) while deep > 0: # 找到当前 deep 范围内的最大值 maximum = self._max(stack, deep) # 统计最大值出现的次数 k = self._times(stack, deep, maximum) # 将这 k 个最大值沉底 self._down(stack, deep, maximum, k) # 待排序的深度减少 k deep -= k
def_deep(self, stack: List[int]) -> int: """返回栈的深度,不改变栈的数据状况""" ifnot stack: return0 num = stack.pop() deep = self._deep(stack) + 1 stack.append(num) # 恢复现场 return deep
def_max(self, stack: List[int], deep: int) -> int: """从栈当前的顶部开始,往下数deep层,返回这deep层里的最大值""" if deep == 0: return -float('inf') # 返回一个极小值作为基准 num = stack.pop() rest_max = self._max(stack, deep - 1) current_max = max(num, rest_max) stack.append(num) # 恢复现场 return current_max
def_times(self, stack: List[int], deep: int, maximum: int) -> int: """返回maximum在deep层中出现的次数,不改变栈的数据状况""" if deep == 0: return0 num = stack.pop() rest_times = self._times(stack, deep - 1, maximum) times = rest_times + (1if num == maximum else0) stack.append(num) # 恢复现场 return times
def_down(self, stack: List[int], deep: int, maximum: int, k: int): """将k个最大值沉到deep层的底部,剩下的数据状况不变""" if deep == 0: # 递归到底部时,先把k个最大值压入栈 for _ inrange(k): stack.append(maximum) else: num = stack.pop() self._down(stack, deep - 1, maximum, k) # 在递归返回时,只把非最大值的元素压回去 if num != maximum: stack.append(num)
算法分析
时间复杂度:O(n²)
空间复杂度:O(n) (递归栈空间)
核心技巧:多递归函数配合 + 选择排序思想
题目七:打印n层汉诺塔问题的最优移动轨迹
问题描述
打印n层汉诺塔问题的最优移动轨迹。
核心思想
这是一个经典的递归分治问题。要将 i 个盘子从 A 移动到 C:
先将 i-1 个盘子从 A 移动到 B (辅助柱)。
再将第 i 个盘子 (最大的那个) 从 A 移动到 C。
最后将 i-1 个盘子从 B 移动到 C。 这个过程完美地将一个大问题分解为两个规模更小的相同问题和一个简单的单步操作。
defrecursive_function(parameters): # base case: 递归终止条件 if base_condition: return base_result # 递归调用:分解问题 result = recursive_function(modified_parameters) # 处理当前层的逻辑 current_result = process(result) return current_result
2. 回溯模板
1 2 3 4 5 6 7 8 9 10 11 12
defbacktrack(path, choices): # base case if satisfied: result.append(path[:]) # 收集结果 return for choice in choices: # 做选择 path.append(choice) backtrack(path, remaining_choices) # 撤销选择 path.pop()
3. 常见递归模式
选择模式
1 2 3 4 5 6 7 8 9 10 11 12 13
# 对每个元素有选择:要 or 不要 defchoose(i, path): if i == len(arr): process(path) return # 要当前元素 path.append(arr[i]) choose(i + 1, path) path.pop() # 不要当前元素 choose(i + 1, path)
交换模式
1 2 3 4 5 6 7 8 9 10
# 全排列问题 defpermute(arr, i): if i == len(arr): process(arr) return for j inrange(i, len(arr)): swap(arr, i, j) permute(arr, i + 1) swap(arr, i, j) # 回溯
# 遇到空,或者p,或者q,直接返回 # 这是递归的基准情况 (base case) ifnot root or root == p or root == q: # 如果root不空的话,这样从下往上会一直传p或者q return root
# 在左子树和右子树中递归查找 p 和 q l = self.lowestCommonAncestor(root.left, p, q) r = self.lowestCommonAncestor(root.right, p, q)
# 左树也搜到,右树也搜到,返回root # 这意味着p和q分别在root的左右两侧,root是它们的LCA if l and r: return root # 如果左右子树的搜索结果都为空,说明p,q不在此子树 ifnot l andnot r: returnNone # l和r一个为空,一个不为空 # 返回不空的那个,这个非空节点要么是p或q本身,要么已经是p和q的LCA return l if l else r
deflowestCommonAncestor(self, root, p, q): ifnot root or root == p or root == q: return root left = self.lowestCommonAncestor(root.left, p, q) right = self.lowestCommonAncestor(root.right, p, q) if left and right: return root return left if left else right
defmain(): # 创建一个SetAllHashMap对象,模拟带有setAll操作的哈希表,模拟牛客网的输入输出处理 solution = SetAllHashMap() # 读取所有输入行(提高输入效率) lines = sys.stdin.readlines() i = 0 while i < len(lines): # 遍历所有行 line = lines[i].strip() ifnot line: # 跳过空行 i += 1 continue
# 读取本组操作数n n = int(line) # 每个测试用例开始前重置数据结构 solution.__init__()
# 连续读取n行操作 for j inrange(n): i += 1 # 解析当前操作的所有参数,map是把字符串转换成列表,int指明了列表的元素类型 parts = list(map(int, lines[i].strip().split())) op = parts[0] if op == 1: # op=1,put操作,后面有两个参数a, b a, b = parts[1], parts[2] solution.put(a, b) elif op == 2: # op=2,get操作,后面一个参数a a = parts[1] print(solution.get(a)) else: # op=3,setAll操作,后面一个参数a a = parts[1] solution.set_all(a) # 处理完一组数据,i+1进入下一组或结束 i += 1
if __name__ == "__main__": # 在提交时,类名需要改为 Main main()
算法分析
时间复杂度:所有操作都是O(1)
空间复杂度:O(N),N为键的数量
核心技巧:时间戳比较 + 懒更新策略
题目二:实现LRU结构
问题描述
实现 LRUCache 类:
LRUCache(int capacity) 以正整数作为容量capacity初始化LRU缓存
int get(int key) 如果关键字key存在于缓存中,则返回关键字的值,否则返回-1
void put(int key, int value) 如果关键字key已经存在,则变更其数据值value;如果不存在,则向缓存中插入该组key-value。如果插入操作导致关键字数量超过capacity,则应该逐出最久未使用的关键字
# 按k个一组翻转链表 defreverseKGroup(self, head: ListNode, k: int) -> ListNode: start = head # start指向当前组的开始节点 end = self.teamEnd(start, k) # 找到第一组的结束节点 if end isNone: # 如果第一组的长度不足k,直接返回原链表头节点 return head # 第一组很特殊因为牵扯到换头的问题 # 翻转后,第一组的末尾节点end会成为整个链表的新头节点 head = end self.reverse(start, end) # 翻转第一组节点 # 翻转之后start变成了上一组的结尾节点 lastTeamEnd = start # lastTeamEnd记录上一组翻转后的尾节点 # 循环处理剩余的链表 while lastTeamEnd.nextisnotNone: start = lastTeamEnd.next# 下一组的开始节点 end = self.teamEnd(start, k) # 找到下一组的结束节点 if end isNone: # 如果剩余部分的长度不足k,直接返回头节点,不进行翻转 return head self.reverse(start, end) # 翻转当前组 lastTeamEnd.next = end # 将上一组的尾节点与当前组翻转后的头节点(即原来的end)连接起来 lastTeamEnd = start # 更新lastTeamEnd为当前组翻转后的尾节点(即原来的start) return head # 返回新的头节点
# 当前组的开始节点是s,往下数k个找到当前组的结束节点返回 defteamEnd(self, s: ListNode, k: int) -> ListNode: """找到从s开始第k个节点""" # 从s开始,向后移动k-1次 while k - 1 != 0and s isnotNone: s = s.next# s指针后移 k -= 1# 计数器减1 return s # 返回第k个节点,如果不足k个则返回None
# s -> a -> b -> c -> e -> 下一组的开始节点 # 上面的链表通过如下的reverse方法调整成 : e -> c -> b -> a -> s -> 下一组的开始节点 # 翻转从s到e的这一段链表 defreverse(self, s: ListNode, e: ListNode): """翻转从s到e的链表段""" e = e.next# e是当前组的结尾,e.next指向下一组的开头 pre = None# pre是前一个节点,初值为None cur = s # cur是当前节点,初值为s next_node = None# next是下一个节点 # 遍历当前组,直到cur到达下一组的开头 while cur != e: next_node = cur.next# 保存当前节点的下一个节点 cur.next = pre # 将当前节点的next指针指向前一个节点 pre = cur # pre, cur向后移动 cur = next_node s.next = e # 翻转后,原来的头节点s变成了尾节点,它的next应该指向下一组的开头
# 提交如下的方法 defisPalindrome(self, head: ListNode) -> bool: if head isNoneor head.nextisNone: # 空链表或只有一个节点的链表是回文结构 returnTrue slow = head # slow指针每次走一步,fast指针每次走两步 fast = head # 找中点,当fast到达链表末尾时,slow正好在中间位置 while fast.nextisnotNoneand fast.next.nextisnotNone: slow = slow.next# slow指针后移一步 fast = fast.next.next# fast指针后移两步 # 现在中点就是slow,从中点开始往后的节点逆序 pre = slow # pre是反转后的链表的头节点,初始是slow cur = pre.next# cur是当前要处理的节点,初始是slow的下一个 next_node = None# next_node用于保存cur的下一个节点 pre.next = None# 断开前半部分和后半部分的连接 # 循环反转后半部分链表 while cur isnotNone: next_node = cur.next# 保存下一个节点 cur.next = pre # 当前节点的next指向前一个节点(pre) pre = cur # pre和cur指针后移 cur = next_node # 上面的过程已经把链表调整成从左右两侧往中间指 # head -> ... -> slow <- ... <- pre ans = True# ans默认为True,即假设是回文 left = head # left指针从头开始 right = pre # right指针从后半部分的头(即反转前的尾)开始 # left往右、right往左,每一步比对值是否一样,如果某一步不一样答案就是false while left isnotNoneand right isnotNone: if left.val != right.val: # 如果左右两边的值不相等 ans = False# 那么不是回文 break# 退出循环 left = left.next# 移动指针 right = right.next # 本着不坑的原则,把链表调整回原来的样子再返回判断结果 # 再次反转后半部分,恢复原链表结构 cur = pre.next# cur是当前要处理的节点,初始是pre的下一个 pre.next = None# 断开连接 next_node = None# next_node用于保存cur的下一个节点 # 循环将后半部分链表反转回来 while cur isnotNone: next_node = cur.next# 保存下一个节点 cur.next = pre # 当前节点的next指向前一个节点(pre) pre = cur # pre和cur指针后移 cur = next_node return ans # 返回最终的判断结果
# 找链表中点 slow = fast = head while fast and fast.next: slow = slow.next fast = fast.next.next # slow指向中点
# 判断链表是否有环 slow = fast = head while fast and fast.next: slow = slow.next fast = fast.next.next if slow == fast: returnTrue# 有环 returnFalse
2. 虚拟头节点技巧
当需要修改头节点时,使用虚拟头节点简化操作:
1 2 3 4
dummy = ListNode(0) dummy.next = head # 对dummy.next进行操作 return dummy.next
3. 双指针技巧
用于处理需要同时操作两个位置的问题:
1 2 3 4 5 6 7 8 9
# 删除倒数第n个节点 dummy = ListNode(0, head) first = second = dummy for _ inrange(n + 1): first = first.next while first: first = first.next second = second.next second.next = second.next.next
4. 链表翻转技巧
翻转是链表的基础操作:
1 2 3 4 5 6 7 8 9
defreverse(head): pre = None cur = head while cur: next_node = cur.next cur.next = pre pre = cur cur = next_node return pre
defbitset_union(bitset1, bitset2): """位图并集运算""" result = Bitset(max(len(bitset1.set), len(bitset2.set)) * 32) for i inrange(min(len(bitset1.set), len(bitset2.set))): result.set[i] = bitset1.set[i] | bitset2.set[i] return result
defbitset_intersection(bitset1, bitset2): """位图交集运算""" result = Bitset(max(len(bitset1.set), len(bitset2.set)) * 32) for i inrange(min(len(bitset1.set), len(bitset2.set))): result.set[i] = bitset1.set[i] & bitset2.set[i] return result
@staticmethod defadd(a, b): """位运算实现加法""" #单次相加的结果为:无进位相加的结果+进位信息 ans = a while b != 0: # 当b为0时,说明没有进位了,加法结束 # 单次进位只能把当前位的进位信息加到下一高位,但新一位的进位可能和更高位产生新的进位冲突,需要继续处理。 # 只有所有进位都为0,结果才是完整无误的。 # ans : a和b无进位相加的结果 ans = a ^ b # b : a和b相加时的进位信息 b = (a & b) << 1 # Python中int无限大,为了模拟int32,需要对超出部分进行处理 # 下面两行确保ans和b都保持32位 ans &= 0xFFFFFFFF b &= 0xFFFFFFFF a = ans # 处理负数转为补码 return ans if ans <= 0x7FFFFFFFelse ~(ans ^ 0xFFFFFFFF) #结果超过最大值时,需要将无符号32位结果转换为有符号,当加法结果超过0x7FFFFFFF时,实际上表示的是负数,需要转换为对应的有符号表示 #将ans与全1进行异或,相当于按位取反,~(...):再次取反,相当于恢复原值
@staticmethod defdivide(a, b): """主除法函数,处理各种边界情况""" # 处理 a 和 b 都为最小值的情况 if a == BitOperationAddMinusMultiplyDivide.MIN and b == BitOperationAddMinusMultiplyDivide.MIN: # a和b都是整数最小 return1 # 处理 a 和 b 都不是最小值的情况 if a != BitOperationAddMinusMultiplyDivide.MIN and b != BitOperationAddMinusMultiplyDivide.MIN: # a和b都不是整数最小,那么正常去除 return BitOperationAddMinusMultiplyDivide.div(a, b) # 处理 b 为最小值的情况 if b == BitOperationAddMinusMultiplyDivide.MIN: # a不是整数最小,b是整数最小,整数最小值是负数,而且整数最小值无法转成相反数 return0 # 处理 a 为最小值,b 为 -1 的情况(防止溢出) # 第1个if不成立:a 和 b 不都是最小值;第2个if不成立:a 和 b 不都不是最小值;第3个if不成立:b 不是最小值,则排除完b,a就是最小值 if b == BitOperationAddMinusMultiplyDivide.neg(1): # a是整数最小,b是-1,返回整数最大,因为题目里明确这么说了 return BitOperationAddMinusMultiplyDivide.MAX # a是整数最小,b不是整数最小,b也不是-1 a = BitOperationAddMinusMultiplyDivide.add(a, b if b > 0else BitOperationAddMinusMultiplyDivide.neg(b)) #让 a 不再是最小值,这样就可以安全地调用 div 函数了,如果 b > 0:a = a + b;如果 b < 0:a = a + (-b) ans = BitOperationAddMinusMultiplyDivide.div(a, b) # 现在 a 不再是最小值,可以安全地调用 div 函数 offset = BitOperationAddMinusMultiplyDivide.neg(1) if b > 0else1# 如果 b > 0,则 offset = -1;如果 b < 0,则 offset = 1 return BitOperationAddMinusMultiplyDivide.add(ans, offset) # 最后把 offset 加回去
核心除法算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
@staticmethod defdiv(a, b): #向下取整,但是不返回余数 """核心除法实现,要求a和b都不是整数最小值""" x = BitOperationAddMinusMultiplyDivide.neg(a) if a < 0else a # 取绝对值 y = BitOperationAddMinusMultiplyDivide.neg(b) if b < 0else b # 取绝对值 ans = 0 i = 30 while i >= 0: # (x >> i) >= y 时,说明y << i 还能减掉 if (x >> i) >= y: #判断x右移i位后是否大于等于y,若大于则记录1 ans |= (1 << i) # 记录这个位 x = BitOperationAddMinusMultiplyDivide.minus(x, y << i) # x 减去 y << i,即y*2^i i = BitOperationAddMinusMultiplyDivide.minus(i, 1) # 相当于 i-- # 最后根据正负判断符号 return BitOperationAddMinusMultiplyDivide.neg(ans) if (a < 0) ^ (b < 0) else ans #当两个数的符号不同时,结果取负;当符号相同时,结果保持正。
# 更简洁的变量交换 defswap_variables(): a, b = -2323, 10 a = a ^ b # a现在是原a^原b b = a ^ b # b现在是原a^原b^原b = 原a a = a ^ b # a现在是原a^原b^原a = 原b return a, b # 示例用法 if __name__ == "__main__": a = -2323# 定义a b = 10# 定义b 因为a^b=b^a ,这么操作的前提是a和b都有自己的内存空间 a = a ^ b # 第一步,a和b异或后的结果给a b = a ^ b # 第二步,a和b再次异或的结果赋给b,b'=(a^b)^b a = a ^ b # 第三步,a和b再次异或的结果赋给a,a''=a^b^((a^b)^b)=a^b^a^b^b=a^b^a,根据交换律,a^b^a=a^a^b=b print(a) # 输出此时的a print(b) # 输出此时的b
defnear2power(n): if n <= 0: return1# 非正数直接返回1 n -= 1# 先减1,保证等于2的幂时不变,这样处理可以保证:如果 n 本身就是 2 的幂,返回的还是 n 本身。 # 例如 n = 8,n-1 = 7 (0111),后面填充后再加1,结果还是 8 n |= n >> 1#这个是或逻辑,右移1位再取或可以保证用1填充右边 n |= n >> 2 n |= n >> 4 n |= n >> 8 n |= n >> 16# 这一坨代码的作用是把左边第一个1开始,往右的1都变成1 n += 1# 再加1得到最小2的幂 # Python没有int溢出,可以直接返回 if n > 0x7fffffff: return -0x80000000# 超过int范围,返回整数最小值 return n
# 测试链接: https://leetcode.cn/problems/bitwise-and-of-numbers-range/ defrangeBitwiseAnd(left, right): while left < right: right -= right & -right # 每次消掉right最右边的1 return right #当 left == right 时,区间内只有1个数,直接返回即可;若left>right, 也照样返回right
defheapify(i, size): """ i位置的数,向下调整大根堆 当前堆的大小为size 时间复杂度:O(logn) """ l = i * 2 + 1# 左孩子 while l < size: # 如果左孩子存在 # 选出左右孩子中更大的那个 best = l + 1if (l + 1 < size and arr[l + 1] > arr[l]) else l # 当前节点和最强孩子之间,选出最强的 best = best if arr[best] > arr[i] else i if best == i: # 如果当前节点最大,调整完成 break swap(best, i) # 交换 i = best # 移动到交换后的位置 l = i * 2 + 1# 更新左孩子 # 1.找到更大的子节点:比较左右子节点,选出值更大的那个 # 2.与父节点比较:将较大的子节点与当前父节点比较 # 3.决定是否交换:如果子节点更大就交换,否则停止调整 # 4.继续向下:交换后继续对新位置进行同样的操作
defmain(): global n data = sys.stdin.read().split() n = int(data[0]) for i inrange(n): arr[i] = int(data[i + 1]) heapSort2() # 使用优化版本 # 高效输出 sys.stdout.write(' '.join(str(arr[i]) for i inrange(n - 1))) sys.stdout.write(' ' + str(arr[n - 1]) + '\n')
defheapInsert(i): while arr[i] > arr[(i - 1) // 2]: swap(i, (i - 1) // 2) i = (i - 1) // 2
defheapify(i, size): l = i * 2 + 1 while l < size: best = l + 1if (l + 1 < size and arr[l + 1] > arr[l]) else l best = best if arr[best] > arr[i] else i if best == i: break swap(best, i) i = best l = i * 2 + 1
defswap(i, j): arr[i], arr[j] = arr[j], arr[i]
defheapSort1(): for i inrange(n): heapInsert(i) size = n while size > 1: swap(0, size - 1) size -= 1 heapify(0, size)
defheapSort2(): for i inrange(n - 1, -1, -1): heapify(i, n) size = n while size > 1: swap(0, size - 1) size -= 1 heapify(0, size)
# 使用dict统计 text = "hello world hello python" word_count = {} for word in text.split(): word_count[word] = word_count.get(word, 0) + 1 print(word_count) # {'hello': 2, 'world': 1, 'python': 1}
场景2:去重并保持顺序
1 2 3 4 5 6 7 8 9 10 11 12
# 使用dict保持插入顺序的去重 defdedupe_keep_order(items): seen = {} result = [] for item in items: if item notin seen: seen[item] = True result.append(item) return result
defadd(x): global size heap[size] = x i = size size += 1 # 上浮调整 while i > 0and heap[i] < heap[(i - 1) // 2]: swap(i, (i - 1) // 2) i = (i - 1) // 2
defpop(): global size swap(0, size - 1) size -= 1 i = 0 l = 1 # 下沉调整 while l < size: best = l + 1if l + 1 < size and heap[l + 1] < heap[l] else l best = best if heap[best] < heap[i] else i if best == i: break swap(i, best) i = best l = i * 2 + 1
defminMeetingRooms(meeting): n = len(meeting) meeting.sort(key=lambda x: x[0]) # 按起始时间排序 heap = [] # 小根堆,会议的结束时间 ans = 0 for i inrange(n): while heap and heap[0] <= meeting[i][0]: # 堆顶会议已结束 heapq.heappop(heap) heapq.heappush(heap, meeting[i][1]) # 当前会议结束时间入堆 ans = max(ans, len(heap)) # 记录最大会议室数量 return ans
分组问题
1 2 3 4 5 6 7 8 9 10 11 12 13
defminGroups(meeting): n = len(meeting) meeting.sort(key=lambda x: x[0]) heap = [] ans = 0 for i inrange(n): while heap and heap[0] < meeting[i][0]: # 注意此处是 <,题意不同 heapq.heappop(heap) heapq.heappush(heap, meeting[i][1]) ans = max(ans, len(heap)) return ans
defhalveArray1(nums): # 大根堆(用负数模拟) heap = [] sum_val = 0 for num in nums: heapq.heappush(heap, -float(num)) # 负数模拟大根堆 sum_val += num sum_val /= 2# 要减少到总和一半 ans = 0# 操作次数 minus = 0# 已减少的和 while minus < sum_val: cur = -heapq.heappop(heap) / 2# 取出最大数、减半 heapq.heappush(heap, -cur) # 新值放回堆 minus += cur # 记录减少量 ans += 1# 操作次数加1 return ans
defhalveArray2(nums): global size size = len(nums) sum_val = 0 # 初始化大根堆,左移20位保证精度 for i inrange(size - 1, -1, -1): heap_arr[i] = int(nums[i]) << 20# 左移20位放大 sum_val += heap_arr[i] heapify(i) # 调整堆 sum_val //= 2# 目标减少量 ans = 0 minus = 0 while minus < sum_val: heap_arr[0] //= 2# 堆顶减半 minus += heap_arr[0] heapify(0) ans += 1 return ans
defheapify(i): global size l = i * 2 + 1 while l < size: best = l + 1if l + 1 < size and heap_arr[l + 1] > heap_arr[l] else l best = best if heap_arr[best] > heap_arr[i] else i if best == i: break swap(best, i) i = best l = i * 2 + 1
defquickSort1(l, r): # l == r,只有一个数 # l > r,范围不存在,不用管 if l >= r: return # 随机这一下,常数时间比较大 # 但只有这一下随机,才能在概率上把快速排序的时间复杂度收敛到O(n * logn) # l......r 随机选一个位置,x这个值,做划分 x = arr[random.randint(l, r)] mid = partition1(l, r, x) #会返回x的下标即index quickSort1(l, mid - 1) quickSort1(mid + 1, r)
# 已知arr[l....r]范围上一定有x这个值 # 划分数组 <=x放左边,>x放右边 # 并且确保划分完成后<=x区域的最后一个数字是x defpartition1(l, r, x): # a : arr[l....a-1]范围是<=x的区域 # xi : 记录在<=x的区域上任何一个x的位置,哪一个都可以 a = l # 初始化指针a,表示<=x区域的右边界,初值为l xi = 0# 初始化xi,用于记录<=x区域内某一个x的位置(后面需要与a-1交换) for i inrange(l, r + 1): # 从l遍历到r,依次考察每个元素,每一步都会i++(重要,所以这里的代码没有显式写i++) if arr[i] <= x: # 如果当前元素小于等于x swap(a, i) # 把当前元素交换到a位置(即<=x区的后面) if arr[a] == x: # 如果新放到a位置的元素等于x xi = a # 记录下这个位置为xi a += 1# <=x区往右扩展一位 swap(xi, a - 1) # 把<=x区域里某个x与区域最后一个元素交换,保证x落在最后 return a - 1# 返回<=x区域的最后一个下标(即x最终所在的位置)
@staticmethod defrandomizedSelect(arr, i): """ 连递归都没用,所以时间复杂度是O(n)左右的量级 在数组中找到如果排序后在位置i的元素 时间复杂度:O(n) """ ans = 0 l = 0 r = len(arr) - 1 while l <= r:#不断二分的过程中,l和r上是仍然有范围的,直到没有范围才终止 # 随机这一下,常数时间比较大 # 但只有这一下随机,才能在概率上把时间复杂度收敛到O(n) pivot = arr[l + int(random.random() * (r - l + 1))] # 使用荷兰国旗问题进行划分 RandomizedSelect.partition(arr, l, r, pivot) # 因为左右两侧只需要走一侧 # 所以不需要临时变量记录全局的first、last,直接用即可 # 等于的区域即first,last包住的位置,i小于first则去左侧,i大于last则去右侧 if i < RandomizedSelect.first: r = RandomizedSelect.first - 1# 目标在左侧,pivot左边变成r elif i > RandomizedSelect.last: l = RandomizedSelect.last + 1# 目标在右侧,pivot右边变成l else: ans = arr[i] # 找到目标 break return ans
@staticmethod defpartition(arr, l, r, x): """荷兰国旗问题划分""" RandomizedSelect.first = l RandomizedSelect.last = r i = l while i <= RandomizedSelect.last: if arr[i] == x: i += 1 elif arr[i] < x: RandomizedSelect.swap(arr, RandomizedSelect.first, i) RandomizedSelect.first += 1 i += 1 else: RandomizedSelect.swap(arr, i, RandomizedSelect.last) RandomizedSelect.last -= 1
@staticmethod defswap(arr, i, j): """交换数组元素""" arr[i], arr[j] = arr[j], arr[i]