旋转数组寻找一个数_数组旋转的乐趣

旋转数组寻找一个数

资料来源: https : //giphy.com/

数组是最通用的数据结构之一。 数组是众多应用程序的基础,许多算法和数据结构都基于数组。

例如,由于数组数据结构为我们提供了对内容的随机访问 ,因此二进制搜索算法可以像它那样工作。 如果不考虑随机访问,就无法以与以前相同的时间复杂度执行著名的二进制搜索算法。

同样,我们拥有优先级队列数据结构,该结构再次基于完整的二叉树的数组表示形式,并且堆本身是众多应用程序的根。

这篇特殊的文章将处理与数组和字符串旋转有关的编程问题 这是一个非常有趣的问题领域,我们将基于数组旋转的概念来研究一系列不同的问题。

在开始实际问题之前,让我们首先看一下轮换的一些示例,并尝试回答以下有关轮换的问题:

  1. 什么是轮换?
  2. 包含N个元素的数组可以旋转多少次?
  3. 将数组旋转一个元素的时间复杂度是多少?
  4. 一些Python魔术来实现旋转。
什么是轮换?

下图将使您清楚地知道实际旋转的位置。

本质上,我们删除数组的第一个元素,然后将其放置在最后,然后将所有其余元素向左移动一步。 这是向左旋转的示例

同样,我们可以右旋转。

转数?

上面的图很清楚。 无论是向左旋转还是向右旋转,对于N个元素的数组,我们将有N个可能的旋转数组(包括self)。

旋转的时间复杂度?

从本质上讲,旋转数组时我们要做的是删除第一个元素(考虑到我们正在谈论的是向左旋转),然后将所有剩余元素向左移动一个位置,最后插入从第一个位置删除的元素数组的末尾。

由于每次我们都要进行一个旋转步骤时,无论是向左旋转还是向右旋转,其余的N-1个元素也都必须移位才能适应旋转,因此此操作的时间复杂度为O(N)。

Python魔术! 🧙‍

我们有很多方法可以解决这个问题。 我们只会展示进行左旋转的方法,并且可以类似的方式实现右旋转。 因此,这留给读者练习。

这是在给定数组上实现左旋转一个步骤的最基本方法。 我们只需将第一个元素放在最后,然后再将剩余的每个元素(即,起始索引1(用于基于0的数组索引))向左移动一次。

这种方法实际上最终会修改基础数组。 很多时候,我们只对数组的旋转版本感兴趣,或者对给定数组的所有旋转感兴趣,但是, 我们实际上并不想修改基础数组 。 您可以说给定的数组是只读数据结构。

如果您仔细注意,为了进行第N次左旋转,您将需要上一个旋转的结果。 因此,例如,如果提供给我们的原始数组为[1,2,3,4,5]而您按照上面列出的方法进行操作,则旋转一圈后,该数组将变为[2,3,4,5,1] ,然后我们可以对此再执行一次左旋转以获得[3,4,5,1,2]

通过遵循上述方法,实际上很难获得在N次左旋转后仍保留的数组。

让我们看一个有趣的方式来实现这一目标。

这里的窍门是运算。 如果您注意到旋转后的数组,它就像旋转后的数组的起点实际上就是原始数组中的某个索引i 。 该索引i可由数字N决定,数字N表示我们要在给定数组上执行的旋转次数,然后返回结果。

而且,您可以想象,N也可以很大。 它可以大于原始数组的长度。 但是,在某个时间点之后,旋转的数组开始重复自身 。 因此,对于大小为N的数组,在N-1次旋转之后,我们得到的下一个旋转数组是原始数组。

要了解为何此处进行模运算的原因,请查看下图,该图显示了一些旋转。

希望此图为您提供足够清晰的信息,说明为什么我们可以简单地进行模运算,并且可以在对其执行N次旋转后直接获得数组。

与其像之前的代码片段所示那样编写代码,我们还可以在Python中使用一个衬里。

现在我们有了旋转感,并且知道如何使用数组,我们终于可以围绕旋转数组的概念来研究一些有趣的问题。

旋转琴弦

让我们看看这个问题要求我们做什么。

它说给我们两个字符串A和B,它们的长度may可能相等,也可能不相等(您是否错过了?),并且如果字符串A的任何特定旋转都可以给我们字符串B,则我们将返回true。 。

在下图中,我们考虑了两个字符串A = abcdeB = cdeab ,经过两次旋转后,字符串A等于字符串B。因此,在这种情况下,我们返回True

如果两个字符串的长度不同,则简单的检查肯定会返回False 。 在这种情况下,无论我们进行什么旋转,弦都永远不会相等。

解决此问题的一种非常幼稚的方法是找出所有旋转,然后与字符串B进行字符串匹配以查看两个字符串是否相等。 我们将首先看一下解决方案,然后看它的复杂性分析,最后我们看一下它在leetcode平台上的其他解决方案中的表现如何

时间复杂度: O(N²),因为对于每次旋转,我们都会进行两个长度为N的字符串的字符串匹配,而这两个字符串的长度为O(N),并且总共旋转O(N)。

空间复杂度: O(N),因为我们每次旋转都会创建一个新列表。

在leetcode平台上,此解决方案的效果不佳。

事实证明,我们可以做得更好。

这里的想法是将字符串A附加到其自身,然后检查字符串B是否是此扩展字符串A + A的子字符串。

您可能会问我们为什么要这样做?

好吧,事实证明,如果我们将给定的数组/字符串附加到其自身,则结果数组或字符串将覆盖原始数组的所有旋转。 让我们看一下下面的图,以了解此级联操作如何有效地产生所有可能的旋转。 我们将在下面的此图中考虑的字符串是abcde ,因此将其与自身连接起来后,我们得到abcdeabcde

该图显示了“ abcdeabcde”覆盖的字符串“ abcde”的所有可能的旋转

现在,如果字符串A或A的任何旋转实际上等于字符串B,则字符串B将是此扩展字符串2A的子字符串。

时间复杂度: O(N),因为我们要做的只是在大小为N的字符串与另一个大小为2N的字符串之间进行字符串匹配。

空间复杂度: O(N),因为我们必须创建一个大小为2N的新字符串,以容纳此放大的字符串A。

该算法比以前的算法快得多,实现起来也要短得多。 这是Python🙈中的一个衬板。

我想那已经足够快了!

让我们继续研究另一个有趣的问题,它看起来很简单,但是在获得完美的解决方案之前需要考虑很多注意事项。

旋转排序数组中的最小值

在继续之前,我要感谢Divya Godayal贡献了本文的这一部分。 干杯!

所以这个问题只是要求我们在数组中找到一个元素

  • 排序
  • 旋转,显然
  • 不包含任何重复的元素。

解决此问题的一种非常粗鲁的方法是搜索整个数组并找到最小元素。 这种方法只会忽略给定数组已排序的事实,这是解决此问题的幼稚方法。 因此,首先让我们看一个基于线性搜索的简单解决方案。

时间复杂度:如果给定数组中有N个元素,则为O(N)。

空间复杂度: O(1)

这实际上很有趣。 O(N)解决方案为我们提供了最佳的leetcode执行时间。 但是,事实证明,就渐进复杂性而言,我们可以做得更好。

给定数组已排序的面Kong本身就是一个巨大的提示。 由于数组已排序并且我们要在数组中查找元素,因此可以使用二进制搜索范例。

但是,阵列被旋转。 因此,简单地应用二进制搜索在这里行不通。

在这个问题中,我们将本质上应用二进制搜索的修改版本,其中决定搜索方向的condition将不同于标准二进制搜索。

在标准的二进制搜索算法中,我们执行以下操作

1. while left <= right
2. mid = (left + right) / 2
3. if element == middle element:
4. return mid
5. elif element < middle element:
6. move to the left i.e. [left, mid - 1]
7. else:
8. move to the right i.e. [mid + 1, right].

由于给定的数组已排序,因此我们可以肯定地应用二进制搜索算法来搜索元素。 唯一的是,元素已经旋转,这是我们必须考虑的问题。

我们如何首先检查数组是否旋转?

如果数组未旋转并且数组按升序排序,则

last_element > first_element

在上面的示例7 > 2 。 这意味着该数组没有任何旋转。 在这种情况下,我们可以简单地返回数组的第一个元素,因为这将是最小元素。

但是,如果实际上旋转了阵列,则阵列中某处会发生心跳形成。 让我们看看心跳形成的含义。

如果查看上面数组的元素,它们按预期的顺序递增(因为数组按升序排序)。 但是,在元素7之后,突然下降,然后值又开始增加。 这是我们正在谈论的心跳结构。

在上面给出的数组3 < 4 。 因此,阵列被旋转。 发生这种情况是因为数组最初是[2, 3 ,4 ,5 ,6 ,7] 。 但是,旋转之后,较小的元素[2,3]位于后面。 即[4,5,6,7,2,2,3 2, 3] 。 因此,旋转数组中的第一个元素[4]变得大于最后一个元素。

从该问题可以明显看出心跳结构,这意味着阵列中存在一个您会注意到变化的点。 这一点将有助于我们解决这个问题。 我们称此为Inflection Point

对于解决这个问题至关重要的拐点的重要属性是:

*拐点左侧的所有元素>数组的第一个元素。
*拐点右边的所有元素<数组的第一个元素。

现在让我们在看一下实现之前,先看一下解决这个问题的算法。

  1. 找到数组的mid元素。
  2. 如果mid element > first element of array这意味着我们需要在mid的右边寻找拐点。
  3. 如果mid element < first element of array那么我们需要在mid的左边寻找拐点。

当满足两个条件之一时,当我们找到拐点时,我们将停止搜索:

nums[mid] > nums[mid + 1]因此, mid+1最小。

nums[mid - 1] > nums[mid]因此, mid是最小的。

时间复杂度: O(logN),因为我们在这里所做的只是依靠我们的好朋友二进制搜索,因此可以利用原始数组的排序性质。

空间复杂度: O(1)

现在做不了更好的了,可以吗? 😉

这个问题的最大收获是数组中没有重复的元素。 如果数组中有重复的元素怎么办? 我们是否仍可以采用类似的方法来解决问题?

这个问题的答案是肯定的。 我们上面讨论的相同概念也适用于问题的此修改版本。 但是,时间复杂度不再保证为O(logN)。 看下面的例子。

下面提到的两种情况更容易解决,因为中间元素与第一个元素和最后一个元素不同,并且可以帮助指导二进制搜索(尽管您会在二进制搜索的中间将4用作中间点)。

关键是,由于此处允许使用重复的元素,因此可能出现以下情况:

leftmost element == middle element == rightmost element

当发生这种情况时,我们如何决定需要朝哪个方向发展。 我们不可能知道二进制搜索算法可以忽略的方向。 因此,我们将不得不尝试将它们都考虑为可能的候选者并对其进行处理,并且如果我们数组中的所有元素都相同,即[4,4,4,4,4,4,4,4]那么我们将最终最终一个接一个地处理每个元素。

因此,我们heavy不休地得出结论:在这个问题上,没有办法获得有保证的O(logN)复杂度算法。 上面我们看到的二进制搜索算法的修改版本的最坏情况时间复杂度为O(N)。

让我们继续本文的最后一个问题,它将成为一个轰动一时的问题。 相信我!

有序排队

在获得解决方案之前,让我们先来看一些可能的字符串旋转。 我们将考虑的字符串是baaca和K = 3,这意味着我们可以选择前三个字符中的任何一个,然后将其从其位置删除,将其添加到末尾,最后将所有字符向左移一个位置以容纳该字符。最后的新元素。

假设该字符串具有以下字符: a[0], a[1], a[2] … a[n-1] ,我们想将某些位置i(i> = 0 && i <n_1)与位置i + 1,或交换a [i]和a [i + 1]。 我们声称可以通过使用字符串上的旋转为字符串中的任何两个相邻元素实现此目的。 例如:-假设字符串由5个字符组成,我们想交换a[2] and a[3] ,这就是我们如何通过旋转数组来实现这一点。

a[0], a[1], a[2], a[3] , a[4], a[5]     ROTATE around first element
a[1], a[2], a[3] , a[4], a[5], a[0]     ROTATE around first element
 a[2], a[3] , a[4], a[5], a[0], a[1]     ROTATE around second  element
 a[2],  a[4], a[5], a[0], a[1], a[3]     ROTATE around first  element
a[5], a[0], a[1], a[3], a[2],  a[4]     ROTATE around first element
a[0], a[1], a[3], a[2],  a[4], a[5]     ROTATE around first element

您可以尝试使用这种想法,但是从本质上讲,我们可以通过以上述方式执行多次旋转来交换给定字符串中的任何两个相邻元素。

因为我们可以交换任何两个元素,所以我们可以执行气泡排序!

冒泡排序算法本质上涉及为了使上/下元素冒泡到其在阵列中的相应位置的目的而在相邻元素之间进行比较

因此,我们实现了字符a [2]和a [3]的交换,而不会干扰其他字符的排序(类似地,可以对任何一对相邻索引执行此操作)。

因此,如果问题中的K> 1,我们基本上可以通过使用旋转来执行冒泡排序算法,并且最终,我们将得到的最小词典序字符串将是以升序排序的原始字符串。

那么当K = 1时呢?

在这种情况下,我们在选择哪个元素移到数组的后面时没有太多的自由。 在这种情况下,我们必须查看原始字符串的所有可能的旋转,然后按字典顺序返回最小的旋转。

如果您没记错的话,大小为N的字符串的旋转数为N。因此,当K = 1时,我们必须查看数组的所有旋转数(记住本文中讨论的mod方法或concat方法,得到所有旋转?),然后按字典顺序获得最小的旋转。

让我们看一下实现,尽管它很小。

时间复杂度: O(NlogN),因为我们正在对K> 1的字符串进行排序

空间复杂度: O(N),因为如果K = 1,则我们创建S + S,即O(N)空间分配。

本文就是这样。 希望您度过了一个愉快的时光,学习数组中的循环,并且希望您能够掌握我们在这里讨论的所有概念。

所有冰雹编码…

翻译自: https://hackernoon.com/fun-with-array-rotations-add4a335d79a

旋转数组寻找一个数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值