为所有非递减序列的列表开发索引方案

时间:2019-02-04 22:53:25

标签: python python-3.x algorithm math

假设我们正在考虑所有非递减序列的排序列表,且每个序列的值在(1, max_num)num_slots元素范围内,如何在其中找到某个给定成员序列的索引O(1)时间复杂度?我不是,实际上是预先给出了整个列表,我只想查找某些成员序列的索引,即所有存在序列的列表。

举一个具体的例子,假设max_num = 3num_slots = 4。然后有15个序列(或者通常有(max_num + num_slots - 1) choose (num_slots)个序列):

[[1, 1, 1, 1],
 [1, 1, 1, 2],
 [1, 1, 1, 3],
 [1, 1, 2, 2],
 [1, 1, 2, 3],
 [1, 1, 3, 3],
 [1, 2, 2, 2],
 [1, 2, 2, 3],
 [1, 2, 3, 3],
 [1, 3, 3, 3],
 [2, 2, 2, 2],
 [2, 2, 2, 3],
 [2, 2, 3, 3],
 [2, 3, 3, 3],
 [3, 3, 3, 3]]

因此,给定像[1, 2, 2, 3]这样的序列输入以及信息max_num = 3,我试图编写一个函数,该函数将返回其正确的索引7。我实际上没有所有的列表要使用的序列。


背景信息

我想出了一种算法来生成我关心的所有非递减序列,但这似乎与在没有实现整个序列列表的情况下生成特定成员序列的索引并不完全相关。

def gen(max_num, num_slots, l = None): 
    if l is None: 
        l = [[1] * num_slots] 
    cur = l[-1].copy() 
    for i in reversed(range(num_slots)): 
        if cur[i] < max_num: 
            cur[i] += 1 
            for j in range(i+1, num_slots): 
                cur[j] = cur[i] 
            l.append(cur) 
            return gen(max_num, num_slots, l) 
    return l

4 个答案:

答案 0 :(得分:4)

这是O(|seq| + max_num)。请注意,这仍然比朴素的“生成所有并搜索”方法快得多,后者在|seq|中是指数级的。

这个想法是在输入序列之前对序列进行计数。例如, 您想知道max_num = 6时[2,4,5,6]的索引是什么。

  • 计数[1,*,*,*]
  • 数[2,2,*,*]
  • 数[2,3,*,*]
  • (注意:您不能计算[2,4,*,*],因为那样的话您会在输入之后加上[2,4,6,6]。您应该一直到比输入少1为止。给定的索引)
  • 数[2,4,4,*]
  • 数[2,4,5,5]

(对于每一行,您可以使用公式(max_num + num_slots - 1) choose (num_slots)并对其求和)

def combinations(slots, available):
    return choose(slots + available - 1, slots)

def find_index(seq, max_num):
    res = 0
    for digit_index in xrange(len(seq)):
        prev = seq[digit_index - 1] if digit_index > 0 else 1
        for digit in xrange(prev, seq[digit_index]):
            res += combinations(len(seq) - digit_index - 1, max_num - digit + 1)
    return res


print find_index([1, 2, 2, 3], 3)

答案 1 :(得分:1)

我将详细说明@DavidFrank为何为O(length + max_num)的答案,并给出一个更容易理解的示例(也有些复杂)。

首先,请注意:

假设总序列可能性为 F(length,max_num) = X

然后针对X中所有以1开头的可能性,例如[1,....],我们在这个组中有一个 F(length-1,max_num)

对于X中所有不以1开头的可能性,例如[2,....]或[3,....],我们有一个 F(length,max_num-1)的计数。

因此,我们可以使用递归来获得O(length * max_num)(如果使用备忘录可以变成O(length + max_num))复杂度:

# This calculate the total number of X of possible entry given (length, max_num)
def calc_sum(length, max_num):
    if max_num == 1:
        return 1
    elif length == 1:
        return max_num
    else:
        total = calc_sum(length-1, max_num) + calc_sum(length, max_num-1)
        return total

现在,我们检查结果以查看是否可以将其设为O(1):

# This is clearly not going to make it O(1), so now we need some generalizations to NOT run this recursion.
import numpy as np
arr = np.zeros((6,6))
for i in range(6):
    for j in range(6):
        arr[i, j] = calc_sum(i+1, j+1)
print(arr)

结果是:

[[  1.   2.   3.   4.   5.   6.]
 [  1.   3.   6.  10.  15.  21.]
 [  1.   4.  10.  20.  35.  56.]
 [  1.   5.  15.  35.  70. 126.]
 [  1.   6.  21.  56. 126. 252.]
 [  1.   7.  28.  84. 210. 462.]]

这是帕斯卡的三角形,如果您在右上角对角看。帕斯卡三角形的对角线由(x选择y)定义

这很清楚它不能为O(1),并且至少将为O(length + max_num),因为这是(Choose)函数的一般复杂性。

我们一直尝试证明O(1)解决方案是不可能的,除非我们将(length + max_num)约束为常数。

# We can expand by solving it now:
from scipy.special import comb # this is choose function.

def get_index(my_list, max_num):
    my_list = np.array(my_list)
    if len(my_list) == 1:
        return my_list[0] - 1
    elif my_list[0] == 1:
        return get_index(my_list[1:], max_num)
    elif my_list[0] != 1:
        return get_index(my_list - 1, max_num - 1) + comb(len(my_list)-2+max_num, max_num-1)

get_index([1,2,2,3],3) # 7

使用comb()的最终函数的总复杂度仍为O(length + max_num),因为comb以外的所有事物的复杂度也均为O(length + max_num)。

答案 2 :(得分:1)

通过将{1...n}映射到{1...n + k − 1},从{c_0, c_1...c_(k−1)}的k个子集(有重复)到{c_0, c_(1+1), c_(2+2)...c_(k−1+k−1)}的k个子集(无重复)有双射(请参见here

转换后,只需使用您喜欢的组合排名工具即可。

[3, 3, 3, 3]  -->  [3, 4, 5, 6]
[2, 3, 3, 3]  -->  [2, 4, 5, 6]
[2, 2, 3, 3]  -->  [2, 3, 5, 6]
[2, 2, 2, 3]  -->  [2, 3, 4, 6]
[2, 2, 2, 2]  -->  [2, 3, 4, 5]
[1, 3, 3, 3]  -->  [1, 4, 5, 6]
[1, 2, 3, 3]  -->  [1, 3, 5, 6]
[1, 2, 2, 3]  -->  [1, 3, 4, 6]
[1, 2, 2, 2]  -->  [1, 3, 4, 5]
[1, 1, 3, 3]  -->  [1, 2, 5, 6]
[1, 1, 2, 3]  -->  [1, 2, 4, 6]
[1, 1, 2, 2]  -->  [1, 2, 4, 5]
[1, 1, 1, 3]  -->  [1, 2, 3, 6]
[1, 1, 1, 2]  -->  [1, 2, 3, 5]
[1, 1, 1, 1]  -->  [1, 2, 3, 4]
import pyncomb

def convert(m, S):
  return (m + len(S) - 1, [ x-1 + i for x,i in zip(S, list(xrange(len(S)))) ])

def rank(m, S):
  k, s = convert(m, S)
  return pyncomb.ksubsetcolex.rank(k, s)

print rank(3, [1,2,2,3])
# 7

答案 3 :(得分:0)

对于每个数字,找出该数字与最低数字之间的差异。在任何更改的数字的右边,每个更改的位置加1。

idx = 0;
for i in range(0,num_slots):
    d = SEQ[i]
    idx += d-min_num
    if (d > min_num):
        idx += num_slots-1 - i

例如:
 [1,1,1,3]0 + 0 + 0 + (2+0)2
[1,2,3,3]0 + (1+2) + (2+1) + (2+0)8
[3,3,3,3](2+3) + (2+2) + (2+1) + (2+0)14