和最长递增子序列(LIS)思路很像。
对于位置 i ,其取值要满足单调递增的约束,最多有两种情况:
- arr1[i]
- 来自arr2
当位于i+1时,首先查看位置 i 的所有可能取值x,若arr1[i+1]大于x,则arr[i+1]满足单调递增且不会产生操作;若arr1[i+1]<=x,则arr[i+1]不满足单调递增。然后在arr2中寻找第一个大于x的值,该值满足单调递增,但会在x的基础上增加一次操作。
这样我们可以通过位置 i 来推出位置 i+1 。所以每个位置 i 需要一个字典dp,它的keys为当前位置的可能取值,values为对应取值的操作数。问题的解即是最后位置对应dp的values的最小值。
举个例子:
Input: arr1 = [1,5,3,6,7], arr2 = [1,3,4]
位置 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
dp | {1:0} | {5:0, 3:1} | {4:2} | {6:2} | {7:2} |
含义 | 位置0可取0,需要0次操作 | 位置1可取5,需要0次操作 位置1可取3,需要1次操作 | 位置2可取4,需要2次操作 | 位置3可取6,需要2次操作 | 位置4可取7,需要2次操作 |
class Solution:
def makeArrayIncreasing(self, arr1: List[int], arr2: List[int]) -> int:
dp = {-1:0}
arr2.sort()
for i in arr1:
tmp = collections.defaultdict(lambda: float('inf'))
for key in dp:
if i > key:
tmp[i] = min(tmp[i],dp[key])
loc = bisect.bisect_right(arr2,key)
if loc < len(arr2):
tmp[arr2[loc]] = min(tmp[arr2[loc]],dp[key]+1)
dp = tmp
if dp:
return min(dp.values())
return -1
还有一种将dp的key、value含义反转的写法(比上面快了4×),即key表示操作数,value表示取值。如果上一种写法看懂了的话,应该也能看懂这个。
class Solution:
def makeArrayIncreasing(self, arr1, arr2) -> int:
N = len(arr1)
arr2.sort()
dp = {0: arr1[0], 1: arr2[0]}
for i in range(1, N):
tmp = collections.defaultdict(lambda: float('inf'))
for k, v in dp.items():
if v < arr1[i]:
tmp[k] = min(tmp[k], arr1[i])
p = bisect.bisect_right(arr2, v)
if p < len(arr2):
tmp[k + 1] = min(tmp[k + 1], arr2[p])
dp = tmp
if len(dp) == 0:
return -1
return min(dp.keys())