LeetCode 每日一题321. 拼接最大数

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;
}

执行结果:

在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值