给定长度分别为 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]
题解:
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @param {number} k
* @return {number[]}
*/
var maxNumber = function (nums1, nums2, k) {
// 求出每个数组中的最大序列
var pickmax = (nums, k) => {
let stack = [];
let drop = nums.length - k;
for (const num of nums) {
while (stack && stack[stack.length - 1] < num && drop) {
stack.pop();
drop--;
}
stack.push(num);
}
return stack.slice(0, k);
}
// 合并成一个最大序列
var merge = (n1, n2) => {
let res = []
// 永远比第一个
while (n1.length > 0 || n2.length > 0) {
bigger = n1 > n2 ? n1 : n2;
res.push(bigger[0]);
bigger.shift();
}
return res;
}
let res = [];
for (let i = 0; i <= k; i++) {
if (i <= nums1.length && k - i <= nums2.length) {
res.push(merge(pickmax(nums1, i), pickmax(nums2, k - i)));
}
}
// 把所有的最大序列存入数组进行比较,最大值即为答案
return res.sort((a, b) => {
if (a > b)
return -1;
else if (a < b)
return 1;
else
return 0;
})[0];
};
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @param {number} k
* @return {number[]}
*/
// 贪心算法
// 解题思路
// 枚举:从2个序列抽出k个数,[0, k]间取i
// 序列1抽i个,还需i <= 序列1长度
// 序列2抽k - i个,还需k - i <= 序列2长度
// 与[0, k]合并:i >= 0 && i >= 序列2长度 - k 且 i <= k && i <= 序列1长度
// >= 取最大,<= 取最小。分别作为循环i的初始位置和结束位置
// 合并序列1抽i个的子序列 和 序列2抽k - i个的子序列。结果集记录合并序列
// 循环i,如果后面合并序列大于结果集记录序列,更新结果集。返回结果集即可
var maxNumber = function (nums1, nums2, k) {
var r = Array(k).fill(0), end = Math.min(k, nums1.length)
for (var i = Math.max(0, k - nums2.length); i <= end; i++) {
r = max(r, merge(maxSubsequence(nums1, i), maxSubsequence(nums2, k - i)))
}
return r
};
// 3问:指定位数,求序列表示数最大的子序列
// 解题思路
// 表示数最大,即位数最高的数最大,即子序列的最左最大
// 序列中的数字本身没有排序,希望一次遍历时,把最大数,放到最左
// 递减单调栈的特点:遇数大,删栈中比它小的数。举例:[1, 2, 3, 0]排队进递减单调栈
// 1先进。2来了,删1。3来了,删2。0来了,不删。最终剩下[3, 0]
// 指定位数k,就是限定单调栈的深度 = k。还是上面的例子,指定位数3。余 4 - 3 = 1
// 1先进。2来了,余 1,删1,余 - 1。3来了,余 0,删2,凑不够3位,故不能删2
// 最终剩下[2, 3, 0]
var maxSubsequence = (nums, k) => {
var mStack = [], len = nums.length, top = -1, last = len - k
for (var i = 0; i < len; i++) {
while (top > -1 && nums[i] > mStack[top] && last > 0) {
//删1,预定1填空
top--, last--
}
// 栈满 且 数小,剩余可用位-1
top < k - 1 ? mStack[++top] = nums[i] : last--
}
return mStack
}
// 2问:合并2个序列,保持元素相对顺序,返回表示数最大的序列
// 解题思路
// 假设2个序列已经降序排列,与归并排序合并序列相同
// 只需新建结果集数组。双指针分别指两个序列开头,谁大放谁,指针右移
var merge = (nums1, nums2) => {
var p1 = p2 = 0, r = [], len1 = nums1.length, len2 = nums2.length
while (p1 < len1 || p2 < len2) {
if (p2 >= len2 || nums1[p1] > nums2[p2]) {
r.push(nums1[p1++])
// 指针指向相同,向后找不同位
} else if (nums1[p1] === nums2[p2]) {
var _p1 = p1, _p2 = p2
// 不越界,避免 undefined 循环
while (_p1 < len1 && nums1[++_p1] === nums2[++_p2]) { }
// p2越界放p1
r.push(_p2 >= len2 || nums1[_p1] > nums2[_p2] ? nums1[p1++] : nums2[p2++])
} else {
r.push(nums2[p2++])
}
}
return r
}
// 1问:2个序列长度相同,返回表示数更大的序列
// 解题思路
// 序列从左到右,表示从最高位到个位。举例:[1, 2, 3]表示123
// 序列表示的数大,即:最高位大。如果最高位相等,再比较下一位
// 下一位还相等,继续下一位,直到不相等,谁大返回谁。若都相等,返回谁都行
var max = (nums1, nums2, p1 = p2 = -1, len1 = nums1.length) => {
// 不越界,避免 undefined === undefined 循环
while (p1 < len1 && nums1[++p1] === nums2[++p2]) { }
// 找到不相等的位,该位谁大返回谁。相等返回谁都行
return nums2[p2] > nums1[p1] ? nums2 : nums1
}
来源:力扣(LeetCode)
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。