321. 拼接最大数
给定长度分别为 m 和 n 的两个数组,其元素由 0-9 构成,表示两个自然数各位上的数字。现在从这两个数组中选出 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]
解题思路:
自己只想出用「单调递减栈」,具体怎么实现实在没想出,最后看了官方解答~
当然,这里并不是严格的「单调递减栈」,而是使用「单调递减栈」的思想~
正文:
先思考简化问题,从一个数组 nums 中顺序取 k 位的最大值? 维护一个「单调递减栈」即可,思路参考 402:移掉K位数字。
从两个数组取 k 位,就维护两个栈,两个栈相加等于 k 位。令 nums1 长度为 m,nums2 长度为 n。从 m 取 k1 位,n 取 k2 位,有 k1 + k2 = k 且 k1,k2 >= 0,合并 k1,k2(类似归并排序的合并操作)。枚举所有的k1,k2,返回合并后的最大值。
一些细节:
- 如何确定 k1 的范围,因为 k2 = k - k1,只确定 k1 即可。k1 理论范围是:0 <= k1 <= k。左边界:当 k > n,即第二个数组不能取够 k 个数,那么 k1 只能从 k - n 开始,所以左边界 = max(0, k - n)。右边界:一共要取 k 个,第一个数组最多取 m 个,右边界 = min(k, m)
- 在合并数组时,不能像归并排序只比较当前索引的值,需要从当前索引向后比较整个数组。如比较 […6,7] 和 […6] ,当前索引都是 6,只比较当前索引会出现 […6,6,7] 的情况,小于正确值 […6,7,6]。
public int[] maxNumber(int[] nums1, int[] nums2, int k) {
int m = nums1.length, n = nums2.length;
int[] ans = new int[k];
int start = Math.max(0, k - n);
int end = Math.min(k, m);
for (int i = start; i <= end; i++) {
int[] arr1 = kLargeArray(nums1, i);
int[] arr2 = kLargeArray(nums2, k - i);
int[] newAns = merge(arr1, arr2);
if (compare(newAns, 0, ans, 0)) {
ans = newAns;
}
}
return ans;
}
// 数组顺序取 k 位,返回最大值
public int[] kLargeArray(int[] nums, int k) {
int len = nums.length;
// 数组实现单调递减栈
int[] stack = new int[k];
int idx = -1;
for (int i = 0; i < len; i++) {
while (idx >= 0 && nums[i] > stack[idx] && (len - i) > (k - 1 - idx)) {
idx--;
}
if (idx < k - 1) {
stack[++idx] = nums[i];
}
}
return stack;
}
// 合并数组,返回最大值(和归并排序合并操作类似)
public int[] merge(int[] A, int[] B) {
int lenA = A.length, lenB = B.length;
if (A.length == 0) {
return B;
}
if (B.length == 0) {
return A;
}
int[] ans = new int[lenA + lenB];
int idxA = 0, idxB = 0, idx = 0;
while (idxA < lenA && idxB < lenB) {
ans[idx++] = compare(A, idxA, B, idxB) ? A[idxA++] : B[idxB++];
}
while (idxA < lenA) {
ans[idx++] = A[idxA++];
}
while (idxB < lenB) {
ans[idx++] = B[idxB++];
}
return ans;
}
// 从指定索引比较两个数组较大者
public boolean compare(int[] A, int a, int[] B, int b) {
while (a < A.length && b < B.length) {
if (A[a] > B[b]) {
return true;
} else if(A[a] < B[b]) {
return false;
}
a++;
b++;
}
return a < A.length;
}