Leetcode 每日一题和每日一题的下一题刷题笔记 8/30

275 阅读8分钟

Leetcode 每日一题和每日一题的下一题刷题笔记 8/30

写在前面

这是我参与更文挑战的第8天,活动详情查看:更文挑战

快要毕业了,才发现自己被面试里的算法题吊起来锤。没办法只能以零基础的身份和同窗们共同加入了力扣刷题大军。我的同学们都非常厉害,他们平时只是谦虚,口头上说着自己不会,而我是真的不会。。。乘掘金鼓励新人每天写博客,我也凑个热闹,记录一下每天刷的前两道题,这两道题我精做。我打算每天刷五道题,其他的题目嘛,也只能强行背套路了,就不发在博客里了。

本人真的只是一个菜鸡,解题思路什么的就不要从我这里参考了,编码习惯也需要改进,各位如果想找刷题高手请教问题我觉得去找 宫水三叶的刷题日记 这位大佬比较好。我在把题目做出来之前尽量不去看题解,以免和大佬的内容撞车。

另外我也希望有得闲的大佬提供一些更高明的解题思路给我,欢迎讨论哈!

好了废话不多说开始第八天的前两道题吧!

2021.6.8 每日一题

1049. 最后一块石头的重量 II

这道题和昨天的题是真的太像了,拿到题目没有思路的建议去看一下我昨天的文章。

题目描述比较像数学家在写教科书时候用的语言,翻译成人话就是两个石头见面谁也不服谁,要碰一碰。大的碰小的小的碎,大的只剩下原来比小的大的那部分,两个一样大的碰一碰,两个一起碎。这玩意像不像动量定理,石头就是抽象的动量,哇简直是一模一样。

我觉得我说到这里应该已经有很多人会做这道题了,就算不会做,也不惧怕这个题的背景了。

为什么说这道题和昨天的每日一题非常像呢?昨天是不是给数组里每个数前面加符号,所有分到 + 号的是一堆,所有分到 - 号的是一堆,这个题不也一样吗,一些石头(动量)是左边这一堆,一些石头(动量)是右边这一堆,左边的和右边的分别拿出来碰一碰,要让最后剩下的石头(动量)最小。(昨天的问题是给出目标,找方案数,今天是直接找最小可达成的目标)这还不像???当然题目相像也有可能不太好,容易被以往的经验带到坑里。。。好了回归正题,这道题应该也是动态规划没跑了。沿用昨天的老思路,那些归到右边这一堆的石头(动量)都带上了负号,它们总和的绝对值是 neg_sum,然后全部石头(动量)还没分两堆,没有负号的时候总和绝对值是 abs_sum,这个时候就可以把那些归到左边这一堆,带上正号的石头(动量)的总和的绝对值表示出来,pos_sum = abs_sum - neg_sum,然后我们就可以求两堆石头碰完的结果了,也就是目标值 target = abs_sum - 2 * neg_sum,今天这道题没有现成的 target,我们要求的是 min(target),目标很明确,现在问题就可以被描述成一个容量是 abs_sum / 2,价值就是已经使用的容量 i 能不能刚好达到,或者说是背包里剩余的空间能不能这么小。背包被我们塞的越满当越好,已经使用的容量越大越好,背包里剩余的空间越小越好。容量为什么是 abs_sum / 2,这个应该不难理解,目标值越小越好但最小到0这里就到头了,不可能是负数。(我这不是玩游戏啊,背包容量被撑爆以后变成-2147483648,然后再硬塞进去一瓶血药背包容量变成2147483647)好了不开玩笑,这道题容量和价值都明确了,接下来写状态转移方程。根据昨天的经验,要省空间复杂度,里面这一层循环是倒着来的。那么,在最后的最后,这一步我们做完,就找到了最小的目标值 min(target),这个时候 neg_sum 应该是非常接近 abs_sum / 2 的,虽然我们现在不清楚 neg_sum 的具体值,但要去搜索这个值,肯定从最接近它的位置开始,也就是说,我们倒序遍历的起始位置,就是背包的最大容量 abs_sum / 2。然后我们一步一步搜索,到哪里停止呢?到我们正在看的这个石头(动量)这里停止。肯定不能无休止的搜下去,当剩余的容量还比现在这个石头(动量)大一点的时候,就是搜索停止的位置,就和前面开玩笑时候说的一样,总不能把背包的容量塞成负数不是?状态转移方程这么写:

dp[i]={dp[i],i<cur_weightdp[i]dp[icur_weight],icur_weight,pick cur_weightdp[i],icur_weight,not pick cur_weight\texttt{dp}[\texttt{i}] = {\left \{ \begin{array}{ll} \texttt{dp}[\texttt{i}], & \texttt{i} < \texttt{cur\_weight} \\ \texttt{dp}[\texttt{i}] \lor \texttt{dp}[\texttt{i} - \texttt{cur\_weight}], & \texttt{i} \geq \texttt{cur\_weight}, pick \ \texttt{cur\_weight} \\ \texttt{dp}[\texttt{i}], & \texttt{i} \geq \texttt{cur\_weight}, not \ pick \ \texttt{cur\_weight} \end{array} \right.}

啥意思呢?dp[i] 就是价值,就是现在说的这个容量 i 它能不能刚好达到。表达式里面那个有转移的情况是指现在这块石头(动量) cur_weight 被我看上了,已经被我放包里了,我看其他的石头(动量)能不能满足我现在的需求(容量 i - cur_weight 能不能刚好达到)。解释到这里只解释了 dp[i - cur_weight],然后这里有个灵性的“或”,前面的 dp[i] 是指我已经知道现在有这么一块石头(动量)了,我不管具体怎么装石头(动量),我只要能达到我的要求就行(体会一下“或”的短路表达)。整理一下,这个式子的意思是,我看上这块石头(动量) cur_weight 了,那么现在能不能达到我的需求 dp[i],先看看不用这块石头(动量)(这块石头(动量)此时没放进背包),用其他还不在背包里的石头(动量)能不能满足;再不济我就把这块看上的石头(动量)塞包里,然后看之后能不能用其他还不在背包里的石头(动量)满足我的需求。其他两种情况就很好解释,容量不够就是要不起,容量够但是看不上,那不就是没放进包里嘛,和要不起有什么区别(联想斗地主啊,很容易懂不是嘛。哇难道说斗地主其实也是一道背包问题???)。

我觉得我已经讲的够清楚了,写到这里我的耐心已经被磨得差不多要没了,赶紧写代码发泄一下。


class Solution {
public:
    int lastStoneWeightII(vector<int> &stones) {
        int sum = accumulate(stones.begin(), stones.end(), 0);
        int m = sum / 2;
        vector<int> dp(m + 1);
        dp[0] = true;
        for (int weight : stones) {
            for (int i = m; i >= weight; i--) {
                dp[i] = dp[i] || dp[i - weight];
            }
        }
        for (int i = m;; i--) {
            if (dp[i]) {
                return sum - 2 * i;
            }
        }
    }
};

image.png

今天我给变量命名又被我老师给批评了。。。那我以后学着点别人的命名习惯吧。

2021.6.8 每日一题下面的题

1854. 人口最多的年份

这道题用差分数组,这个东西怎么这么像。。。前缀和。哦,原来前缀和在这等着我呢。把记录里面年份都调整成相对年份好一点,就和好多地方用的时间戳是从 1970 开始计时一样。1950到2050一共是101个年份,出生一个加一个,老掉一个减一个,然后这是差分量啊,别搞混了。从初态一直加到现态才是积累量,这是当前年份真实的人口数量。差分方程应该不陌生,就那个意思。遍历差分数组,把各个年份的人口数算出来的同时更新最大人口数,只有比最大的要大,才更新,一样大的不更新,这样就能找到最大人口数最早出现的年份了。(当初信号与系统虐我千百遍,差分方程这时候居然让我开了窍,奇妙)


class Solution {
private:
    static constexpr int offset = 1950;   // birth_i >= 1950 && birth_i <= 2050
    
public:
    int maximumPopulation(vector<vector<int>>& logs) {
        vector<int> delta(101, 0);
        for (auto&& log: logs) {
            // relative birth
            delta[log[0]-offset]++;
            // relative death
            delta[log[1]-offset]--;
        }
        int max_people = 0;
        int first_year = 0;
        int cur_people = 0;
        for (int i = 0; i < 101; ++i){
            cur_people += delta[i];
            if (cur_people > max_people){
                max_people = cur_people;
                first_year = i;
            }
        }
        return first_year + offset;
    }
};

image.png

小结

0-1 背包问题,差分数组(前缀和反过来用)

参考链接

我觉得我应该放一个动量定理的链接,但是百度百科的内容和我想讲的东西相差的有点多,所以我建议各位去翻翻物理课本,这个碰石头的问题如果用动量来描述真的太形象了,但是我估计好多人把动量这个概念给忘掉了。。。

Leetcode 每日一题和每日一题的下一题刷题笔记 7/30

我真的是对着昨天的每日一题的笔记写的今天的笔记,两道题真的太像了。