不使用额外空间的合并两个排序数组

2024 年 8 月 29 日 | 阅读 17 分钟

在此问题中,我们将得到两个已排序的数组。我们的任务是合并给定的两个数组。但是,限制条件是我们必须在不使用任何额外空间的情况下合并它们。因此,在排序数组后,初始元素将位于第一个数组中(根据第一个数组的初始容量),其余元素位于第二个数组中。

让我们看一些例子来理解程序的输入和输出。

示例

输入: array1 = [11], array2 = [3, 4]

输出: array1 = [2], array2 = [4, 11]

输入: array1 = [1, 6, 9, 11, 16, 21], array2 = [1, 4, 7, 13]

输出: array1 = [1, 1, 4, 6, 7, 9], array2 = [11, 13, 16, 21]

如果我们允许使用额外空间,这个问题将会很简单。然而,在没有额外空间的情况下解决这个问题会使问题变得复杂。

蛮力方法将以 O(m * n) 的时间复杂度解决此问题。但是,有更有效的方法来解决它。

方法 - 1

我们将采用此方法来解决此问题

我们将从第二个数组 array2 的最后一个元素开始迭代,并搜索它是否存在于 array1 中。如果我们发现 array1 中有一个元素大于该元素,我们将把最后一个元素移动到第一个数组 array1,而将另一个元素移动到 array2。为了保持两个数组的排序,我们必须将元素插入到正确的位置。我们可以使用插入排序算法来搜索这个正确的位置。

以下是解决此问题的算法方法

  • 我们将从第二个数组的末尾开始迭代该数组的每个元素。
  • 对于第二个数组的每个元素,执行以下步骤
    • 我们将第一个数组的最后一个元素存储在一个变量中:last = array1[-1]
    • 从第一个数组的倒数第二个元素开始迭代,直到我们满足条件 array1[j] < array2[i]。
    • 现在我们将第一个数组的元素向后移动一个索引。
    • 我们将检查第一个数组的最后一个元素是否大于第二个数组的最后一个元素,然后我们将交换这些位置的元素。
  • 最后,我们将打印合并后的排序数组。
  • 在第二个循环中,单个数组的元素始终保持排序状态。

以下是实现上述方法的 Python 程序。

代码

输出

Initial First Array: 1 
6 9 11 
16 21  
Initial Second array: 1 
4 7 13  
Merged First Array: 1 
1 4 6 
7 9  
Merged First Array: 11 
13 16 21

时间复杂度:该程序需要 O(M * N) 来完成。时间复杂度是非线性的,因为我们在嵌套循环中迭代两个数组。

辅助空间:由于问题本身要求只使用常数额外空间来解决。因此,空间复杂度为 O(1)。

方法 - 2

现在我们将讨论另一种解决此问题的有效方法。

上述解决方案中存在一个重复的模式。当同时遍历两个已排序数组时,假设我们到达第二个已排序数组的第 j 个元素,它小于第一个已排序数组的第 i 个元素。那么在下一步,我们必须将第 j 个元素插入到第一个数组中,从第一个数组中移除某个第 k 个元素并将其插入到第二个数组中。我们将使用此模式来优化上述代码。

以下是我们为解决此问题将遵循的步骤

  • 我们将初始化三个变量:i = 0, j = 0, k = n - 1。这里 n 是第一个数组的大小。
  • 我们将迭代 array1 和 array2 的每个元素。但是,这次我们将使用每个数组的两个指针 i 和 j。
    • 现在,如果第一个数组的第 i 个元素小于第二个数组的第 j 个元素,则递增 i。
    • 否则,如果上述条件不满足,我们将用第二个数组的第 j 个元素替换第一个数组的第 k 个元素。将 i 增加 1 并将 k 减少 1。
  • 最后一步是在交换两个元素后对单个数组进行排序,以保持顺序。

以下是上述算法的 Python 程序

代码

输出

Initial First Array: 1 
6 9 11 
16 21  
Initial Second array: 2 
4 7 13  
Merged First Array: 1 
2 4 6 
7 9  
Merged Second Array: 11 
13 16 21  

时间复杂度:该程序需要 O((N + M) * log(N + M)) 来完成。这里 N 和 M 是两个数组的大小。我们只遍历列表一次 (N + M);但是,我们在每次迭代结束时对数组进行排序;因此,线性时间乘以 log(N + M)。

空间复杂度:由于程序不使用任何额外空间,因此为 O(1)。

方法 - 3

我们将使用此方法解决此问题

此方法将比较第一个数组的最后一个元素与第二个数组的第一个元素。现在,如果第一个数组的元素大于第二个数组的元素,我们将交换这两个元素。下一步是排序数组。第一个数组已经排序,但我们有第二个数组。我们必须重复这些步骤,直到第一个条件为真。在过程结束时,我们将得到两个已排序的数组。

我们将为这种方法遵循以下步骤。

  • 我们将初始化一个变量 i 为 0。
  • 我们将启动一个 while 循环,直到第一个数组的最后一个元素大于第二个数组的第一个元素。
    • 如果第一个数组的第 i 个元素大于第二个数组的第一个元素。
    • 我们将交换两个数组的第 i 个元素和第 0 个元素。
    • 然后我们将对第二个数组进行排序。
    • 下一步是将 i 增加 1。
  • 最后,我们将打印两个数组。

以下是使用上述方法的 Python 程序,用于解决此问题。

代码

输出

Initial First Array: 1 
6 9 11 
16 21  
Initial Second array: 1 
4 7 13  
Merged First Array: 1 
1 4 6 
7 9  
Merged First Array: 11 
13 16 21 

时间复杂度:该程序的时间复杂度为 O(M * (N * logN))。这里 M 和 N 是两个数组的大小。LogN 的时间复杂度是在每次迭代结束时对第二个数组进行排序。

辅助空间:空间复杂度与 O(1) 相同。

方法 - 4

以下是解决此问题的另一种方法。

  • 我们将以较短数组的长度为 N,以较大数组的长度为 M。
  • 我们将取较短的数组并找到应从中划分它的索引。
  • 我们必须在较短数组的中值所在的位置进行划分。(m1)
  • 现在我们将取第二个数组的前 M - m1 个元素。
  • 我们将比较边界元素。
    • 如果 m1 元素小于 f1 元素且 l2 元素小于 f2 元素,那么我们就找到了索引。
    • 否则,如果 m1 元素大于 f2 元素,那么我们需要在左子数组中搜索。
    • 否则,我们将在右子数组中搜索。
  • 下一步是将所有较小的元素存储在长度较短的数组中。
  • 直到索引 i,我们将较短数组的所有元素与较大数组的前 M - i 个元素进行交换。
  • 下一步是对两个数组进行排序。
  • 如果第一个数组的长度大于第二个数组的长度,那么所有较小的元素都将移到第一个数组中。
  • 然后我们将较大数组逆时针旋转 N 次。
  • 最后一步是交换两个数组的前 N 个元素。

以下是实现上述方法的 Python 代码。

代码

输出

Initial First Array: 1 
6 9 11 
16 21  
Initial Second array: 1 
4 7 13  
Merged First Array: 1 
1 4 6 
7 9  
Merged First Array: 11 
13 16 21  

时间复杂度:该程序需要 O(max(M * logM, N * logN)) 来完成。这里 log 的时间复杂度是在每次迭代后对两个数组进行排序。

空间复杂度:该程序将占用恒定的额外空间。因此,为 O(1)

方法 - 5

在此方法中,我们将使用插入排序合并两个已排序的数组。

在上述程序中,当我们向其他数组插入元素时,我们可以使用插入排序来插入元素,因为其余数组已经排序。这将进一步降低程序的时空复杂度。因此,在交换两个元素时,我们必须将交换的元素放置在适当的位置,以便数组仍然是排序的。这可以通过单次遍历来完成。

以下是我们为解决此问题将遵循的步骤

  • 我们将通过持续将第一个列表与第二个列表的第一个元素进行比较来对第一个列表进行排序。如果第二个列表的元素大于第一个列表的元素,那么我们将交换这两个元素。
  • 当我们交换一个元素时,我们将使用插入排序方法将交换的元素插入到第二个列表中。这样,元素将始终插入到正确的位置,并且在迭代结束时,第二个列表将始终是排序的。
  • 由于我们执行交换,如果我们发现第二个列表的元素大于第一个列表的元素,那么交换这两个元素将始终使第一个列表保持排序状态。但是,通过执行插入排序,我们可以维护第二个列表的顺序。

以下是使用上述方法解决此问题的 Python 程序。

代码

输出

Initial First Array: [1, 6, 9, 11, 16, 21]
Initial Second array: [1, 4, 7, 13]
Merged First Array: [1, 1, 4, 6, 7, 9]
Merged First Array: [11, 13, 16, 21]

时间复杂度:该程序具有非线性时间复杂度。时间复杂度为 O(M * N)。这里,M 和 N 是两个给定数组的长度。

空间复杂度:空间复杂度是所需的常数空间复杂度。因此,为 O(1)。

方法 - 6

在此方法中,我们将使用欧几里得除法引理来合并两个数组。该引理陈述为 (((Operation_on_array) % A) * A)

以下是我们为解决此问题需要遵循的步骤

  • 我们将使用与合并排序时相同的合并数组的方法。但是,这次我们将同时使用欧几里得除法引理和之前的方法。
  • 合并两个数组后,我们将用 A 除以它们。
  • 这里 A 是一个大于两个数组中所有元素的数字。
  • 在我们的问题中,数组元素没有上限。为了保持通用性,我们将 A 取为 10e7 + 1。

以下是使用欧几里得除法引理解决此问题的 Python 程序。

代码

输出

Initial First Array: 1 
6 9 11 
16 21  
Initial Second array: 1 
4 7 13  
Merged First Array: 1 
1 4 6 
7 9  
Merged First Array: 11 
13 16 21  

时间复杂度:由于我们使用线性循环,该程序的时空复杂度为 O(M + N)

空间复杂度: O(1)