321. Create Maximum Number
Problem
You are given two integer arrays nums1 and nums2 of lengths m and n respectively. nums1 and nums2 represent the digits of two numbers. You are also given an integer k.
Create the maximum number of length k <= m + n from digits of the two numbers. The relative order of the digits from the same array must be preserved.
Return an array of the k digits representing the answer.
https://leetcode.com/problems/create-maximum-number/
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]
Constraints:
m == nums1.lengthn == nums2.length1 <= m, n <= 5000 <= nums1[i], nums2[i] <= 91 <= k <= m + nnums1andnums2do not have leading zeros.
Test Cases
class Solution:
def maxNumber(self, nums1: List[int], nums2: List[int], k: int) -> List[int]:import pytest
from solution import Solution
@pytest.mark.parametrize('nums1, nums2, k, expected', [
([3,4,6,5], [9,1,2,5,8,3], 5, [9,8,6,5,3]),
([6,7], [6,0,4], 5, [6,7,6,0,4]),
([3,9], [8,9], 3, [9,8,9]),
([6,7,5], [4,8,1], 3, [8,7,5]),
([8, 9], [3, 9], 3, [9, 8, 9]),
([7,6,1,9,3,2,3,1,1], [4,0,9,9,0,5,5,4,7], 9, [9,9,9,7,3,2,3,1,1]),
([2,5,6,4,4,0], [7,3,8,0,6,5,7,6,2], 15, [7,3,8,2,5,6,4,4,0,6,5,7,6,2,0]),
])
@pytest.mark.parametrize('sol', [Solution()])
def test_solution(sol, nums1, nums2, k, expected):
assert sol.maxNumber(nums1, nums2, k) == expected
Thoughts
将 k 分成 k1 和 k2(k = k1 + k2),分别从 nums1 中 取 k1 个元素、从 nums2 中取 k2 个元素,合并成长度为 k 的数组。
对于特定的 k1 和 k2,从 nums1 和 nums2 中取的时候,应该取最大的子序列出来(稍后看如何取出最大的)。合并的时候也合并成最大的,就可以得到 k1 和 k2 对应的最大的结果。遍历所有可能的 k1 和 k2,取结果最大的那个。
遍历
显然 k1 的下限为 max{0, k - n},上限为 min{m, k}。遍历所有可能的 k1 即可。
选取
在 1475. Final Prices With a Special Discount in a Shop 中提到用单调栈确定数组中每个元素左侧/右侧第一个比当前值小/大的元素,其中 正向扫描 时最终留在单调栈里的就是右侧没有比它更大值的元素集合(如果是在找右侧第一个比当前值大的元素)。代码示意:
def pick_max(nums: list[int]) -> list[int]:
stack = []
for num in nums:
while stack and stack[-1] < num:
stack.pop()
stack.append(num)
return stack比如 nums = [9, 1, 2, 5, 8, 3],最后栈里剩下的是 [9, 8, 3],这几个数在 nums 中右侧都找不到比它们更大的数。
如果 k = 3,[9, 8, 3] 刚好就是从 nums 中能找到的最大的子序列。如果 k < 3,那么在 [9, 8, 3] 中从左开始取 k 个即可。如果 k > 3,那么在单调栈弹出的时候记录弹出的个数,最多弹出 n - k 个,够了之后就不再弹出。可以有两种类似的对 pick_max 的改造方法:
def pick_max_sub(nums: list[int], k: int) -> list[int]:
remain_drop = len(nums) - k
stack = []
for num in nums:
while remain_drop and stack and stack[-1] < num:
stack.pop()
remain_drop -= 1
if len(stack) < k:
stack.append(num)
else:
remain_drop -= 1
return stackdef pick_max_sub(nums: list[int], k: int) -> list[int]:
remain_drop = len(nums) - k
stack = []
for num in nums:
while remain_drop and stack and stack[-1] < num:
stack.pop()
remain_drop -= 1
stack.append(num)
return stack[:k]可知数组 [9, 1, 2, 5, 8, 3] 对于不同的 k,返回的结果分别为:
1: [9]
2: [9, 8]
3: [9, 8, 3]
4: [9, 5, 8, 3]
5: [9, 2, 5, 8, 3]
6: [9, 1, 2, 5, 8, 3]对于指定的 k,算法的时间复杂度都是 O(n),空间复杂度 O(k)(或者 O(n))。
合并
从 nums1 中取出长度为 k1 的子序列 sub1,从 nums2 中取出长度为 k2 的子序列 sub2,需要合并二者使得结果最大。
这就跟归并类似,每次看 sub1 和 sub2 最左边的数字,哪个大就把哪个取走。
但是如果两边最左边数字相等,就不能随意选,还要继续比下一个数字。
对于长度分别为 m 和 n 的两个序列,合并的时间复杂度最环情况是 O(m * n),一般情况下是 O(m + n)。
整体
所以对 k1 从 max{0, k - n} 遍历到 min{m, k},最多 k + 1 次。每一次用 O(m + n) 时间取出两个子序列,用平均 O(k) 时间合并。
最终时间复杂度大约 O(k * (m + n)),空间复杂度 O(k)。
Code
def pick_max_sub(nums: list[int], k: int) -> list[int]:
remain_drop = len(nums) - k
stack = []
for num in nums:
while remain_drop and stack and stack[-1] < num:
stack.pop()
remain_drop -= 1
if len(stack) < k:
stack.append(num)
else:
remain_drop -= 1
return stack
def merge(nums1: list[int], nums2: list[int]) -> list[int]:
m = len(nums1)
n = len(nums2)
res = [0] * (m + n)
i = j = 0
def ge(i:int, j: int) -> bool:
# return nums1[i:] >= nums2[j:]
while i < m and j < n:
if nums1[i] > nums2[j]:
return True
elif nums1[i] < nums2[j]:
return False
else:
i += 1
j += 1
if i < m: return True
return False
while i < m or j < n:
if ge(i, j):
res[i+j] = nums1[i]
i += 1
else:
res[i+j] = nums2[j]
j += 1
return res
class Solution:
def maxNumber(self, nums1: list[int], nums2: list[int], k: int) -> list[int]:
min2 = lambda a, b: a if a <= b else b
max2 = lambda a, b: a if a >= b else b
m = len(nums1)
n = len(nums2)
largest = [0]
for k1 in range(max2(0, k - n), min2(m, k) + 1):
k2 = k - k1
sub1 = pick_max_sub(nums1, k1)
sub2 = pick_max_sub(nums2, k2)
res = merge(sub1, sub2)
if res > largest:
largest = res
return largest
选取和合并逻辑的测试代码:
import pytest
from solution import merge, pick_max_sub
@pytest.mark.parametrize('nums, expected', [
(
[3,4,6,5],
[
[6],
[6, 5],
[4, 6, 5],
[3, 4, 6, 5],
]
),
(
[9,1,2,5,8,3],
[
[9],
[9, 8],
[9, 8, 3],
[9, 5, 8, 3],
[9, 2, 5, 8, 3],
[9, 1, 2, 5, 8, 3],
]
),
(
[6,0,4],
[
[6],
[6, 4],
[6, 0, 4],
]
),
(
[4,0,9,9,0,5,5,4,7],
[
[9],
[9, 9],
[9, 9, 7],
[9, 9, 5, 7],
[9, 9, 5, 5, 7],
[9, 9, 5, 5, 4, 7],
[9, 9, 0, 5, 5, 4, 7],
[4, 9, 9, 0, 5, 5, 4, 7],
[4, 0, 9, 9, 0, 5, 5, 4, 7],
]
),
(
[2,5,6,4,4,0],
[
[6],
[6, 4],
[6, 4, 4],
[6, 4, 4, 0],
[5, 6, 4, 4, 0],
[2, 5, 6, 4, 4, 0],
]
),
(
[7,3,8,0,6,5,7,6,2],
[
[8],
[8, 7],
[8, 7, 6],
[8, 7, 6, 2],
[8, 6, 7, 6, 2],
[8, 6, 5, 7, 6, 2],
[8, 0, 6, 5, 7, 6, 2],
[7, 8, 0, 6, 5, 7, 6, 2],
[7, 3, 8, 0, 6, 5, 7, 6, 2],
]
),
])
def test_pick_max_sub(nums, expected):
result = [pick_max_sub(nums, k) for k in range(1, len(nums) + 1)]
assert result == expected
@pytest.mark.parametrize('nums1, nums2, expected', [
([6, 5], [9, 8, 3], [9, 8, 6, 5, 3]),
([6, 7], [6, 0, 4], [6, 7, 6, 0, 4]),
([9], [8, 9], [9, 8, 9]),
([4, 6, 5], [9, 5, 8, 3], [9, 5, 8, 4, 6, 5, 3]),
([6, 5], [], [6, 5]),
([], [9, 8, 3], [9, 8, 3]),
([2, 5, 6, 4, 4, 0], [7, 3, 8, 0, 6, 5, 7, 6, 2], [7, 3, 8, 2, 5, 6, 4, 4, 0, 6, 5, 7, 6, 2, 0]),
])
def test_merge(nums1, nums2, expected):
assert merge(nums1, nums2) == expected
ToDo
在考虑贪心法的可行性。每次直接从 nums1 和 nums2 中找出能够作为结果数组的下一个元素。显然每次都应该在可选的位置区间内找最大的数字。而使用一定的预处理,可以让每次操作的时间复杂度接近常数。
但如果两个数字可选的最大数字一样,就应该类似于上边合并那里,需要再比前边的数字,而不能随便选。这块还没想好可行性以及具体的处理逻辑。
评论需要 JavaScript。