检查一个列表是不是是另一个与重复项一起使用的列表的轮换

Posted

技术标签:

【中文标题】检查一个列表是不是是另一个与重复项一起使用的列表的轮换【英文标题】:Check if a list is a rotation of another list that works with duplicates检查一个列表是否是另一个与重复项一起使用的列表的轮换 【发布时间】:2015-09-09 02:57:02 【问题描述】:

我有这个函数来确定一个列表是否是另一个列表的轮换:

def isRotation(a,b):
  if len(a) != len(b):
    return False

  c=b*2
  i=0

  while a[0] != c[i]:
    i+=1

  for x in a:
    if x!= c[i]:
      return False
    i+=1

  return True

例如

>>> a = [1,2,3]
>>> b = [2,3,1]
>>> isRotation(a, b)
True

如何使用重复项进行这项工作?例如

a = [3,1,2,3,4]
b = [3,4,3,1,2]

可以在O(n)time 完成吗?

【问题讨论】:

重复的数量重要吗?示例 - [1,2,3] 和 [3,1,2,3] 它们不同吗? 是的,这些肯定是不同的。 好吧,在这种情况下,记录长度并在它们不同时拒绝匹配。 @MTilsted 旋转数组是从一端获取 N 个元素并将它们放在另一端,这不是任何顺序都有效的洗牌:[1,2,3,4,5,6] - > [3, 4, 5, 6, 1, 2] 高度相关:***.com/questions/2553522/… 还有更多来自... 【参考方案1】:

下面的元算法将解决它。

构建a 的串联,例如a = [3,1,2,3,4] => aa = [3,1,2,3,4,3,1,2,3,4]

运行任何字符串匹配算法的字符串适配,例如,Boyer Moore 以在 aa 中找到 b


我首先尝试的一个特别简单的实现是使用Rabin Karp 作为底层算法。在这种情况下,你会

计算Rabin Fingerprint 的b

计算aa[: len(b)]aa[1: len(b) + 1]、...的Rabin指纹,并仅在指纹匹配时比较列表

注意

可以非常高效地迭代计算滑动窗口的 Rabin 指纹(在 Rabin-Karp 链接中了解它)

如果你的列表是整数,你实际上比字符串更容易,因为你不需要考虑一个字母的数字哈希值是多少

-

【讨论】:

所以,您建议我将其转换为字符串。这听起来效率不高。与将其转换为字符串相比,O(n^2) 解决方案是否会更好? 我当然不是建议你把它转换成一个字符串,而是你应该为它适配一个字符串匹配算法。将填写更多详细信息。【参考方案2】:

您可以使用最大后缀算法的修改版本在0(n) 时间和0(1) 空间中执行此操作:

来自Jewels of Stringology: 单词循环相等

长度为 n 的单词 u 的旋转是 u[k + 1...n][l...k] 形式的任何单词。设 u, w 是长度相同的两个单词 n。如果 u(i) == w(j) 对于某些 i, j,则称它们是循环等价的。

如果单词 u 和 w 写成圆,如果圆在适当的旋转后重合,它们就是循环等价的。

有几种线性时间算法可用于测试循环等价性 的两个词。最简单的一种是将任何字符串匹配算法应用于模式 pat = u 和 text = ww,因为如果 pat 出现在文本中,单词 u 和 w 是 cyclic=equivalent。

另一种算法是找到 uu 和 ww 的最大后缀并检查是否 它们在大小为 n 的前缀上是相同的。我们选择这个问题是因为有更简单有趣的算法,同时在线性时间和恒定空间中工作,值得介绍。

Algorithm Cyclic-Equivalence(u, w)
 checks cyclic equality of u and w of common length n 
    x := uu; y := ww;
    i := 0; j := 0;
    while (i < n) and (j < n) do begin
        k := 1;
        while x[i + k] = y[j + k] do k := k + 1;
        if k > n then return true;
        if x[i + k]> y[i + k] then i := i + k else j := j + k;
         invariant 
    end;
    return false; 

翻译成python变成:

def cyclic_equiv(u, v):
    n, i, j = len(u), 0, 0
    if n != len(v):
        return False
    while i < n and j < n:
        k = 1
        while k <= n and u[(i + k) % n] == v[(j + k) % n]:
            k += 1
        if k > n:
            return True
        if u[(i + k) % n] > v[(j + k) % n]:
            i += k
        else:
            j += k
    return False

运行几个例子:

In [4]: a = [3,1,2,3,4]   
In [5]: b =[3,4,3,1,2]   
In [6]: cyclic_equiv(a,b)
Out[6]: True    
In [7]: b =[3,4,3,2,1]    
In [8]: cyclic_equiv(a,b)
Out[8]: False    
In [9]: b =[3,4,3,2]    
In [10]: cyclic_equiv(a,b)
Out[10]: False
In [11]: cyclic_equiv([1,2,3],[1,2,3])
Out[11]: True
In [12]: cyclic_equiv([3,1,2],[1,2,3])
Out[12]: True

更天真的方法是使用 collections.deque 来旋转元素:

def rot(l1,l2):
    from collections import deque
    if l1 == l2:
        return True
    # if length is different we cannot get a match
    if len(l2) != len(l1):
        return False
    # if any elements are different we cannot get a match
    if set(l1).difference(l2):
        return False
    l2,l1 = deque(l2),deque(l1)
    for i in range(len(l1)):
        l2.rotate() # l2.appendleft(d.pop()) 
        if l1 == l2:
            return True
    return False

【讨论】:

这里是c++ implementation for comparison。 感谢您解决这个问题,帕德莱克。它引起了与this question相关的一点混乱@【参考方案3】:

我认为你可以使用这样的东西:

a1 = [3,4,5,1,2,4,2]
a2 = [4,5,1,2,4,2,3]

# Array a2 is rotation of array a1 if it's sublist of a1+a1
def is_rotation(a1, a2):
   if len(a1) != len(a2):
       return False

   double_array = a1 + a1

   return check_sublist(double_array, a2)

def check_sublist(a1, a2):
   if len(a1) < len(a2):
       return False

   j = 0
   for i in range(len(a1)):
        if a1[i] == a2[j]:
              j += 1
        else:
              j = 0

        if j == len(a2):
              return True

   return j == len(a2)

如果我们谈论的是面试问题,这只是常识

我们应该记住,解决方案应该易于编码和描述。 不要试图在面试时记住解决方案。最好记住核心原则并重新实现它。

【讨论】:

“in”键似乎不起作用,实现对重复项有效的键是我的问题。 @RahatMahbub,是的,你是对的,我会修复它。在此任务中重复不是问题。 所以,您建议我将其转换为字符串。这听起来效率不高。与将其转换为字符串相比,O(n^2) 解决方案是否会更好? @RahatMahbub,你可以试试这个 :) 这是检查一个列表是否是另一个列表的最简单方法。 第二个if len(a1) &lt; len(a2):是多余的,如果长度相同a1+a1不能更小【参考方案4】:

或者(我无法让b in aa 解决方案起作用),您可以“旋转”您的列表并检查旋转后的列表是否等于 b:

def is_rotation(a, b):

    for n in range(len(a)):
        c = c = a[-n:] + a[:-n]
        if b == c:
            return True

    return False

我相信这将是 O(n),因为它只有一个 for 循环。希望对你有帮助

【讨论】:

你忘了in 本身就是 O(n) :) 这很酷但是由于b==c,我相信运行时间将是O(n^2)。【参考方案5】:

这似乎行得通。

def func(a,b):
    if len(a) != len(b):
        return False
    elif a == b:
        return True

    indices = [i for i, x in enumerate(b) if x == a[0] and i > 0]

    for i in indices:
        if a == b[i:] + b[:i]:
            return True

    return False

还有这个:

def func(a, b):
    length = len(a)

    if length != len(b):
         return False

    i = 0
    while i < length:
        if a[0] == b[i]:
            j = i
            for x in a:
                if x != b[j]:
                    break
                j = (j + 1) % length
            return True
        i += 1

    return False

【讨论】:

【参考方案6】:

您可以尝试测试仅在 deque 集合中使用 rotate() 函数的性能:

from collections import deque

def is_rotation(a, b):

    if len(a) == len(b):
        da = deque(a)
        db = deque(b)

        for offset in range(len(a)):
            if da == db:
                return True

            da.rotate(1)

    return False

在性能方面,您需要对小型数组进行多次计算,还是对非常大的数组进行几次计算?这将决定特殊情况测试是否会加快速度。

【讨论】:

这是一个面试问题,所以对尺寸没有特别要求。但是用 for 循环旋转应该会导致 O(n^2)。我的算法确实对除重复项之外的任何输入进行 O(n)。【参考方案7】:

如果您可以将它们表示为字符串,则只需:

def cyclically_equivalent(a, b):
    return len(a) == len(b) and a in 2 * b

否则,应该得到一个子列表搜索算法,例如 Knuth-Morris-Pratt(Google 提供了一些实现)并做

def cyclically_equivalent(a, b):
    return len(a) == len(b) and sublist_check(a, 2 * b)

【讨论】:

【参考方案8】:

Knuth-Morris-Pratt algorithm 是一个字符串搜索算法,在 O(n) 中运行,其中 n 是文本 S 的长度(假设存在预先构建的表 T,它在 O(m) 中运行,其中 m 是文本的长度)搜索字符串)。总而言之就是O(n+m)。

您可以执行受 KMP 启发的类似模式匹配算法。

将列表连接到自身,例如 a+ab+b - 这是搜索到的包含 2*n 个元素的文本/列表 根据另一个列表(ba)构建表 T - 这是在 O(n) 中完成的 运行受 KMP 启发的算法 - 这是在 O(2*n) 中完成的(因为您将列表连接到自身)

总体时间复杂度为 O(2*n+n) = O(3*n),在 O(n) 中

【讨论】:

以上是关于检查一个列表是不是是另一个与重复项一起使用的列表的轮换的主要内容,如果未能解决你的问题,请参考以下文章

如何检查平面列表中是不是有重复项?

检查一个数组是不是是另一个数组的子集

将 .index() 与具有重复元素的列表一起使用 [重复]

检查列表是不是包含与某些东西不同的元素[重复]

使用 Python 删除对象列表中的重复项

如何使用重复项编辑列表项