【每日一题】旋转数组——生活也会旋转

日常刷题,保持脑子的灵活,应对不确定的生活


生活如同一个旋转的数组,我们每个人都是数组中的一个元素,随着时间的推移,我们在不断地旋转、变换位置。有时,我们处于数组的前端,阳光明媚,一切都顺风顺水;有时,我们却被旋转到了数组的末尾,面临着各种困难和挑战。


就像算法中的旋转数组问题,生活中的旋转也有多种解决方案。有些人选择借助外力,就像使用额外数组一样,寻求他人的帮助和支持,以便更快地适应新的位置。有些人则选择自我反转,通过不断调整心态和策略,在逆境中寻找机会,重新定位自己。还有些人则像环状替换一样,在生活的旋转中不断交换和尝试,直到找到最适合自己的位置。


今天,分享一道有趣的编程题目——旋转数组。

题目描述:给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

示例:

输入: nums = [1,2,3,4,5,6,7], k = 3输出: [5,6,7,1,2,3,4]解释:向右轮转 1 步: [7,1,2,3,4,5,6]向右轮转 2 步: [6,7,1,2,3,4,5]向右轮转 3 步: [5,6,7,1,2,3,4]

解法一:使用额外数组 思路:创建一个与原数组相同大小的新数组,将原数组中的每个元素放到新数组中的正确位置。最后,将新数组复制回原数组。

def rotate(nums, k):    n = len(nums)    k = k % n    result = [0] * n    for i in range(n):        result[(i + k) % n] = nums[i]    nums[:] = result

解释:

1.计算实际需要旋转的步数 k,对数组长度 n 取模,避免 k 大于 n 的情况。2.创建一个与原数组相同大小的新数组 result3.遍历原数组,将每个元素放到新数组中的正确位置:result[(i + k) % n] = nums[i]4.将新数组复制回原数组:nums[:] = result

让我们通过一个具体的例子来理解这个过程:

假设原数组为 [1, 2, 3, 4, 5]k = 2

1.计算实际需要旋转的步数:k = 2 % 5 = 22.创建一个新数组 result,初始值为 [0, 0, 0, 0, 0]3.遍历原数组:当 i = 0 时,result[(0 + 2) % 5] = nums[0],即 result[2] = 1当 i = 1 时,result[(1 + 2) % 5] = nums[1],即 result[3] = 2当 i = 2 时,result[(2 + 2) % 5] = nums[2],即 result[4] = 3当 i = 3 时,result[(3 + 2) % 5] = nums[3],即 result[0] = 4当 i = 4 时,result[(4 + 2) % 5] = nums[4],即 result[1] = 54.将新数组复制回原数组:nums = [4, 5, 1, 2, 3]

通过这个例子,我们可以看到使用额外数组的解法是如何逐步将元素放到正确位置的。这种解法的优点是思路简单,容易理解;缺点是需要额外的空间来存储新数组。

时间复杂度:O(n),其中 n 是数组的长度。空间复杂度:O(n),使用了一个额外的数组。

解法二:反转三次 思路:通过三次反转操作,实现数组的旋转。

def rotate(nums, k):    n = len(nums)    k = k % n    reverse(nums, 0, n - 1)    reverse(nums, 0, k - 1)    reverse(nums, k, n - 1)
def reverse(nums, start, end): while start < end: nums[start], nums[end] = nums[end], nums[start] start += 1 end -= 1

解释:

1.计算实际需要旋转的步数 k,对数组长度 n 取模,避免 k 大于 n 的情况。2.反转整个数组:reverse(nums, 0, n - 1)3.反转前 k 个元素:reverse(nums, 0, k - 1)4.反转剩余的元素:reverse(nums, k, n - 1)

让我们通过一个具体的例子来理解这个过程:

假设原数组为 [1, 2, 3, 4, 5]k = 2

1.计算实际需要旋转的步数:k = 2 % 5 = 22.反转整个数组:[5, 4, 3, 2, 1]3.反转前 k 个元素:[4, 5, 3, 2, 1]4.反转剩余的元素:[4, 5, 1, 2, 3]

通过这个例子,我们可以看到反转三次的解法是如何巧妙地利用反转操作来实现旋转的。这种解法的优点是原地操作,不需要额外的空间;缺点是需要理解反转的思想,相对于使用额外数组的解法稍微复杂一些。

时间复杂度:O(n),其中 n 是数组的长度。空间复杂度:O(1),使用常数额外空间。

解法三:环状替换 思路:通过环状替换的方式,原地旋转数组。

def rotate(nums, k):    n = len(nums)    k = k % n    count = 0    start = 0    while count < n:        current = start        prev = nums[start]        while True:            next_index = (current + k) % n            nums[next_index], prev = prev, nums[next_index]            current = next_index            count += 1            if start == current:                break        start += 1

解释:

1.计算实际需要旋转的步数 k,对数组长度 n 取模,避免 k 大于 n 的情况。2.从位置 0 开始,将当前位置的元素移动到它应该在的位置,然后继续移动被替换的元素,直到回到初始位置。3.移动到下一个位置,重复步骤 2,直到所有元素都被移动。

让我们通过一个具体的例子来理解这个过程:

假设原数组为 [1, 2, 3, 4, 5]k = 2

1.计算实际需要旋转的步数:k = 2 % 5 = 22.从位置 0 开始:当前位置为 0,prev = nums[0] = 1下一个位置为 (0 + 2) % 5 = 2,将 nums[2] 的值赋给 nums[0],即 nums[0] = 3prev = 1当前位置更新为 2,nums[2] = prev = 1下一个位置为 (2 + 2) % 5 = 4,将 nums[4] 的值赋给 nums[2],即 nums[2] = 5prev = 1当前位置更新为 4,nums[4] = prev = 1下一个位置为 (4 + 2) % 5 = 1,将 nums[1] 的值赋给 nums[4],即 nums[4] = 2prev = 1当前位置更新为 1,nums[1] = prev = 1下一个位置为 (1 + 2) % 5 = 3,将 nums[3] 的值赋给 nums[1],即 nums[1] = 4prev = 1当前位置更新为 3,nums[3] = prev = 1下一个位置为 (3 + 2) % 5 = 0,回到了初始位置,退出循环。3.移动到位置 1,重复步骤 2,直到所有元素都被移动。

通过这个例子,我们可以看到环状替换的解法是如何通过不断将元素移动到正确位置来实现旋转的。这种解法的优点是原地操作,不需要额外的空间;缺点是相对于前两种解法,理解起来稍微复杂一些。

时间复杂度:O(n),其中 n 是数组的长度。空间复杂度:O(1),使用常数额外空间。

总结:通过以上三种解法,我们可以看到旋转数组问题的不同思路和方案。每种解法都有其优势:

使用额外数组:思路简单,易于理解,但需要额外的空间。反转三次:原地操作,不需要额外空间,但需要理解反转的思想。环状替换:原地操作,不需要额外空间,但相对较难理解。

下表总结了三种解法的时间复杂度和空间复杂度:

解法 时间复杂度 空间复杂度
使用额外数组 O(n) O(n)
反转三次 O(n) O(1)
环状替换 O(n) O(1)

在实际应用中,我们可以根据具体情况选择适合的解法。如果对空间复杂度要求较高,可以选择反转三次或环状替换;如果对代码简洁性要求较高,可以选择使用额外数组。无论选择哪种解法,都要理解其中的思想和原理,这对提高编程能力和问题解决能力都有很大帮助。

在解题过程中,我们还可以总结一些常用的技巧和思路:

1.当涉及到循环旋转时,可以通过取模运算来处理索引,避免越界的问题。2.反转操作是一种常见的数组操作,可以用来解决多种问题,例如字符串反转、数组旋转等。3.环状替换的思想可以用来解决一些需要原地操作的问题,通过不断交换元素的位置来实现目标。


请使用浏览器的分享功能分享到微信等