Python 阵列的循环旋转解释
前提:我的问题不是重复的。我不是问如何解决问题,也不是问为什么我的解决方案不起作用,我已经解决了问题,而且有效。我的问题是关于我发现的同一问题的另一个特定解决方案,因为我想了解另一个解决方案背后的逻辑 我遇到了以下循环数组旋转问题(源代码下方):Python 阵列的循环旋转解释,python,arrays,algorithm,list,Python,Arrays,Algorithm,List,前提:我的问题不是重复的。我不是问如何解决问题,也不是问为什么我的解决方案不起作用,我已经解决了问题,而且有效。我的问题是关于我发现的同一问题的另一个特定解决方案,因为我想了解另一个解决方案背后的逻辑 我遇到了以下循环数组旋转问题(源代码下方): 给出了一个由N个整数组成的数组A。数组的旋转意味着每个元素右移一个索引,数组的最后一个元素移到第一位。例如,数组A=[3,8,9,7,6]的旋转为[6,3,8,9,7](元素向右移动一个索引,6移动到第一位)。 目标是将数组旋转K次;也就是说,
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
有人能解释一下这个特殊的解决方案是如何工作的吗?(作者未在其网站上解释)
对于大型A
和K
,我的解决方案性能不太好,其中K
,例如:
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
,我的代码的时间复杂度为O(N*K)
,其中N是数组的长度。
对于大的K
和小的N
(K>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>K
,其复杂性为O(N)
这一解决方案背后的逻辑是什么
感谢您的关注。让我先谈谈基本情况下的
K
,这种情况下的想法是将数组分成两部分A
和B
,A
是第一个N-K元素数组,B
是最后的K个元素。算法分别反转A
和B
,最后反转整个数组(两部分分别反转)。要管理使用K>N
的情况,请考虑每次反转数组N次,您将再次获得原始数组,这样我们就可以使用模块运算符找到拆分数组的位置(只反转真正有用的时间,避免无用的移位)
图形示例
一个图形化的分步示例有助于更好地理解该概念。注意
- 粗体线表示数组的拆分点(
,在本例中)李>K=3
- 红色数组表示输入和预期输出
A = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
K = 22
N = len(A)
请注意,我们想要在最终输出之前反转最后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) 这是一个非常简单的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
你真的在问为什么
a,b=b,a
交换a
和b
?不,我不知道。我了解它的功能,我不了解3reverse()
调用以及其中索引的相关计算。中有一些关于算法的信息。顺便问一下,说这个循环旋转算法的总体时间复杂度是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)
,因为问题的实例是N
,K
只是一个常数项。所以最坏的反转情况是0(N)
,重复3次就会得出O(N)
。如果您谈论有效的交换数量(纯交换,我们现在谈论的是O()
),在这种情况下,我们可以说算法进行了K/2+N-K/2+N/2=N
交换。注意,多次分析交换次数对于给出算法的效率和想法非常重要。好的。但是:最坏的反转情况是O(N),重复3次会再次给出O(N)
这不意味着O吗(N)
重复3次应导致O(3*N)
?这不适用于此算法,该算法具有N
交换数,其中N
也是列表/数组的元素数。此外,我想说反向函数的时间复杂度始终为O(N/2)
,因为元素是通过循环交换的,直到到达数组的中间…我只是尝试按照这个推理,以便更好地理解。您以错误的方式使用渐近符号。请查看属性