You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

生成满足源元素最小间隔要求的随机排列的优化方案咨询

问题描述

给定包含n个唯一元素的序列a,需生成a的一个随机排列序列b,要求将b追加到a后,所有重复元素之间的最小距离不小于指定值d。

例如,当a=[1,2,3]且d=2时,6种可能的排列中仅前4种符合要求:

a         b
[1, 2, 3] (1, 2, 3) mindist = 3
[1, 2, 3] (1, 3, 2) mindist = 2
[1, 2, 3] (2, 1, 3) mindist = 2
[1, 2, 3] (2, 3, 1) mindist = 2
[1, 2, 3] (3, 1, 2) mindist = 1
[1, 2, 3] (3, 2, 1) mindist = 1

后两种的最小距离1 < d,不符合要求。

原实现及性能瓶颈

原实现代码如下:

import random
n = 10
alist = list(range(n))

blist = alist[:]

d = n//2

avail_indices = list(range(n))
for a_ind, a_val in enumerate(reversed(alist)):
  min_ind = max(d - a_ind - 1, 0)
  new_ind = random.choice(avail_indices[min_ind:])
  avail_indices.remove(new_ind)
  blist[new_ind] = a_val
print(alist, blist)

该实现的时间复杂度为O(n²),核心瓶颈在于avail_indices.remove(new_ind)操作:列表的remove方法需要遍历查找元素,每次耗时O(n),循环n次后总复杂度达到O(n²),n增大时耗时会显著上升。

优化实现方案

方案一:使用SortedList(第三方库,最优复杂度)

借助sortedcontainers.SortedList可以实现O(logn)时间的插入、删除和二分查找操作,彻底解决原代码的性能瓶颈。

代码示例:

import random
from sortedcontainers import SortedList

n = 10
alist = list(range(n))
blist = [0] * n
d = n // 2

avail_indices = SortedList(range(n))
for a_ind, a_val in enumerate(reversed(alist)):
    min_pos = max(d - a_ind - 1, 0)
    # 找到可选范围的起始位置在有序列表中的索引
    start_idx = avail_indices.bisect_left(min_pos)
    # 在可选范围内随机选择一个元素的索引
    selected_idx = random.randint(start_idx, len(avail_indices) - 1)
    new_ind = avail_indices.pop(selected_idx)
    blist[new_ind] = a_val

print(alist, blist)

复杂度分析

每次bisect_left和pop操作的时间复杂度为O(logn),循环n次后总时间复杂度为O(nlogn),相比原O(n²)实现有数量级的性能提升,n越大优化效果越明显。

方案二:纯Python实现(无第三方库,近似高效)

如果无法使用第三方库,可借助bisect模块优化查找逻辑,同时用索引pop替代元素remove,虽然理论复杂度仍为O(n²),但实际运行效率远高于原代码。

代码示例:

import random
import bisect

n = 10
alist = list(range(n))
blist = [0] * n
d = n // 2

avail_indices = list(range(n))
for a_ind, a_val in enumerate(reversed(alist)):
    min_ind = max(d - a_ind - 1, 0)
    # 找到可用索引中第一个大于等于min_ind的位置
    start_pos = bisect.bisect_left(avail_indices, min_ind)
    # 在可选范围内随机选择一个索引位置
    selected_pos = random.randint(start_pos, len(avail_indices)-1)
    # 通过索引直接删除,比remove更高效
    new_ind = avail_indices.pop(selected_pos)
    blist[new_ind] = a_val

print(alist, blist)

方案三:线段树实现(纯Python,严格O(nlogn))

对于超大规模的n,可通过实现线段树来维护可用位置,支持O(logn)时间的第k个可用位置查询和更新操作,达到严格的O(nlogn)时间复杂度。

代码示例:

import random

class SegmentTree:
    def __init__(self, size):
        self.n = 1
        while self.n < size:
            self.n <<= 1
        self.tree = [0] * (2 * self.n)
        # 初始化叶子节点:标记所有位置为可用
        for i in range(size):
            self.tree[self.n + i] = 1
        # 构建线段树
        for i in range(self.n - 1, 0, -1):
            self.tree[i] = self.tree[2*i] + self.tree[2*i+1]
    
    def query_kth(self, k):
        # 查询第k个可用位置(k从0开始计数)
        node = 1
        while node < self.n:
            left_count = self.tree[2*node]
            if k < left_count:
                node = 2*node
            else:
                k -= left_count
                node = 2*node + 1
        return node - self.n
    
    def update(self, pos):
        # 标记指定位置为已用
        node = self.n + pos
        self.tree[node] = 0
        node >>= 1
        while node >= 1:
            prev_val = self.tree[node]
            self.tree[node] = self.tree[2*node] + self.tree[2*node+1]
            if self.tree[node] == prev_val:
                break
            node >>= 1

def get_prefix_sum(st, r):
    # 计算[0, r]范围内的可用位置数量
    res = 0
    l_node = st.n + 0
    r_node = st.n + r + 1
    while l_node < r_node:
        if l_node % 2 == 1:
            res += st.tree[l_node]
            l_node += 1
        if r_node % 2 == 1:
            r_node -= 1
            res += st.tree[r_node]
        l_node >>= 1
        r_node >>= 1
    return res

n = 10
alist = list(range(n))
blist = [0] * n
d = n // 2

st = SegmentTree(n)
available_count = n

for a_ind, a_val in enumerate(reversed(alist)):
    min_ind = max(d - a_ind - 1, 0)
    # 计算小于min_ind的可用位置数量
    count_less_min = get_prefix_sum(st, min_ind-1) if min_ind > 0 else 0
    # 在可选范围内随机选择第k个可用位置
    selected_k = random.randint(count_less_min, available_count - 1)
    new_ind = st.query_kth(selected_k)
    blist[new_ind] = a_val
    st.update(new_ind)
    available_count -= 1

print(alist, blist)

内容的提问来源于stack exchange,提问作者EllipticalInitial

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 15:05:36