Article / 文章

LeetCode 第321题:拼接最大数

给定长度分别为 m 和 n 的两个数组,其元素由 0-9 构成,表示两个自然数各位上的数字。现在从这两个数组中选出 k (k <= m + n) 个数字拼接成一个新的数,要求从同一个数组中取出的数字保持其在原数组中的相对顺序。 求满足该条件的最大数。结果返回一个表示该最大数的长度为 k 的数组。

📖 文章摘要

本文详细解析LeetCode第321题“拼接最大数”,这是一道贪心算法和单调栈的问题。文章提供了基于单调栈和归并的解法,包含C#、Python、C++三种语言实现,配有详细的算法分析和性能对比。适合想要提升贪心算法和单调栈应用能力的程序员。

核心知识点: 贪心算法、单调栈、归并
难度等级: 困难
推荐人群: 具有一定算法基础,想要提升复杂问题解决能力的程序员

题目描述

给定长度分别为 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]

提示

  • m == nums1.length
  • n == nums2.length
  • 1 <= m, n <= 500
  • 0 <= nums1[i], nums2[i] <= 9
  • 1 <= k <= m + n

解题思路

方法:单调栈 + 归并

这道题可以分解为三个子问题:

  1. 从一个数组中选出指定长度的最大子序列
  2. 合并两个子序列得到最大数
  3. 枚举两个数组分别选择的数字个数

关键点:

  • 使用单调栈选择最大子序列
  • 设计特殊的归并规则
  • 正确处理相同数字的情况

具体步骤:

  1. 实现从单个数组选择i个数字组成最大子序列的函数
  2. 实现合并两个子序列得到最大数的函数
  3. 枚举从nums1中选择i个数字的所有可能情况
  4. 对每种情况,从nums2中选择k-i个数字
  5. 合并两个子序列,更新最大值

时间复杂度:O(k * (m + n + k)) 空间复杂度:O(k)

图解思路

单调栈过程分析表

步骤 栈内元素 当前数字 操作 剩余可删除数量
初始 [] 3 入栈 2
1 [3] 4 入栈 2
2 [3,4] 6 入栈 2
3 [3,4,6] 5 保持 2

归并过程分析表

数组1位置 数组2位置 选择数字 结果数组 说明
0 0 9 [9] 选择较大的9
0 1 8 [9,8] 选择8
1 1 6 [9,8,6] 选择6

代码实现

C# 实现

public class Solution {
    public int[] MaxNumber(int[] nums1, int[] nums2, int k) {
        int m = nums1.Length, n = nums2.Length;
        int[] maxSubsequence = new int[k];
        int start = Math.Max(0, k - n), end = Math.Min(k, m);
        
        for (int i = start; i <= end; i++) {
            if (k - i > n) continue;
            int[] candidate = Merge(
                MaxArray(nums1, i),
                MaxArray(nums2, k - i),
                k
            );
            if (Greater(candidate, 0, maxSubsequence, 0)) {
                Array.Copy(candidate, maxSubsequence, k);
            }
        }
        
        return maxSubsequence;
    }
    
    private int[] MaxArray(int[] nums, int k) {
        int[] stack = new int[k];
        int top = -1;
        int remain = nums.Length - k;
        
        foreach (int num in nums) {
            while (top >= 0 && stack[top] < num && remain > 0) {
                top--;
                remain--;
            }
            if (top < k - 1) {
                stack[++top] = num;
            } else {
                remain--;
            }
        }
        
        return stack;
    }
    
    private int[] Merge(int[] nums1, int[] nums2, int k) {
        int[] result = new int[k];
        int i = 0, j = 0, r = 0;
        
        while (r < k) {
            if (Greater(nums1, i, nums2, j)) {
                result[r++] = nums1[i++];
            } else {
                result[r++] = nums2[j++];
            }
        }
        
        return result;
    }
    
    private bool 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]);
    }
}

Python 实现

class Solution:
    def maxNumber(self, nums1: List[int], nums2: List[int], k: int) -> List[int]:
        def max_array(nums: List[int], k: int) -> List[int]:
            stack = []
            drop = len(nums) - k
            for num in nums:
                while drop and stack and stack[-1] < num:
                    stack.pop()
                    drop -= 1
                stack.append(num)
            return stack[:k]
        
        def merge(A: List[int], B: List[int]) -> List[int]:
            ans = []
            while A or B:
                bigger = A if A > B else B
                ans.append(bigger[0])
                bigger.pop(0)
            return ans
        
        m, n = len(nums1), len(nums2)
        max_result = [0] * k
        for i in range(max(0, k - n), min(k, m) + 1):
            if k - i > n:
                continue
            candidate = merge(max_array(nums1, i), max_array(nums2, k - i))
            max_result = max(max_result, candidate)
            
        return max_result

C++ 实现

class Solution {
public:
    vector<int> maxNumber(vector<int>& nums1, vector<int>& nums2, int k) {
        int m = nums1.size(), n = nums2.size();
        vector<int> maxSubsequence(k);
        int start = max(0, k - n), end = min(k, m);
        
        for (int i = start; i <= end; i++) {
            if (k - i > n) continue;
            vector<int> candidate = merge(
                maxArray(nums1, i),
                maxArray(nums2, k - i),
                k
            );
            if (greater(candidate, 0, maxSubsequence, 0)) {
                maxSubsequence = candidate;
            }
        }
        
        return maxSubsequence;
    }
    
private:
    vector<int> maxArray(vector<int>& nums, int k) {
        vector<int> stack(k);
        int top = -1;
        int remain = nums.size() - k;
        
        for (int num : nums) {
            while (top >= 0 && stack[top] < num && remain > 0) {
                top--;
                remain--;
            }
            if (top < k - 1) {
                stack[++top] = num;
            } else {
                remain--;
            }
        }
        
        return stack;
    }
    
    vector<int> merge(vector<int>& nums1, vector<int>& nums2, int k) {
        vector<int> result(k);
        int i = 0, j = 0, r = 0;
        
        while (r < k) {
            if (greater(nums1, i, nums2, j)) {
                result[r++] = nums1[i++];
            } else {
                result[r++] = nums2[j++];
            }
        }
        
        return result;
    }
    
    bool greater(vector<int>& nums1, int i, vector<int>& nums2, int j) {
        while (i < nums1.size() && j < nums2.size() && nums1[i] == nums2[j]) {
            i++;
            j++;
        }
        return j == nums2.size() || 
               (i < nums1.size() && nums1[i] > nums2[j]);
    }
};

执行结果

C# 实现

  • 执行用时:288 ms
  • 内存消耗:45.2 MB

Python 实现

  • 执行用时:892 ms
  • 内存消耗:15.8 MB

C++ 实现

  • 执行用时:36 ms
  • 内存消耗:24.6 MB

性能对比

语言 执行用时 内存消耗 特点
C# 288 ms 45.2 MB 实现简洁,性能适中
Python 892 ms 15.8 MB 代码最简洁,但性能较差
C++ 36 ms 24.6 MB 性能最优

代码亮点

  1. 🎯 巧妙使用单调栈选择最大子序列
  2. 💡 设计特殊的归并规则处理相等元素
  3. 🔍 通过枚举优化搜索空间
  4. 🎨 代码模块化,逻辑清晰

常见错误分析

  1. 🚫 没有正确处理数组长度限制
  2. 🚫 归并规则处理不当
  3. 🚫 相同元素的比较逻辑错误
  4. 🚫 没有考虑所有可能的分配方案

解法对比

解法 时间复杂度 空间复杂度 优点 缺点
单调栈+归并 O(k(m+n+k)) O(k) 效率较高 实现复杂
暴力枚举 O(C(m+n,k)) O(k) 思路简单 效率极低

相关题目


📖 系列导航

🔥 算法专题合集 - 查看完整合集

📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新至第321题。


💬 互动交流

感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。

如果这篇文章对你有帮助,请:

  • 👍 点个赞,让更多人看到这篇文章
  • 📁 收藏文章,方便后续查阅复习
  • 🔔 关注作者,获取更多高质量算法题解
  • 💭 评论区留言,分享你的解题思路或提出疑问

你的支持是我持续分享的动力!

💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!