乐趣区

关于leetcode:聊聊刷题中的顿悟时刻

和几位始终保持刷题好敌人探讨了一下刷题的 顿悟 时刻,他们几位大都获得了不错的 offer,比方 Google 微软,Amazon,BAT 等。

通过和他们的沟通,我发现了大家 顿悟 时刻都是相似的。那具体他们有哪些类似点呢?咱们一起来看下。

1. 同样的类型要刷很多能力顿悟

比方你想 顿悟 二分,那么首先你须要做足够多的二分题。

而因为二分其实是一个大的分类。因而实践上你如果想对二分大类顿悟,那么必不可少的是 先做足够多的二分题,并且这些题目能够笼罩所有的二分类型

比方西法总结的根底二分,最左最右二分以及能力检测二分,其中大部分有点艰难的题目都是能力检测二分。

对二分不相熟的能够看下西法之前总结的《二分专题》:

  • 简直刷完了力扣所有的二分题,我发现了这些货色(上)
  • 简直刷完了力扣所有的二分题,我发现了这些货色(下)

那么推而广之,如果你想对刷算法题整体进行 顿悟 ,那么就不得不先 做足够多的题目,且这些题目能笼罩所有你想顿悟的考点

这也就是说为什么你看的大佬中的大佬都刷了上千道题的起因。因为没有上千道题目的积攒,你很难对所有题目类型都 顿悟 的。当然你如果只是应酬大多数的考点并且不参加比赛的话,兴许小几百道也是 ok 的。

2. 回顾做过的题目

有的同学比拟间接,他们就是间接温习做过的题目。而有的同学则是通过做新的题目回想到之前做过的某些题,从而达到温习的作用。

不论是哪种类型。他们都必须通过一个阶段,那就是 和曾经做过的题目建立联系。如果你只是自觉做题的话,效率必定上不去。

最开始刷题的时候,我会建设一些 anki 卡片。这其实就是为了 强制回顾做过的题目。另外做新的题目的时候,我会强制本人思考:

  • 这道题考查了什么常识?
  • 和之前做过的哪些题能够建立联系?
  • 是否能够用之前刷题的解法套?
  • corner case 有哪些?
  • 。。。

通过这些思考,缓缓就会把做过的题目 有机地联合起来,而不是让这些题目变成彼此的信息孤岛。

3. 对做过的题目进行形象

这个是我要讲的最初一点,然而这点却尤为重要,说它是最重要也不过分。

一方面,如果一道题目没有通过形象,那么咱们很难记住,很难在将来回顾起来。另一方面,如果一道题目可能形象为纯正的题目,那么阐明你对这个题目看的比拟透彻了。未来碰到换皮题,你一形象,就会发现: 这不就是之前 xxxx 的换皮题么?

常常看我题解和文章的同学晓得我之前写过不少换皮题的扒皮解析,这就是我做题和写文章格调。

在这里,我再举个例子。

留神:上面举的三道题例子都须要你 把握二分法的能力检测二分,如果不理解倡议先看下我下面的文章。

Shopee 的零食柜

这是 shopee 的校招编程题。

题目形容

shopee 的零食柜,有着各式各样的零食,然而因为贪吃,小虾同学体重日益减少,终于被人叫为小胖了,他终于下定决心减肥了,他决定每天晚上去操场跑两圈,然而跑步太累人了,他想转移注意力,遗记苦楚,正在听着音乐的他,忽然有个想法,他想跟着音乐的节奏来跑步,音乐有 7 种音符,对应的是 1 到 7,那么他对应的步长就能够是 1 - 7 分米,这样的话他就能够转移注意力了,然而他想放弃本人跑步的速度,在规定工夫 m 分钟跑完。为了防止被累死,他须要布局他每分钟须要跑过的音符,这些音符的步长总和要尽量小。上面是小虾同学听的歌曲的音符,以及规定的工夫,你能通知他每分钟他应该跑多少步长?输出形容:
输出的第一行输出 n(1 ≤ n ≤ 1000000,示意音符数),m(1<=m< 1000000, m <= n)组成,第二行有 n 个数,示意每个音符(1<= f <= 7)输入形容:
输入每分钟应该跑的步长
示例 1
输出
8 5 6 5 6 7 6 6 3 1
输入
11

链接:https://www.nowcoder.com/ques…
起源:牛客网

思路

通过形象,这道题实质上就是 给你一个数组(数组值范畴是 1 到 7 的整数),让你将数组分为最多 m 子数组,求 m 个子数组和的最小值

间接答复子数组和最小值比拟艰难,然而答复某一个具体的值是否能够达到绝对容易。

比方答复子数组和最小值为 100 能够不能够绝对容易。因为咱们只须要遍历一次数组,如果间断子数组大于 100 就切分新的一块,这样最初切分的块数小于等于 m 就意味着 100 能够。

另外一个关键点是这种检测具备枯燥性。比方 100 能够,那么任何大于 100 的数(比方 101)必定都是能够的。如果你看过我下面的《二分专题》或者做过不少能力检测二分的话,不难想到能够利用这种枯燥性做能力检测二分失去答案。并且咱们要找到满足条件的最小的数,因而能够套用最左能力检测二分失去答案。

代码

临时不写,因为这道题和前面的一道题是一样的。

410. 宰割数组的最大值

题目形容

给定一个非负整数数组 nums 和一个整数 m,你须要将这个数组分成 m 个非空的间断子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。示例 1:输出:nums = [7,2,5,10,8], m = 2
输入:18
解释:一共有四种办法将 nums 宰割为 2 个子数组。其中最好的形式是将其分为 [7,2,5] 和 [10,8]。因为此时这两个子数组各自的和的最大值为 18,在所有状况中最小。示例 2:输出:nums = [1,2,3,4,5], m = 2
输入:9
示例 3:输出:nums = [1,4,4], m = 3
输入:4
 

提醒:1 <= nums.length <= 1000
0 <= nums[i] <= 106
1 <= m <= min(50, nums.length)

起源:力扣(LeetCode)
链接:https://leetcode-cn.com/probl…

思路

这道题官网难度是 hard。和后面题形象后截然不同,不必我多解释了吧?

你看通过这样的形象,是不是有种必由之路的顿悟感觉?

代码

代码反对:Python3

Python3 Code:


class Solution:
    def splitArray(self, nums: List[int], m: int) -> int:
        lo, hi = max(nums), sum(nums)
        def test(mid):
            cnt = acc = 0
            for num in nums:
                if acc + num > mid:
                    cnt += 1
                    acc = num
                else:
                    acc += num
            return cnt + 1 <= m

        while lo <= hi:
            mid = (lo + hi) // 2
            if test(mid):
                hi = mid - 1
            else:
                lo = mid + 1
        return lo

你认为这就完了么?相似的题目几乎不要太多了。西法再给你举个例子。

LCP 12. 小张刷题打算

题目形容

为了进步本人的代码能力,小张制订了 LeetCode 刷题打算,他选中了 LeetCode 题库中的 n 道题,编号从 0 到 n-1,并打算在 m 天内依照题目编号程序刷完所有的题目(留神,小张不能用多天实现同一题)。在小张刷题打算中,小张须要用 time[i] 的工夫实现编号 i 的题目。此外,小张还能够应用场外求助性能,通过询问他的好敌人小杨题目的解法,能够省去该题的做题工夫。为了避免“小张刷题打算”变成“小杨刷题打算”,小张每天最多应用一次求助。咱们定义 m 天中做题工夫最多的一天耗时为 T(小杨实现的题目不计入做题总工夫)。请你帮小张求出最小的 T 是多少。示例 1:输出:time = [1,2,3,3], m = 2

输入:3

解释:第一天小张实现前三题,其中第三题找小杨帮忙;第二天实现第四题,并且找小杨帮忙。这样做题工夫最多的一天破费了 3 的工夫,并且这个值是最小的。示例 2:输出:time = [999,999,999], m = 4

输入:0

解释:在前三天中,小张每天求助小杨一次,这样他能够在三天内实现所有的题目并不花任何工夫。限度:1 <= time.length <= 10^5
1 <= time[i] <= 10000
1 <= m <= 1000

起源:力扣(LeetCode)
链接:https://leetcode-cn.com/probl…

思路

和后面的题目相似。通过形象,这道题实质上就是 给你一个数组(数组值范畴是 1 到 10000 的整数),让你将数组分为最多 m 子数组,每个子数组能够删除最多一个数,求 m 个子数组和的最小值

和下面题目惟一的不同是,这道题容许咱们在子数组中删除一个数。显然,咱们应该贪婪地删除子数组中最大的数。

因而我的思路就是能力检测局部保护子数组的最大值,并在每次遍历过程中减少判断:如果 删除子数组最大值后当前 能够满足子数组和小于检测值(也就是 mid)。

代码

代码反对:Python3

Python3 Code:

class Solution:
    def minTime(self, time: List[int], m: int) -> int:
        def can(mid):
            k = 1 # 须要多少天
            t = 0 # 以后块的总工夫
            max_time = time[0]
            for a in time[1:]:
                if t + min(max_time, a) > mid:
                    t = 0
                    k += 1
                    max_time = a
                else:
                    t += min(max_time, a)
                    max_time = max(max_time, a)
            return k <= m

        l, r = 0, sum(time)

        while l <= r:
            mid = (l+r)//2
            if can(mid):
                r = mid - 1
            else:
                l = mid + 1
        return l

工夫复杂度的话三道题都是一样的,咱们来剖析一下。

咱们晓得,工夫复杂度剖析就看执行次数最多的代码即可,显然这道题就是能力检测函数中的代码。因为能力检测局部咱们须要遍历一次数组,因而工夫为 $O(n)$,而能力检测函数执行的次数是 $logm$。因而工夫复杂度都是 $nlogm$,其中 n 为数组长度,m 为数组和。

总结

顿悟真的是一种十分美好的感觉,我通过采访几位大佬发现大家顿悟的经验都是相似的,那就是:

  1. 同样的类型要刷很多能力顿悟
  2. 回顾做过的题目
  3. 对做过的题目进行形象

对第三点西法通过三道题给大家做了粗疏的解说,心愿大家做题的时候也能把握好节奏,触类旁通。最初祝大家刷题高兴,offer 多多。

退出移动版