前言
7.30 日晚上做这道题,花了3小时多,最后没做出来,想了一晚上为什么我的解法有错误,最终发现根本问题在于本题在逻辑上无解,只能进行枚举求解。
以下我将展示我在逻辑上是如何思考这一题的,又是怎么钻进死胡同里,直到最后才明白这是一个死循环。
这题同一批次的学习笔记在:
算法学习 (门徒计划)4-3 专项面试题解析 学习笔记
学习笔记里记录了成功的解法,而本文讨论这次失败的解法的问题所在。
LeetCode 321. 拼接最大数
链接:https://leetcode-cn.com/problems/create-maximum-number
给定长度分别为 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]
解题思路
阶段一:思考可用知识点
本题显而易见就是在获取数有限的情况下,拼一个尽可能大的数字,那么就相当于期望高位尽可能的大,对于这么一个期望值尽可能的大的需求则通常是用单调栈或者单调队列来做的,由于不存在滑动窗口,那么就用单调栈来做。
另外根据期望找到最大值这个需求,那么就要用单调递减栈。
因此: 用一个数组作为单调栈递减栈,同时也作为最终结果进行返回
对对应代码中的:
int [] stack = new int [k];
阶段二:讨论问题
简单讨论
相比起普通的单调递减栈,这次的入栈选择就变得不一样了,要求尽可能的从两个数列中进行依次取大的数字,但是当剩余的数字和栈内数字数量总和为k时,入栈操作应该取消旧数据出栈需求,最终将这个结果返回。
但是这种思考是有问题的,通常情况下,单调栈是对于一个震荡序列进行入栈来进行单调性维持的,但是如果入栈选择是两个序列,则在合并成一个序列时有多种情况,因此如果不能讨论清楚这多种情况,则不能这么入栈
显然是讨论不清楚的,因此一个单调栈的方案就被否决了。
进一步讨论
那么合并成一个序列再进行单调性维护不可行,如果先对两个各自的序列进行单调性维护,再将结果进行合并,可行吗?
可行,但不完全可行,当两个子序列长度之和能够大于需求的结果序列长度时,此时可以从这两个单调性序列中进行取尽可能的大的数字来合并成结果。
但是当两个子序列长度之和不足时情况就不同了
问题转换为:有没有办法进行子序列长度的补充,但是让补充的结果对于数值的影响最小呢。
新问题的讨论 (我走入死胡同的开始)
我认为有:
- 首先这个用于补足长度的字符一定得尽可能的补充在低位,因此我找到合并序列的最后一位试图在前进行数值的插入。
于是我遇到了一个新的问题,这些插入的数字从何而来,又能插入几个?
- 这些数字从被出栈的元素中来
- 出栈的元素区间有多大就能提供多少个
由此对应这一段代码:
stack[endIndex--]= nums1[q1[q1top]];
System.out.println(endIndex+"-e1--"+stack[endIndex+1]);
int size =0;
if(q1top>0)
size = q1[q1top]-q1[q1top-1]-1;
else
size = q1[q1top];
while(size>0&&needLen>0) {
stack[endIndex--] = nums1[s1.pop()];
size --;
needLen --;
}
q1top--;
完善设计(无法补完的设计)
既然已经知道了如何插入数字,并找到了数字的来源(当单调栈进行单调维护时,将出栈的元素放入一个备用栈中,从这个栈中出的元素在一个区间内都是符合单调性的)唯一的问题就是如何选择用来插入数值的区间。
这个需求咋一看可以实现,但是逻辑上不可能
这个区间的选择有4个优先级,优先级数字越大,优先级越低
(优先级是逐层比较的,先进行级别外的比较,再进行级别内的比较)
- 区间有或者无。当需要一个区间进行数值的插入时,优先选择区间长度大于0的区间(这很好理解,如果长度为0,那就不可用)
- 区间结尾小或者大。两个备选区间结尾如果一大一小,那么这两个区间一定是结尾大的排在前面(这很好理解,因为前面的数字可