不使用比较运算符在O(nlog(n))时间内对整数数组按值分组的算法问询
核心思路
利用二分查找匹配新元素的所属分组,将每个元素的query调用次数控制在O(logn)量级,总调用次数符合O(nlogn)要求。
核心判断逻辑:如果将新元素加入若干个已有分组的代表元素集合后,query返回的不同值数量没有增加,说明新元素的值已经存在于这些分组中。
具体实现步骤
- 按顺序逐个处理数组下标(不直接访问元素值,仅用下标指代元素),同时维护每个分组的代表元素列表(每个分组取第一个元素作为代表)
- 对于当前处理的新下标x,在已有的分组集合中做二分查找:
- 取二分中点mid,将前mid+1个分组的代表元素和x组成查询子集
- 若
query(子集)等于mid+1(前mid+1个分组本身有mid+1个不同值),说明x的值存在于前mid+1个分组中,收缩右边界 - 否则说明x的值不在前mid+1个分组中,收缩左边界
- 二分结束后验证左边界指向的分组是否和x值相等,相等就加入该分组,否则新建分组存储x
完整代码实现
import random # 题目给定初始配置 n = 100 querycalls = 0 secretarray = [random.randint(0, n-1) for i in range(n)] def query(items): global querycalls querycalls += 1 return len(set(secretarray[i] for i in items)) groups = [] reps = [] # 存储每个分组的代表元素下标,不同代表对应值一定不同 # 核心算法实现 for x in range(n): if not groups: groups.append([x]) reps.append(x) continue left = 0 right = len(groups) - 1 target = len(groups) # 二分查找所属分组 while left <= right: mid = (left + right) // 2 check_list = reps[:mid+1] + [x] distinct_cnt = query(check_list) if distinct_cnt == mid + 1: target = mid right = mid - 1 else: left = mid + 1 # 验证并加入分组 if target < len(groups) and query([reps[target], x]) == 1: groups[target].append(x) else: groups.append([x]) reps.append(x) # 结果正确性验证 correct = True # 验证同组元素值相同 for g in groups: base_val = secretarray[g[0]] for idx in g: if secretarray[idx] != base_val: correct = False break if not correct: break # 验证不同组元素值不同 seen_vals = set() for r in reps: if secretarray[r] in seen_vals: correct = False break seen_vals.add(secretarray[r]) print(f"分组正确性:{correct}") print(f"总query调用次数:{querycalls}") print(f"nlogn量级参考值(n=100时):{int(n*7)}")
复杂度说明
每个元素最多需要O(logk)次二分查询(k为当前已有的分组数,最大不超过n),加上1次验证查询,总query调用次数稳定在O(nlogn)量级。以n=100为例,调用次数通常在700~900之间,远低于O(n²)的10000次上限。
内容的提问来源于stack exchange,提问作者Muffinlicious
相关产品推荐
相关产品推荐

