leetcode 321拼接最大数

本文详细解析了LeetCode的一道难题,涉及如何使用单调栈来确保组合的最大数字。作者首先纠正了对题意的误解,然后介绍了单调栈的概念和在该问题中的应用。通过举例和代码解释,阐述了单调栈如何找到数组中的最大数字组合,同时保持数字的相对顺序。最后,展示了实现算法的代码,并分析了关键函数的作用。
摘要由CSDN通过智能技术生成

前几天遇上了一块硬骨头,看了题目半天。根本不知道怎么写,去看了题解又死活想不通为什么要用单调栈,或者说单调栈凭什么能保证出来的数字是最大的?原题链接https://leetcode-cn.com/problems/create-maximum-number/

直到昨天我才发现了原来我一直理解错题目了,本题的意思是最后返回的数组组成的数字最大,而不是数组的和最大。我之前一直理解成数组和最大,认为[6,7,3,2,4]最后形成的单调栈为[7,4]按照我原先的理解“和最大”,我认为[6,7]的和明显比[7,4]要大。直到我看懂了题目,那没事了。

首先说一下这个题目的解法,我们分为以下两步:
1 分别在两个数组里面找出最大的几项
2 把这两个数组进行合并
其实难的地方就在第一步,因为第二步的过程与归并排序的归并过程十分类似。那么第一步到底该如何处理?其实使用单调栈就可以完美地解决这个问题。

按照题目的意思,我们在数组中肯定要找出一个最大的数,后面跟着比他小的数,这是因为在长度相同的两个数中,以8开头的数是绝对比以7开头的数要大的,无论7后面的数字是怎样天花乱坠,如果两个数开头都是8的话,那么就继续比较后面的数字大小。但是你不能对这个数组进行排序,因为必须保持数字其在原数组中的相对顺序,那么为什么单调栈就能实现这个功能呢?

首先说明一下啥是单调栈,因为我在做这道题之前也没了解过这个功能特异的数据结构。其实他的底层实现就是一个普通的栈,只不过就是栈里面的数据是单调递增或者单调递减的。例如[3,5,7,8]和[6,3,1]都是一个单调栈。本道题中我们需要实现的单调栈是从栈顶到栈底单调递增的。我们拿实例1中的数据来说:nums2 = [9, 1, 2, 5, 8, 3]
1)栈空,对于9来说,栈是空的,我们就让其入栈。
2) 1<9,所以1入栈,此时栈元素是 9 ,1
3) 2>1,1出栈,2入栈,此时栈内元素是9,2
4) 5>2,2出栈,5入栈,此时栈内元素是9,5
5) 8>5,5出栈,8入栈,此时栈内元素是9,8
6) 3<8,3入栈,此时栈内元素是9,8,3
可以看出来,使用单调栈时,不但能保证出来的数是最大的,还能维持这些数的相对顺序。

我先将我AC的代码发出来

class Solution {
public:
    vector<int> maxNumber(vector<int>& nums1, vector<int>& nums2, int k) {
        int m = nums1.size(), n = nums2.size();
        vector<int> ret(k, 0);
        int begin= max(0,k-n), end = min(k,m);
        for (int i=begin;i<=end;i++){
            vector<int> stack1(dandiaostack(nums1,i));
            vector<int> stack2(dandiaostack(nums2,k-i));
            vector<int> cur(merge(stack1,stack2));
            if(compare(cur,0,ret,0)>0) 
                ret.swap(cur);   
        }
        return ret;
    }
    vector<int> dandiaostack(vector<int>& nums,int length){
        int drop=nums.size()-length;
        stack<int> s;
        for(int i=0;i<nums.size();i++){
            while(!s.empty()&&s.top()<nums[i]&&drop>0){
                s.pop();
                drop--;
            }
            if(s.size()<length)
                s.push(nums[i]);
            else
                drop--;
        }
        vector<int> ret;
        while(!s.empty()){
            ret.insert(ret.begin(),s.top());
            s.pop();
        }
        return ret;
    }
    int compare(vector<int>& nums1,int index1,vector<int>& nums2,int index2){
        while(index1<nums1.size()&&index2<nums2.size()){
            if(nums1[index1]!=nums2[index2])
                return nums1[index1]-nums2[index2];
            index1++;
            index2++;
        }
        return (nums1.size()-index1)-(nums2.size()-index2);
    }
    vector<int> merge(vector<int>& nums1,vector<int>& nums2){
        if(!nums1.size())
            return nums2;
        if(!nums2.size())
            return nums1;
        int a=0,b=0,i=0;
        int sum=nums1.size()+nums2.size();
        vector<int> ret(sum,0);
        while(i<sum){
            if(compare(nums1,a,nums2,b)>0)
                ret[i++]=nums1[a++];
            else
                ret[i++]=nums2[b++];
        }
        return ret;
    }
};

看起来好像代码很长,说实话,当我看别人代码这么零零散散一大堆的时候,我也懒得看。不过我相信我下面的讲解可以让这段代码变得容易理解。

这个代码分为四块,其中一个是main函数(并不是严格的main函数),另外三个一个作用是构造单调栈(就是我们提到的第一步),一个是归并的过程(就是我们提到的第二部),还有一个是compare辅助函数,无论是在归并过程或者是main函数中都让我们方便了很多。

首先我想说的是在main函数中如何能找到最大的值,我们可以使用枚举算法, 既然需要选出k个数字,那么我们可以依次枚举nums1中取0个数 取1个数 ……一直到取k个数,那么相对应地,nums2就依次取k,k-1……0个数。不过细心的同学可以发现,为什么我的main函数中左边界是max(0,k-n) 而右边界是min(k,m),这其实不难理解,因为如果nums1中只有3个数,而此时的k需要5个数,我们取值范围只能从0-3而不能到5;于此同时,如果nums2中只有3个数,k还是需要5个数,那么nums1至少需要取2个数,这个时候取值范围就变成了2-3。这样子做可以防止下标越界同时也算是一次剪枝。

其次关于我写的单调栈这个函数,这道题的单调栈和我上面说的普通单调栈还有一点区别,这是因为当我们需要从nums1数组中取5个数时,若nums1是[5,7,6,3,8,3,1],平常的结果应是 8,3,1。但是现在的情况我们需要取5个数,所以我设置了一个drop变量,目的就是算一下我们还能再扔掉几个数。就我的这个例子来看,drop=nums.size()-k=2,也就是说我们只能扔掉两个数,后面的话不管你愿不愿意,都必须组成这个特殊的单调栈。
1)栈空,5入栈,栈内元素为5
2)7>5, 5出栈,7入栈,栈内元素为7,此时drop为1
3)6<7,6入栈,栈内元素为 7 6
4)3<6,3入栈,栈内元素为 7 6 3
5)8>3,3出栈,栈内元素为7 6,此时drop为0,已经不能再出栈了
6)后面的元素全部加入,栈内元素为 7 6 8 3 1
相信此时再看我的单调栈函数已经不再困难了吧 :)

然后我再说说这个compare辅助函数,这个函数很简单,就是实现两个数组的比较,如果nums1的index下标的元素比nums2的index下标的第一个元素不相等,那么就可以直接返回nums1[index1]-nums2[index2];返回值大于0就是nums1[index1]大,否则就是nums2[index2]大。不相等时就依次比较下面的数,如果都不满足的话,就return (nums1.size()-index1)-(nums2.size()-index2);有两种情况会触发这句话
1)nums1或者nums2中的一个index已经越界了,那么就肯定取另一个
2)nums1和nums2前面的n个元素全相等,其中一个数组长度只有n,例如nums1[5,5,5,7]和nums2[5,5,5]依次比较时,我们就取长的,因为
当我们取完5的时候,nums2变成[7],此时7>5,这样子才是最优解,当然这个数字也可能小于5,不过我们这样做就可以保证一定是最优解。

最后就说这个归并过程,两个数组nums1[5,4,2]和nums2[6,3,1]
a和b都为0,也就是nums1和nums2开始的下标
1)6>5 ret数组加入6, b++
2) 5>3 ret数组为[6,5] a++
3) 4>3 ret数组为[6,5,4] a++
4) 3>2 ret数组为[6,5,4,3] b++
5)2>1 ret数组为[6,5,4,3,2,1] a++
6)nums1中的下标已经越界了,ret数组为[6,5,4,3,2,1] b++
这时候出来的数组就一定是最大的

其实我之前一直在思考为什么这两个比较的时候一定需要用到compare函数,既然两个数组都是单调的,例如 [5,3,2]和[5,2,1]我们无论从哪个数组里拿出5,下一次拿的值也一定是5,因为后面的数是一定小于等于前面的数。但是我想到了两个数组不单调! 因为这是一个特殊的单调栈,我前面已经说过了。就拿那个例子来说:7 6 8 3 1,他就不是一个单调递减的数组,例如它和[9,6,3,1]进行merge操作,那么在ret数组里为[9,7]的时候,两个数组的index上的元素都为6,就应该先取走后面的数比较大的那个,会形成[9,7,6,8],可以明显发现9768比9766要大的。

相信我说了这么多,应该就会觉得这道题已经不是毫无头绪了吧,我觉得写代码就应该亲手敲,纵使你是照猫画虎效果也是比你光看好多了!继续加油:)

  • 7
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值