一、题目描述
给你两个整数数组 nums1
和 nums2
,它们的长度分别为 m
和 n
。数组 nums1
和 nums2
分别代表两个数各位上的数字。同时你也会得到一个整数 k
。
请你利用这两个数组中的数字中创建一个长度为 k <= m + n
的最大数,在这个必须保留来自同一数组的数字的相对顺序。
返回代表答案的长度为 k
的数组。
示例 1:
输入:nums1 = [3,4,6,5], nums2 = [9,1,2,5,8,3], k = 5
输出:[9,8,6,5,3]
示例 2:
输入:nums1 = [6,7], nums2 = [6,0,4], k = 5
输出:[6,7,6,0,4]
示例 3:
输入:nums1 = [3,9], nums2 = [8,9], k = 3
输出:[9,8,9]
提示:
m == nums1.length
n == nums2.length
1 <= m, n <= 500
0 <= nums1[i], nums2[i] <= 9
1 <= k <= m + n
二、解题思路
- 创建一个辅助函数
monoInc
,该函数能够从一个数组中找出长度为k
的最大单调递增子序列。这个子序列是在保持原数组元素相对顺序的情况下得到的。 - 创建另一个辅助函数
merge
,该函数能够合并两个单调递增的数组,并返回一个长度为k
的最大单调递增数组。 - 在
maxNumber
函数中,遍历所有可能的从nums1
中取i
个元素,从nums2
中取k-i
个元素的情况(i
的取值范围是0
到k
,并且要满足i <= nums1.length
和k-i <= nums2.length
),然后使用merge
函数合并这些元素,并保留合并后的最大数组。
三、具体代码
java
class Solution {
public int[] maxNumber(int[] nums1, int[] nums2, int k) {
int m = nums1.length, n = nums2.length;
int[] ans = new int[k];
for (int i = Math.max(0, k - n); i <= k && i <= m; ++i) {
int[] candidate = merge(monoInc(nums1, i), monoInc(nums2, k - i), k);
if (greater(candidate, 0, ans, 0)) {
ans = candidate;
}
}
return ans;
}
private int[] monoInc(int[] nums, int k) {
int n = nums.length;
int[] res = new int[k];
for (int i = 0, j = 0; i < n; ++i) {
while (n - i > k - j && j > 0 && res[j - 1] < nums[i]) {
--j;
}
if (j < k) {
res[j++] = nums[i];
}
}
return res;
}
private int[] merge(int[] nums1, int[] nums2, int k) {
int[] res = new int[k];
for (int i = 0, j = 0, r = 0; r < k; ++r) {
res[r] = greater(nums1, i, nums2, j) ? nums1[i++] : nums2[j++];
}
return res;
}
private boolean greater(int[] nums1, int i, int[] nums2, int j) {
while (i < nums1.length && j < nums2.length && nums1[i] == nums2[j]) {
++i; ++j;
}
return j == nums2.length || (i < nums1.length && nums1[i] > nums2[j]);
}
}
在这段代码中:
monoInc
函数通过贪心算法,维护一个单调递增的序列。merge
函数比较两个单调递增序列的元素,并合并成一个新的单调递增序列。greater
函数比较两个序列的元素,如果第一个序列在某个位置上的元素更大,或者第二个序列已经结束,则返回true
。maxNumber
函数遍历所有可能的组合,并保留合并后的最大数组。
四、时间复杂度和空间复杂度
1. 时间复杂度
-
maxNumber
函数:- 外层循环的次数取决于
nums1
和nums2
的长度以及k
的值,最坏情况下,循环次数为k
。 - 在每次循环中,我们调用
monoInc
两次和merge
一次。 greater
函数在merge
中被调用k
次。
- 外层循环的次数取决于
-
monoInc
函数:- 对于每个数组
nums
,我们进行一次遍历,时间复杂度为O(n)
,其中n
是数组的长度。 - 在每次遍历中,可能需要执行一个内部循环来移除不满足条件的元素,最坏情况下,内部循环可能执行
k
次。 - 因此,
monoInc
函数的时间复杂度为O(nk)
。
- 对于每个数组
-
merge
函数:- 这个函数对两个数组合并,每个数组长度为
k
,因此时间复杂度为O(k)
。 - 在合并过程中,我们调用了
greater
函数,其时间复杂度在最坏情况下为O(k)
。
- 这个函数对两个数组合并,每个数组长度为
-
greater
函数:- 这个函数比较两个数组的元素,最坏情况下,它需要遍历整个数组,时间复杂度为
O(k)
。
- 这个函数比较两个数组的元素,最坏情况下,它需要遍历整个数组,时间复杂度为
综合上述分析,总的时间复杂度为: O(k * (O(mk) + O(nk) + O(k) + O(k)))
= O(k * (mk + nk + 2k))
= O(k^2 * (m + n + 2))
= O(k^2 * (m + n))
。
2. 空间复杂度
-
maxNumber
函数:- 需要常数空间来存储变量
m
、n
、ans
、i
等。 - 需要额外空间来存储每次
merge
函数的返回结果。
- 需要常数空间来存储变量
-
monoInc
函数:- 需要一个长度为
k
的数组res
来存储结果。
- 需要一个长度为
-
merge
函数:- 需要一个长度为
k
的数组res
来存储合并后的结果。
- 需要一个长度为
-
greater
函数:- 只需要常数空间。
综合上述分析,总的空间复杂度为:O(k)
,因为最大的空间占用是在monoInc
和merge
函数中创建的长度为k
的数组。
五、总结知识点
-
数组的操作:
- 数组的创建、初始化。
- 数组的索引访问和赋值。
-
辅助函数(Helper Functions):
- 使用辅助函数来分解复杂问题,提高代码的可读性和可维护性。
-
贪心算法:
monoInc
函数实现了贪心算法,通过维护一个单调递增的序列来找到最大子序列。
-
双指针技术:
- 在
monoInc
函数中,使用双指针来维护单调递增序列。 - 在
merge
函数中,使用双指针来合并两个单调递增序列。
- 在
-
递归逻辑:
greater
函数中包含递归逻辑,用于比较两个序列的大小。
-
循环与迭代:
- 使用
for
循环和while
循环来遍历数组,以及执行迭代操作。
- 使用
-
边界条件处理:
- 在
maxNumber
函数中,通过Math.max(0, k - n)
来确保索引不会超出数组的界限。
- 在
-
逻辑运算:
- 使用逻辑运算符(如
&&
、||
、!
)来控制代码流程。
- 使用逻辑运算符(如
-
条件判断:
- 使用
if
、else
语句进行条件判断。
- 使用
-
函数参数传递:
- 通过引用传递(数组)和值传递(基本数据类型)来传递函数参数。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。