【问题标题】:Cyclic rotation of array explanation数组循环旋转说明
【发布时间】:2020-01-19 18:50:33
【问题描述】:

前提:我的问题不是 Cyclic rotation in Python 的重复。我不是在问如何解决问题或为什么我的解决方案不起作用,我已经解决了它并且它有效。我的问题是关于我发现的同一问题的另一种特定解决方案,因为我想了解其他解决方案背后的逻辑。

我遇到了以下循环数组旋转问题(在源代码下方):

给定一个由 N 个整数组成的数组 A。数组的旋转意味着每个元素右移一个索引,并且数组的最后一个元素移动到第一位。例如,数组 A = [3, 8, 9, 7, 6] 的旋转为 [6, 3, 8, 9, 7] (元素右移一个索引,6 移到第一位)。 目标是将数组 A 旋转 K 次;也就是A的每个元素都会右移K次。

我设法用以下 Python 代码解决了这个问题:

def solution(A , K):
    N = len(A)
    if N < 1 or N == K:
        return A
    K = K % N
    for x in range(K):
        tmp = A[N - 1]
        for i in range(N - 1, 0, -1):
            A[i] = A[i - 1]
        A[0] = tmp
    return A

然后,在以下网站https://www.martinkysel.com/codility-cyclicrotation-solution/ 上,我找到了以下针对同一问题的奇特解决方案:

def reverse(arr, i, j):
    for idx in xrange((j - i + 1) / 2):
        arr[i+idx], arr[j-idx] = arr[j-idx], arr[i+idx]

def solution(A, K):
    l = len(A)
    if l == 0:
        return []

    K = K%l

    reverse(A, l - K, l -1)
    reverse(A, 0, l - K -1)
    reverse(A, 0, l - 1)

    return A

有人能解释一下这个特殊的解决方案是如何工作的吗? (作者在他的网站上没有解释)

我的解决方案对于大型AK 表现不佳,其中K &lt; N,例如:

    A = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] * 1000
    K = 1000
    expectedResult = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] * 1000
    res = solution(A, K) # 1455.05908203125 ms = almost 1.4 seconds

因为对于K &lt; N,我的代码的时间复杂度为O(N * K),其中N是数组的长度。 对于大 K 和小 N (K &gt; N),由于模运算 K = K % N,我的解决方案表现良好:

    A = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    K = 999999999999999999999999
    expectedRes = [2, 3, 4, 5, 6, 7, 8, 9, 10, 1]
    res = solution(A, K) # 0.0048828125 ms, because K is torn down to 9 thanks to K = K % N

另一方面,另一种解决方案在所有情况下都表现出色,即使在 N &gt; K 并且复杂度为 O(N) 时也是如此。

该解决方案背后的逻辑是什么?

感谢您的关注。

【问题讨论】:

  • 你真的在问为什么a, b = b, a 交换ab
  • 不,我不知道。我了解它的作用,我不了解 3 个 reverse() 调用以及其中的索引的相关计算。
  • this question 中有一些关于算法的信息。

标签: python arrays algorithm list


【解决方案1】:

这是一个非常简单的 Ruby 解决方案。 (在代码中得分 100%) 删除数组中的最后一个元素,并将其插入到开头。

def solution(a, k)
  if a.empty?
    return []
  end
  modified = a
  1.upto(k) do 
    last_element = modified.pop
    modified = modified.unshift(last_element)
  end
  return modified
end

【讨论】:

  • 与问题无关。
  • 除了这个变化:
【解决方案2】:

让我先谈谈K &lt; N的基本情况,这种情况下的想法是将数组分成两部分ABA是第一个NK元素数组,B是最后 K 个元素。算法分别反转AB,最后反转整个数组(两部分分别反转)。要使用K &gt; N 处理这种情况,请认为每次反转数组 N 次都会再次获得原始数组,因此我们可以使用模块运算符找到拆分数组的位置(仅反转真正有用的时间,避免无用的移位)。

图形示例

图形分步示例有助于更好地理解概念。请注意

  • 粗线表示数组的分割点(本例中为K = 3);
  • 红色数组表示输入和预期输出。

起点:

看看我们想要在最终输出前面的最后 3 个字母反转,现在让它反转(算法的第一个反转):

现在反转前 N-K 个元素(算法的第二个反转):

我们已经有了解决方案,但是在相反的方向,我们可以通过反转整个数组来解决它(算法的第三个也是最后一个反转):

这里是最终输出,原始数组循环旋转了 K = 3。

代码示例

让我们再举一个 Python 代码的分步示例,从:

A = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
K = 22
N = len(A)

我们找到分裂指数:

K = K%N
#2

因为,在这种情况下,前 20 个移位将没有用,现在我们将原始数组的最后 K(2) 个元素反转:

reverse(A, N-K, N-1)
# [1, 2, 3, 4, 5, 6, 7, 8, 10, 9]

如您所见,9 和 10 已经移位,现在我们反转前 N-K 个元素:

reverse(A, 0, N-K-1)
# [8, 7, 6, 5, 4, 3, 2, 1, 10, 9]

最后,我们反转整个数组:

reverse(A, 0, N-1)
# [9, 10, 1, 2, 3, 4, 5, 6, 7, 8]

请注意,反转数组的时间复杂度为 O(N)。

【讨论】:

  • 顺便说一下,这个循环旋转算法的整体时间复杂度是O(N)是不是正确,其中N是列表中元素的个数因为,假设K % N,我们有O(K/2)用于右块的第一次反转操作,O(N-K/2)用于左块的左反转操作和O(N / 2)用于反转整个列表,其中导致O(K/2 + N-K/2 + N/2) = O((K + N - K + N)/2) = O(2*N/2) = O(N),对吗?
  • 不完全是,正式地,如果你用时间复杂度术语说话,你不能说O(K/2),因为问题的实例是NK 只是一个常数项。所以最坏的反转情况是0(N),重复3次再次给出O(N)。如果您谈论交换的有效数量是正确的(纯交换,我们注意不再谈论 O()),在这种情况下,我们可以说该算法使 K/2 + N-K/2 + N/2 = N 交换。请注意,多次分析交换次数对于给出和了解算法效率非常重要。
  • 好的。但是:So the worst reversing case is O(N), that repeated 3 times gives again O(N) 是否意味着O(N) 重复3 次应该导致O(3 * N)?这不适用于该算法的情况,该算法具有N 交换次数,其中N 也是列表/数组的元素数。另外,我会说反向函数的时间复杂度始终为O(N/2),因为元素通过循环进行交换,直到到达数组的中间......我只是试图遵循这个推理,以便我能更好地理解。
  • 您以错误的方式使用渐近符号。查看大 O 表示法的 this 属性。 O(3*N) = O(N/2) = O(N) 因为当您使用此表示法时,您对算法的时间/空间复杂度给出了更高的限制。顺便说一句,你对纯掉期的分析是完美的。但是你不能使用大 O 表示法。查看Big O notation的维基百科页面
  • 感谢您的回复法比奥!所以它有点像函数极限的数学概念,例如lim x -&gt; +∞f(x) = k * xf(∞) = k * ∞ = ∞。对吗?
猜你喜欢
  • 2018-02-25
  • 2023-03-18
  • 1970-01-01
  • 2021-04-27
  • 1970-01-01
  • 2021-11-25
  • 2017-10-30
  • 1970-01-01
  • 2017-08-27
相关资源
最近更新 更多