LeetCode 1425
最直接的方式应该是dfs+backtracking,遍历所有的可能,找到最大值。这个效率最差。换一个思路,考虑DP解法。
对于i位置,有两种可能,一种是选择,一种是没有选中。假设dp【i】是i被选中之后【0,i】的最大sum,那么最后的结果就是,max(dp【i】 for i in range(0,n). 考虑最后的n-1位置,如果最后是要被选中的,最大结果是dp【n], 如果没有被选中,那么结果应该是在dp【0】到dp【n-2】中的一个。
那么dp【i】,因为允许有k个范围,选选了前面的值,那么应该是前面k个里面最大的值加上自己的值。max(dp【j】for j in rang(i-k,i,1)),当然还有一种情况就是前面的都没有选,那么就是0加上自己的值。
转为代码:
def constrainedSubsetSumTimeout(self, nums: List[int], k: int) -> int:
if k <1: raise Exception("invalid input: k")
nl = len(nums)
dp = [0]* nl
result = -sys.maxsize
for i in range(0, nl):
maxinPreK = 0
for j in range(max(0, i-k), i, 1):
maxinPreK = max(maxinPreK, dp[j])
dp[i] = nums[i] + maxinPreK
result = max(result, dp[i])
return result
这个效率是O(N*K),最后是超时了
那么考虑怎么优化这个K个结果求最大值的过程,这个其实是个K slide window求最大值的问题。
如果我们用个double end queue,每次加入新的结果的时候,如果排在自己之前的结果都是比自己小的,并且离当前位置更远,这些结果其实都不可能是最大的结果了,所以可以全部去掉。那么我们其实就是维护了一个单调递减队列。
def constrainedSubsetSumMemory(self, nums: List[int], k: int) -> int:
if k <1: raise Exception("invalid input: k")
nl = len(nums)
dp = [0]* nl
dqueue = []
result = -sys.maxsize
for i in range(0, nl):
maxinPreK = 0
#去掉超出k range的值
while dqueue and dqueue[0][1] < i-k:
dqueue.pop(0)
if dqueue:
maxinPreK = dqueue[0][0]
dp[i] = nums[i] + max(maxinPreK,0)
#去掉队尾比自己小的那些值,因为最大值不可能是他们了
while dqueue and dp[i] > dqueue[-1][0]:
dqueue.pop()
dqueue.append((dp[i], i))
result = max(result, dp[i])
return result
写完这个,发现加入的都是dp【i】和i,那么我们不需要记录这个dp【i】,只要存下标就够了。
def constrainedSubsetSum(self, nums: List[int], k: int) -> int:
if k <1: raise Exception("invalid input: k")
nl = len(nums)
dp = [0]* nl
dqueue = []
result = -sys.maxsize
for i in range(0, nl):
maxinPreK = 0
while dqueue and dqueue[0] < i-k:
dqueue.pop(0)
if dqueue:
maxinPreK = dp[dqueue[0]]
dp[i] = nums[i] + max(maxinPreK,0)
while dqueue and dp[i] > dp[dqueue[-1]]:
dqueue.pop()
dqueue.append(i)
result = max(result, dp[i])
return result