和最長遞增子序列(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())