Maximum Number of Books You Can Take

本文解析了一道结合单调栈与动态规划的题目,通过实例展示了如何运用dp数组存储每个shelter能取的最大书量,并利用单调栈找到递减范围以避免重复计算。核心思想是通过dp的节省资源特性,找出最优取书方案。
摘要由CSDN通过智能技术生成

Question:

请添加图片描述

思路

这个题, 挺nb 结合了monontonic stack和 dp的思想.
首先 我们要找一个subarray, 然后take最大数量的书. 看到subarray就会不自觉想到monotonic stack和prefixSum, 这个题和prefixSum 没关系. 我直接截图discussion里比较好的一个分析, 然后解释重点. 请添加图片描述

这一段分析详细解释了为什么要用dp,以及dp怎么save了recomputing resource.
其实是非常有道理的, 所以说我们有一个dp[], 每一个index i 存的都是从shelter 0 到 shelter i 最大可以取多少书.

然后我要解释如何结合montonic stack.
看一个例子, arr= [2, 1, 4, 8, 6, 7] 就用上面图里的.
and。dp[3] = dp[2] + 8 = , dp[4] = dp[1] + 4 + 5+ 6 (其实= dp[2] + 5 + 6)
首先我们想想为什么dp[3] 可以直接 = dp[2] + 8 而 dp[4] 就要从dp[2] 开始加呢。
显然 因为arr[4] < arr[3]. 所以当我们考虑终点是arr[4]的时候. 我们shelter 3就不能取走所有的书, 而是最多取走6 -1 = 5本书. 然后shelter 2 只能拿4本. 而dp[3], 因为 arr[3] > arr[2]. 所以我们可以直接arr[2] 取走所有。 而dp[2]的结果恰好就是 shelter 2 取走所有书, 依次推到shelter 0. 所以我们dp[3] 可以直接= dp[2] + arr[3]. dp[2]的结果就是dp[3] 里包含的, 重复利用. dp principle!!!

然后再说为什么可以用montonic stack. 因为montonic stack 本质上找的就是下一个smallest 或者next largest.
那么, 我们要想实现dp. 那就是相当于我们要找到一个range. 这个range 内所有的shelter 都按照递减, 不能取最大值. range 之外(往左) 就可以直接加上. 因为再往左由于他们的book数量比range 内小很多. 所以无条件直接用 dp 之前算过的value 就行了
就像arr[2] = 4, arr[3] = 8, arr[4] = 6. 就算我们arr[4] 取6, arr[3] 取 5, arr[2] 取4. dp[2] 原先算出来的结果就是假设arr[2] 取4. 所以可以直接dp[4] = dp[2] + 5(arr[3]取值) + 6(arr[4] 取值)

那么对于这道题的case. 对于arr[3] 和 arr[4] 来看.
当arr[4] < arr[3] 的时候. arr[3] 只能最大取arr[4] - 1. ,arr[2] 依次递减.
那么假设我们正在计算dp[i], 我们是不是可以通过montonoic stack, 找到这个点j (first to the left of i)where arr[j] < arr[I] - I + j
注意这里arr[i] - i + j = arr[i] - (i - j) 因为从arr[i] 开始往左, 我们的取值都是依次递减. 所以我们要找到第一个index j where arr[j] 不能满足依次递减的取值(换句话说就是arr[j]的值特别小, 可以无条件直接取最大, 用dp) 而arr[j+1] 到 arr[i] 都是那些原本shelter里可取的书就比arr[i] 递减的值多. 意思就是说 arr[j+1] 到 arr[i] 这些值需要重新取, 再加一起, 而arr[j] 之前就可以直接用dp[j].

那我们就可以一个increasing stack.
while(arr[stack.peek() >= arr[I] - I + stack.peek()]) stack.pop()
用这种方式找到这个需要重新算递减的range.
最后加一起就好了!!

有一个地方我写code的时候想了很久, 就是这个summation 算 index 的问题.
如果我算好了 1+2+3+4+5 (假设这5个数就是stack里的index 对应的value) arr = [1,2,3,4,5]
然后我需要算3 + 4 + 5 那肯定是 1+2+3+4+5 - (1+2)
那就是相当于我要算getSumm(5) - getSumm(2) = getSumm(arr[4]) - {getSumm(arr[4] - i + j) = getSumm(arr[4] - 4 + 1) = getSumm(2) = getSumm(arr[2])};
那么换成montonic stack, 这个5就是i. 2 就是 while结束之后stack顶上剩下的那个item.
= getSum(arr[i]) - getSum(arr[i] - i + stack.peek())

如果是这种拿没问题, 正好对应stack. 但是如果stack是空的, 我们怎么计算这个range.
先假设我们的stack还剩一个index 0 的
那么我们会得到
getSumm(arr[4]) - getSumm(arr[4] - 4 + 0) = getSumm(arr[4]) - getSumm(arr[4] - 4)
这个时候, 我们其实相当于算得是 2 + 3 + 4 + 5. 相当于是arr[i] 到 arr[1] 依次递减, arr[0] 用dp[0]
但其实如果计算的时候stack是空的. 就说明我们从arr[i] 到 arr[0] 全部都得依次递减相加, 不能用dp算过的. 所以, 我们得得到1+2+3+4+5.
因此, 我们的公式就变成了
if stack.isEmpty(). => getSum(arr[i]) - getSum(arr[i] - i + (-1))

好了写了很多,大概就这些重点需要牢记。

class Solution {
    public long maximumBooks(int[] books) {
        long[] mem = new long[books.length+1];
        Deque<Integer> stack = new ArrayDeque<>();
        
        long max = 0;
        
        for(int i = 0;i < books.length; ++i){
            while(!stack.isEmpty() && books[stack.peek()] >= books[i] - i + stack.peek()){
                stack.pop();
            }
            
            mem[i] = (stack.isEmpty()? 0: mem[stack.peek()]) + getSummation(books[i]) - getSummation(books[i] - i + (stack.isEmpty() ? -1 : stack.peek()));
            max = Math.max(max, mem[i]);
            
            stack.push(i);
        }
        
        return max;
    }
    
    public long getSummation(int n){
        if (n <= 0) return 0;

	    return (long)((long)n * (long)(n + 1)) / 2;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值