leetcode : 321. Create Maximum Number : 有点难度的贪心法

321. Create Maximum Number

My Submissions
Total Accepted: 4587  Total Submissions: 22666  Difficulty: Hard

Given two arrays of length m and n with digits 0-9 representing two numbers. Create the maximum number of length k <= m + n from digits of the two. The relative order of the digits from the same array must be preserved. Return an array of the k digits. You should try to optimize your time and space complexity.

Example 1:

nums1 = [3, 4, 6, 5]
nums2 = [9, 1, 2, 5, 8, 3]
k = 5
return [9, 8, 6, 5, 3]

Example 2:

nums1 = [6, 7]
nums2 = [6, 0, 4]
k = 5
return [6, 7, 6, 0, 4]

Example 3:

nums1 = [3, 9]
nums2 = [8, 9]
k = 3
return [9, 8, 9]


开始看题目,没有想法,肯定不会是暴力搜索所有可能的拼接。后来想到:如果能分别从两个数组中选出候选子集,那么就应该能够根据类似归并的样子,将其拼接成最大数组。但是,如何从一个数组中选出候选子集。瞄了一眼提示才知道:长度为subK候选子集就是在子数组中最大的子数组。
1.推论:假设数组x和数组y分别选出长度是r的subX和长度是(k-r)的subY组成的数组Z最大,并且保证元素保持原有的顺序(元素o在subX中哪些元素前后,在Z中仍然保持这样的顺序)。那么subX和subY分别是X,Y的固定长度的最大数组
原因很简单:贪心法证明,假设 subX是拼接成最大数组Z的子数组,如果subX不是X的最大数组,那么存在subXX是X数组在固定长度的最大数组。subX和subY可以拼接成Z1,那么由subXX的数组元素来替换subX拼接成Z2,很明显Z2一定大于Z1。因此subX不是拼接成最大数组Z的子数组,与假设矛盾。因此, subX是X的最大数组。

既然知道subX是X中长度为r的最大数组,找出这个数组只要在元素的可能范围内选取最大元素,即可贪心法找出。

问题的下一步是:如何将两个最大数组合并成一个最大数组。开始的想法是:反正所有元素都要使用,使用类似归并法的方法,将大值尽可能排在前面即可。整体思路是对的,但是有个致命的bug:当遇到相等的比较,那么使用哪一个?一开始没有意识到这个问题,随便是用哪一个,结果wrong了好久。最后终于意识到,选择哪一个结果不一样的:
例如:
案例1:
5,3
5,8
案例2:
5,5,8
5,8
案例3:
5
5,8
案例4:
5,5,3,7,9
5,5,3,7,8
等考虑完成这些情况后,调试AC
public class Solution {
    public int[] submax(int[]numx,int subk){
		if(subk>numx.length||subk==0)
			return null;
		if(subk==numx.length)
			return numx;
		int[] subre=new int[subk];
		int left=0,right=numx.length-subk;
		for(int i=0;i<subk;i++){
			int max=Integer.MIN_VALUE;
			int maxIndex=-1;
			for(int j=left;j<=right;j++){
				if(max<numx[j]){
					max=numx[j];
					maxIndex=j;
				}
			}
			if(maxIndex!=-1){
				subre[i]=max;
				left=maxIndex+1;
				right++;
			}
		}
		return subre;
	}
	public int[] merge(int[] sub1,int[] sub2){
		if(sub1==null||sub2==null)
			return sub1!=null?sub1:sub2;
		int len1=sub1.length;
		int len2=sub2.length;
		int len=len1+len2;
		int[] mergeList=new int[len];
		int i=0,j=0;
		int index=0;
		while(i<len1&&j<len2){
			if(sub1[i]<sub2[j])
			    mergeList[index++]=sub2[j++];
			else {
				if(sub1[i]>sub2[j])
					mergeList[index++]=sub1[i++];
				else {
					int equalNum;
					boolean find=false;
					for(equalNum=1;i+equalNum<len1&&j+equalNum<len2;equalNum++){
						if(sub1[i+equalNum]!=sub2[j+equalNum]){
							find=true;break;
						}
					}
					if(!find&&(i+equalNum<len1||j+equalNum<len2))
						find=true;
					if(find){
						int whichone=0;
						if(i+equalNum>=len1||j+equalNum>=len2)
							whichone=i+equalNum<len1?1:2;
						else {
							whichone=sub1[i+equalNum]<sub2[j+equalNum]?2:1;
						}
						mergeList[index++]=whichone==1?sub1[i++]:sub2[j++];
					}
					else {
						mergeList[index++]=sub1[i++];
					}
				}
			}
		}
		while(i<len1)
			mergeList[index++]=sub1[i++];
		while(j<len2)
			mergeList[index++]=sub2[j++];
		return mergeList;
	}
	public int comparIntList(int[] first,int[] second){
		if(first.length!=second.length)
			return first.length-second.length;
		for(int i=0;i<first.length;i++)
			if(first[i]!=second[i]){
				return first[i]-second[i];
			}
		return 0;
	}
	public int[] maxNumber(int[] nums1, int[] nums2, int k) {
		int[] re=new int[k];
		int[] subre1,subre2;
		int len1=nums1.length;
		int len2=nums2.length;
		if(k>len1+len2)
			return null;
		int minNum1=k-len2<0?0:k-len2;
		int maxNum1=len1<k?len1:k;
		for(int i=minNum1;i<=maxNum1;i++){
			subre1=submax(nums1, i);
			subre2=submax(nums2, k-i);
			int[] temp=merge(subre1,subre2);
			if(i==minNum1||comparIntList(temp,re)>0){
				System.arraycopy(temp, 0, re, 0, k);
			}
		}
		return re;
	}
}

实际上,针对相等比较的情况,需要比较的是剩余数组的大小,然后对大的数组,压一个元素进结果数据。隔壁家的代码就是精炼
public int[] maxNumber(int[] nums1, int[] nums2, int k) {
    int n = nums1.length;
    int m = nums2.length;
    int[] ans = new int[k];
    for (int i = Math.max(0, k - m); i <= k && i <= n; ++i) {
        int[] candidate = merge(maxArray(nums1, i), maxArray(nums2, k - i), k);
        if (greater(candidate, 0, ans, 0)) ans = candidate;
    }
    return ans;
}
private int[] merge(int[] nums1, int[] nums2, int k) {
    int[] ans = new int[k];
    for (int i = 0, j = 0, r = 0; r < k; ++r)
        ans[r] = greater(nums1, i, nums2, j) ? nums1[i++] : nums2[j++];
    return ans;
}
public boolean 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]);
}
public int[] maxArray(int[] nums, int k) {
    int n = nums.length;
    int[] ans = new int[k];
    for (int i = 0, j = 0; i < n; ++i) {
        while (n - i + j > k && j > 0 && ans[j - 1] < nums[i]) j--;
        if (j < k) ans[j++] = nums[i];
    }
    return ans;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值