Problem overview
The method MaxNumber receives two int[] arrays, nums1 and nums2, whose items are digits from 0 to 9, and an int named k with 1 <= k <= m + n. It must pick k digits from the two arrays so that the number they form is as large as possible, and return those digits as an int[]. The digits taken from one array must keep their original relative order, but digits from the two arrays can be interleaved freely. Each array has up to 500 digits.
With nums1 = [3, 4, 6, 5], nums2 = [9, 1, 2, 5, 8, 3] and k = 5, the answer is [9, 8, 6, 5, 3]. With [6, 7], [6, 0, 4] and k = 5, every digit is used and the answer is [6, 7, 6, 0, 4]. With [3, 9], [8, 9] and k = 3, it is [9, 8, 9].
Approach: split, shrink each side, merge
An answer takes i digits from nums1 and k - i from nums2, for some i. Trying every i from 0 to k covers all answers. For one fixed split, the problem breaks into three smaller ones:
- Find the largest number of
idigits that can be kept fromnums1, in order. - Find the same for
k - idigits ofnums2. - Merge the two results into the largest possible number of
kdigits, each side keeping its order.
Then the best candidate over all splits is the answer. Two candidates always have k digits, so comparing them as numbers is the same as comparing them digit by digit from the left.
Largest subsequence of a given size. Read the digits left to right and keep them on a stack. When a digit is bigger than the top, dropping the top moves the bigger digit one place left, which makes the number larger, so pop the top. The pop is allowed only while enough digits remain to still fill the stack to size.
For [9, 1, 2, 5, 8, 3] and size 3: 9 is pushed, 1 is pushed, then 2 pops 1, 5 pops 2, 8 pops 5, and 3 is pushed on top of 8. The result is [9, 8, 3].
Merging. The next digit should come from the array whose remaining part is greater. Comparing only the two next digits is not enough when they are equal. For [6, 7] and [6, 0, 4], both start with 6. Taking the 6 from the second array first gives [6, 6, 7, 0, 4], while taking it from the first gives the larger [6, 7, 6, 0, 4]. Comparing everything that remains, [6, 7] against [6, 0, 4], shows that the first is greater, because 7 beats 0 at the first difference.
On the first example, the splits give the candidates [9, 2, 5, 8, 3] (0 digits from nums1), [9, 6, 5, 8, 3], [9, 8, 6, 5, 3], [9, 8, 4, 6, 5] and [9, 3, 4, 6, 5] (4 digits from nums1). The third is the largest.
Step by step
- Start with
bestas an emptyint[],new int[0]. Any candidate beats it. - Loop
ifrom 0 tokinclusive. It is the number of digits taken fromnums1. - Skip impossible splits. Require
i <= nums1.Lengthandk - i <= nums2.Length. - Shrink each side with
MaxSubsequence(nums1, i)intofirstandMaxSubsequence(nums2, k - i)intosecond. - Merge them into
candidatewithMerge(first, second). - Keep the larger. If
IsGreater(candidate, 0, best, 0), replacebestwithcandidate. The two zeros mean that both comparisons start at index 0. - Return
bestafter the loop. - Declare
MaxSubsequence(int[] nums, int size)as aprivatehelper that returns anint[]. - Create the stack: an
int[]calledstackof lengthsize, andcount, how many digits it holds. The top isstack[count - 1]. - Loop over the indices of
nums. The index tells how many digits are still to come. - Pop while it helps. A
whileloop runs as long ascount > 0, the top is smaller thannums[i], andcount + nums.Length - i > size. The last condition says the digits not yet read, including this one, plus the digits that stay on the stack after the pop, still makesize. - Pop by decreasing
count. The old digit stays in the array until the next push overwrites it. - Push if there is room. If
count < size, storenums[i]atstack[count]and increasecount. A digit that finds the stack full is dropped. - Return
stack. - Declare
Merge(int[] a, int[] b), anotherprivatehelper returningint[], and createresultwith lengtha.Length + b.Length. - Keep two indices,
iintoaandjintob, both 0. - Loop while either array has digits left:
i < a.Length || j < b.Length. Each turn places exactly one digit. - Choose by the remainders. If
IsGreater(a, i, b, j), storea[i]atresult[i + j]and advancei. Otherwise storeb[j]atresult[i + j]and advancej. Exactlyi + jdigits are placed so far, so the next goes at indexi + j. - Return
result. - Declare
IsGreater(int[] a, int i, int[] b, int j)returningbool. It tells whetherafrom indexiis greater thanbfrom indexj. - Skip equal digits: a
whileloop advances both indices while both arrays have digits anda[i] == b[j]. - Decide at the first difference. Return whether
bran out, orastill has a digit that is bigger thanb[j].
The solution in C#
class Solution {
public int[] MaxNumber(int[] nums1, int[] nums2, int k) {
int[] best = new int[0];
for (int i = 0; i <= k; i++) {
if (i <= nums1.Length && k - i <= nums2.Length) {
int[] first = MaxSubsequence(nums1, i);
int[] second = MaxSubsequence(nums2, k - i);
int[] candidate = Merge(first, second);
if (IsGreater(candidate, 0, best, 0)) {
best = candidate;
}
}
}
return best;
}
private int[] MaxSubsequence(int[] nums, int size) {
int[] stack = new int[size];
int count = 0;
for (int i = 0; i < nums.Length; i++) {
while (count > 0 && stack[count - 1] < nums[i] && count + nums.Length - i > size) {
count--;
}
if (count < size) {
stack[count] = nums[i];
count++;
}
}
return stack;
}
private int[] Merge(int[] a, int[] b) {
int[] result = new int[a.Length + b.Length];
int i = 0, j = 0;
while (i < a.Length || j < b.Length) {
if (IsGreater(a, i, b, j)) {
result[i + j] = a[i];
i++;
} else {
result[i + j] = b[j];
j++;
}
}
return result;
}
private bool IsGreater(int[] a, int i, int[] b, int j) {
while (i < a.Length && j < b.Length && a[i] == b[j]) {
i++;
j++;
}
return j == b.Length || (i < a.Length && a[i] > b[j]);
}
}
The C# details worth knowing:
- Arrays as stacks. The
int[]has a fixed length, socountmarks the top. A pop only lowerscount, and the next push overwrites the old digit. The returned array always has exactlysizedigits: the pop condition keeps enough digits available to fill it. new int[0]is a valid empty array.MaxSubsequence(nums, 0)returns one, andMergethen returns a copy of the other side's digits.- Parameters are local copies.
IsGreateradvances its owniandjwithout affecting the caller's variables. Only the arrays are shared references, and the method never writes to them. - Short-circuit
||and&&. InIsGreater,j == b.Lengthis checked first, sob[j]is read only when it exists. In the second part,i < a.Lengthcomes beforea[i]. C# arrays are bounds-checked, so an out-of-range read would throwIndexOutOfRangeException. - Empty sides in
IsGreater. Withbempty,j == b.Lengthis true at once, so any candidate beats the initialbest. Withaused up andbnot, the result is false, soMergetakes fromb. - Equal arrays. When all compared digits are equal and
bruns out first or at the same time,IsGreaterreturns true. InMergethis means ties are broken towarda, which gives the same number either way.
Complexity
Let m and n be the lengths of the arrays.
Time. There are at most k + 1 splits. For each split, the two MaxSubsequence calls take O(m + n) in total: each digit is pushed at most once and popped at most once. Merge places k digits, and each IsGreater call may scan up to k digits when the remainders start with long equal runs, so one merge costs O(k²) in the worst case. Comparing the candidate with best costs O(k). The total is at most O(k × (m + n + k²)); it is much smaller when the digits differ early, since the comparisons then stop quickly.
Space: O(m + n). Each split allocates arrays of at most k digits, and best holds k more.
Edge cases and common mistakes
- Taking all digits. With
k = m + n, only the spliti = mis possible, and the problem reduces to the merge.[6, 7]and[6, 0, 4]gives[6, 7, 6, 0, 4]. - Splits that don't fit. For
[3, 9],[8, 9],k = 3, the candidates are[9, 8, 9](one digit fromnums1) and[9, 3, 9](two digits). The splits that would need three digits from one array are skipped because neither array has three. - Merging by the next digit only. For
[6, 7]and[6, 0, 4], that choice gives[6, 6, 7, 0, 4], which is smaller than[6, 7, 6, 0, 4]. - Popping without the remaining-digits check. The stack could end up shorter than
size, with zeros in its unused cells. Thecount + nums.Length - i > sizetest prevents that. - Zeros. The digits can be 0, so the answer may contain zeros, even at the front:
[0, 0]and[0]withk = 2gives[0, 0]. The result is an array of digits, so no leading zero is lost.
Testing
[3, 4, 6, 5],[9, 1, 2, 5, 8, 3],k = 5gives[9, 8, 6, 5, 3].[6, 7],[6, 0, 4],k = 5gives[6, 7, 6, 0, 4].[3, 9],[8, 9],k = 3gives[9, 8, 9].[1],[2],k = 1gives[2].[0, 0],[0],k = 2gives[0, 0].
