Week7

问题描述

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.

Note: You should try to optimize your time and space complexity.

Example 1:

Input:
nums1 = [3, 4, 6, 5]
nums2 = [9, 1, 2, 5, 8, 3]
k = 5
Output:
[9, 8, 6, 5, 3]

Example 2:

Input:
nums1 = [6, 7]
nums2 = [6, 0, 4]
k = 5
Output:
[6, 7, 6, 0, 4]

Example 3:

Input:
nums1 = [3, 9]
nums2 = [8, 9]
k = 3
Output:
[9, 8, 9]

思路

题目的意思是给我们两个数组,以及一个k,要我们从这两个数组中找出 k 个元素,这 k 个元素要构成的最大的数字,并且要求数字之间的相对顺序要得到保持。

要选出最大的数字,很明显可以看出这是一个贪心算法。要使数字最大,我们每次从所给的两个数组中选择一个符合限制条件的最大的数。

首先如何挑出k个符合条件的数字。我们可以先看一个数组,比如num = [9, 1, 2, 5, 8, 3]。

num_maxstr[1] = [9] 当k为1时,我们只需要选择最大的数字即可

num_maxstr[2] = [9, 8] 从9向后查找,找到最大值8

num_maxstr[3] = [9, 8, 3] 从8向后查找,找到最大值3

num_maxstr[4] = [9, 5, 8, 3] 由于3后面没有数字了,我们必须向前找,找到最大值5

num_maxstr[5] = [9, 2, 5, 8, 3]

num_maxstr[6] = [9, 1, 2, 5, 8, 3]

可以用栈实现上述过程——每次遍历一整个数组,如果遍历到比栈中数大的数 A,则把栈中小于 A 的数都弹出,再把 A 压栈。要设置一个最大弹出的数量 to_throw,比如 num_maxstr[3] 的最大弹出数量为 num-3=3,弹出 3 个数就不再弹出数了,而是直接将数字放到栈里了。但用栈不利于之后数字的合并,所以可以用 vector 来代替stack,在后面将 num1 和 num2 合并时可以直接下标索引会方便很多。vector 的 pop_back() 可以相当于 stack 的 pop()。

num = [9, 1, 2, 5, 8, 3] k = 3 to_throw = 6 - 3 = 3

step1 [9] 将第一个数字放入vector

step2 [9, 1] 1比9小,所以可以直接把1加进去

step3 [9, 2] 2比1大,所以把1弹出,把2放入 to_throw = to_throw - 1 = 2

step4 [9, 5] 5比2大,所以把2弹出,把5放入 to_throw = to_throw -1 = 1

step5 [9, 8] 8比5大,所以把5弹出,把8放入 to_throw = to_throw - 1 = 0

step6 [9, 8, 3] 因为to_throw已经为0了,这个时候只需要把后面的数放入即可

for(j = 1; j <= nums1.size(); j++){
    int to_throw = nums1.size() - j;
    for(int i = 0; i < nums1.size(); i++){
        while(nums1_dp[j].size() && nums1_dp[j].back() < nums1[i] && to_throw){
        	nums1_dp[j].pop_back();
            to_throw--;
        }
        nums1_dp[j].push_back(nums1[i]);
    }
    
    nums1_dp[j].resize(j);
} 

有可能遍历完数组to_throw都没有变为0,比如[9, 8, 7, 6 , 5],这个时候如果 k=2 的话我们就只用取前面的两个数。可以用 vector 的 resize() 函数进行缩短。

nums1_dp[len].resize(len);  // 只取前面len个数字

我们可以用上述的方法分别找出两个数组 num1,num2 的最大 n 个数的集合(n 从 1 到 num1.size() 或 num2.size())。这个时候我们可以尝试组成 k 的可能(比如5 = 0 + 5, 1 + 4, 2 + 3, 3 + 2, 4 + 1, 0 + 5, 但要注意有些组合因为数组长度的限制是不可能出现的),然后找出最大的组合。

// len1最长的长度
int limit = (k >= nums1.size()) ? nums1.size() : k;

for(len1 = 0; len1 <= limit; len1++){
	if(k-len1 > nums2.size()){continue;}
	len2 = ((k - len1) >= nums2.size()) ? nums2.size() : (k-len1);
    // MERGE num1_maxstr[len1] AND num2_maxstr[len2]
    //....
}   

在合并的时候可以直接比较两个vector(直接用>, <),选出大的 vector,将它的第一个数字取出放在merge_result 里,然后将这个数字从 vector 中删除,循环直至两个 vector 的所有数字都被取出。

例如 ·nums1_dp[2]= [6, 5]nums2_dp[3] = [9, 8, 3] 合并

[6, 5] [9, 8, 3] -> [9]

[6, 5] [8, 3] -> [9, 8]

[6, 5] [3] -> [9, 8, 6]

[5] [3] -> [9, 8, 6, 5]

[ ] [3] -> [9, 8, 6, 5, 3]

while(nums1_dp[len1].size() && nums2_dp[len2].size()){
    // 如果nums2_dp[len2]的第一个数字大于等于nums1_dp[len1]的第一个数,则把nums2_dp[len2][0]放入merge
	if(nums1_dp[len1] <= nums2_dp[len2]){
    	merge.push_back(nums2_dp[len2][0]);
        nums2_dp[len2].erase(nums2_dp[len2].begin());   // 清除掉第一个数字
    }
    // 如果nums1_dp[len1]的第一个数字更大,则把nums1_dp[len1][0]放入merge
    else{
        merge.push_back(nums1_dp[len1][0]);
		nums1_dp[len1].erase(nums1_dp[len1].begin());
	}
}

但用 vector 的 erase() 除去第一个数效率其实很低,因为每次 erase 之后,vector都需要将后面的数字往前移。所以其实可以用两个指针,做和归并排序里面类似的归并操作。但要注意nums1[pointer1] == nums2[pointer2]的情况有点复杂,比如下面的输入

Input: 

[2, 5, 6, 4, 4, 0]

[7, 3, 8, 0, 6, 5, 7, 6, 2]

k = 15

[2, 5, 6, 4, 4, 0] [7, 3, 8, 0, 6, 5, 7, 6, 2] -> [7]

[2, 5, 6, 4, 4, 0] [3, 8, 0, 6, 5, 7, 6, 2] -> [7, 3]

[2, 5, 6, 4, 4, 0] [8, 0, 6, 5, 7, 6, 2] -> [7, 3, 8]

[2, 5, 6, 4, 4, 0] [0, 6, 5, 7, 6, 2] -> [7, 3, 8, 2]

[5, 6, 4, 4, 0] [0, 6, 5, 7, 6, 2] -> [7, 3, 8, 2, 5]

[6, 4, 4, 0] [0, 6, 5, 7, 6, 2] -> [7, 3, 8, 2, 5, 6]

[4, 4, 0] [0, 6, 5, 7, 6, 2] -> [7, 3, 8, 2, 5, 6, 4]

[4, 0] [0, 6, 5, 7, 6, 2] -> [7, 3, 8, 2, 5, 6, 4, 4]

[0] [0, 6, 5, 7, 6, 2]

这个时候出现了两个 0,这个时候我们需要比较两个数组的字典序,只有将字典序大的数组的0先放入才能得到最大的数(7, 3, 8, 2, 5, 6, 4, 4, 0, 6, 5, 7, 6, 2, 0),否则会得到(7, 3, 8, 2, 5, 6, 4, 4, 0, 0, 6, 5, 7, 6, 2),所以出现了相等的情况的话,需要向后查找(tmp1, tmp2 分别从 pointer1 和 pointer2 向后查找)直到

① 出现了num1[tmp1] != num2[tmp2] 的情况, 比较两者大小,把大的数所在的数列的pointer所指的数放入merge。

② 一个数组已经到尾了但 num1[tmp1] 还是等于 num2[tmp2],这时我们把长的数组的 pointer所指的数放入merge

③ 两个数组同时到尾但 num1[tmp1] 还是等于 num2[tmp2],随便选择一个数组的pointer所指数放入merge

	vector<int> merge;
	int pointer1 = 0, pointer2 = 0;
            
	int num1_size = nums1_dp[len1].size();
    int num2_size = nums2_dp[len2].size();
      
	while(pointer1 < num1_size && pointer2 < num2_size){
        bool flag = 0;
        
        // 如果出现两个数相等的情况
        if(nums1_dp[len1][pointer1] == nums2_dp[len2][pointer2]){
        	int tmp1 = pointer1;
            int tmp2 = pointer2;
            
            // 使用tmp1,tmp2向后查
            while(nums1_dp[len1][tmp1] == nums2_dp[len2][tmp2]){
                // num1查到结尾
				if(tmp1 == num1_size - 1 && tmp2 != num2_size - 1){
                	flag = 1;
                    merge.push_back(nums2_dp[len2][pointer2]);
                    pointer2++;
                    break;
                }
				// num2查到结尾
                else if(tmp2 == num2_size - 1 && tmp1 != num1_size - 1){
                	flag = 1;
                    merge.push_back(nums1_dp[len1][pointer1]);
                    pointer1++;
                    break;
              	}
                
                // 同时到结尾
                else if(tmp1 == num1_size - 1 && tmp2 == num2_size - 1){
					flag = 1;
                    merge.push_back(nums1_dp[len1][pointer1]);
                    pointer1++;
                }
                        
                tmp1++;
                tmp2++;  
            }
            
            if(flag == 0){
                // 查到num1后面的数比num2的大,所以把nums1_dp[len][pointer1]放入
                if(nums1_dp[len1][tmp1] > nums2_dp[len2][tmp2]){
                	merge.push_back(nums1_dp[len1][pointer1]);
                    pointer1++;
                }
                else{
                	merge.push_back(nums2_dp[len2][pointer2]);
                    pointer2++;
                }
            }
        }

        // num1第一个数大于num2第一个数,直接把num1第一个数放入
        else if(nums1_dp[len1][pointer1] > nums2_dp[len2][pointer2]){
        	merge.push_back(nums1_dp[len1][pointer1]);
            pointer1++;
        }
		// num2第一个数大于num1第一个数,直接把num2第一个数放入
		else if(nums2_dp[len2][pointer2] > nums1_dp[len1][pointer1]){
        	merge.push_back(nums2_dp[len2][pointer2]);
            pointer2++;
        }
	}
	
	// num1没放完
	while(pointer1 < num1_size){
    	merge.push_back(nums1_dp[len1][pointer1]);
        pointer1++;
    }

	// num2没放完
    while(pointer2 < num2_size){
    	merge.push_back(nums2_dp[len2][pointer2]);
        pointer2++;
    }

源代码

class Solution {
public:
    vector<int> maxNumber(vector<int>& nums1, vector<int>& nums2, int k)
    {
            vector<vector<int>> nums1_dp(nums1.size()+1);
            vector<vector<int>> nums2_dp(nums2.size()+1);

            // nums1
            int j = 1;
            for(j = 1; j <= nums1.size(); j++){
                int to_throw = nums1.size() - j;

                // 检查是否为空+是否栈顶值小于要加入的值
                for(int i = 0; i < nums1.size(); i++){
                    while(nums1_dp[j].size() && nums1_dp[j].back() < nums1[i] && to_throw){
                        nums1_dp[j].pop_back();
                        to_throw--;
                    }
                    nums1_dp[j].push_back(nums1[i]);
                }
                nums1_dp[j].resize(j);
            }


            // nums2
            for(j = 1; j <= nums2.size(); j++){
                int to_throw = nums2.size() - j;
                
                for(int i = 0; i < nums2.size(); i++){
                    while(nums2_dp[j].size() && nums2_dp[j].back() < nums2[i] && to_throw){
                        nums2_dp[j].pop_back();
                        to_throw--;
                    }
                    nums2_dp[j].push_back(nums2[i]);
                }

                nums2_dp[j].resize(j);

            } 

            // 现在我们得到了nums1_dp和nums2_dp
            // 接下来要遍历所有可能的情况把它们merge起来

            int len1 = 0;
            int len2 = 0;

            vector<int> max = vector<int>(k, 0);

            int limit = (k >= nums1.size())?nums1.size():k;
            for(len1 = 0; len1 <= limit; len1++){
                if(k-len1 > nums2.size()){continue;}
                len2 = ((k - len1) >= nums2.size()) ? nums2.size() : (k-len1);

                vector<int> merge;
                int pointer1 = 0, pointer2 = 0;

                int num1_size = nums1_dp[len1].size();
                int num2_size = nums2_dp[len2].size();

                while(pointer1 < num1_size && pointer2 < num2_size){
                    bool flag = 0;
                    if(nums1_dp[len1][pointer1] == nums2_dp[len2][pointer2]){
                        int tmp1 = pointer1;
                        int tmp2 = pointer2;
                     
                        while(nums1_dp[len1][tmp1] == nums2_dp[len2][tmp2]){

                            if(tmp1 == num1_size - 1 && tmp2 != num2_size - 1){
                                flag = 1;
                                merge.push_back(nums2_dp[len2][pointer2]);
                                pointer2++;
                                break;
                            }

                            else if(tmp2 == num2_size - 1 && tmp1 != num1_size - 1){
                                flag = 1;
                                merge.push_back(nums1_dp[len1][pointer1]);
                                pointer1++;
                                break;
                            }
                            else if(tmp1 == num1_size - 1 && tmp2 == num2_size - 1){
                                flag = 1;
                                merge.push_back(nums1_dp[len1][pointer1]);
                                pointer1++;
                            }

                            tmp1++;
                            tmp2++;
                        }
                        
                        if(flag == 0){
                            if(nums1_dp[len1][tmp1] > nums2_dp[len2][tmp2]){
                                merge.push_back(nums1_dp[len1][pointer1]);
                                pointer1++;
                            }
                            else{
                                merge.push_back(nums2_dp[len2][pointer2]);
                                pointer2++;
                            }
                        }
                    }

                    else if(nums1_dp[len1][pointer1] > nums2_dp[len2][pointer2]){
                        merge.push_back(nums1_dp[len1][pointer1]);
                        pointer1++;
                    }

                    else if(nums2_dp[len2][pointer2] > nums1_dp[len1][pointer1]){
                        merge.push_back(nums2_dp[len2][pointer2]);
                        pointer2++;
                    }
                }

                while(pointer1 < num1_size){
                    merge.push_back(nums1_dp[len1][pointer1]);
                    pointer1++;
                }

                while(pointer2 < num2_size){
                    merge.push_back(nums2_dp[len2][pointer2]);
                    pointer2++;
                }

                if(merge > max){
                    max = merge;
                }

            }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值