向灵神学算法第一课:入门快刷

昨天偶然看了灵神的LeetCode周赛视频,顿感新奇!Online Assessment中的题在我看来是复杂无比,完全不知从何下手,就像我看周赛题目一样。而这题目在灵神面前如同捏🤏小鸡🐤一般简单。于是怀着无比激动的心情一下看完了灵神所有的文档,顿感这才是我一直寻求的方向。当务之急是尽快刷完越多的题目越好。所以从这篇文章开始,近期我将把重心放在灵神的学习路径上,开新文集以记录学习心得。

灵神学习路径链接
这篇文章主要记录的是入门清单LeetCode编程入门题

2235. 两整数相加
第一题非常简单,这才叫入门嘛。网上很多别的刷题列表,上来就是链表反转之类的东西,作为新手的我还很多操作都不会/不熟呢,就很难下笔。这个入门才是真的入门!

2413. 最小偶倍数
这题求2与正整数n的公倍数。也比较简单,但是我一开始写的是如果n是奇数就返回2 * n,如果是偶数就返回n。很简单也正确的思路对吧。但是灵神提供了一个更简单的方法:return (n % 2 + 1) * n。这题给我的关键启发在于基础知识的应用。就是即便是简单的判断式,也有的时候可以用更简单的表达式代替,而关键就在于能否想得到了,所以 经验还得是在于积累。

class Solution: // Python3
    def smallestEvenMultiple(self, n: int) -> int:
        return (n % 2 + 1) * n

1486. 数组异或操作
这题虽然标的是简单题,但是要想利用好数学公式来简化,还得是有深厚的数学功底。至少我再接触这个数学结论的时候是不知道的,看着解析还思考了好半天。数学结论就是:从0开始一直异或到n,异或的结果是以4为倍数的循环函数。换句话说就是,0 XOR(异或) 1 XOR 2 ... XOR n, 当n = (4k)/(4k+1)/(4k+2)/(4k+3)时,分别为n/1/n+1/0。根据此结论,再去根据异或的性质去把原表达式配成 一个方便利用这个结论的表达式。使用这个方法的好处就在于优化:时间复杂度和空间复杂度都是O(1)了。

class Solution: // Python3
    def xorOperation(self, n: int, start: int) -> int:
        xor_n = lambda n:(n, 1, n+1, 0)[n % 4]
        a = start // 2
        b = n & start & 1
        return (xor_n(a + n -1) ^ xor_n(a - 1)) * 2 + b

709. 转换成小写字母
本题肯定不是考察python库函数的应用哈,所以记录一下除了return s.lower()之外的心得。
如果要自行实现这个API的话,就得从ascii码下手了。A-Z的ascii码是65-90。a-z是97-122。所以大小写之间是差32,原因是在二进制保存中,保证大小写对应的字母的二进制数其他位都相等,只有(从右往左数)第六位有区别。这样做是便于计算/开发的遍历。1️⃣所以要从大写字母变小写只需在ascii码上加32就好,但是直接加略显基础,新学了位运算后就知道会用 | 32来计算了。| 是只要有1,结果就是1。所以在 |32之后,所有字母就都保持小写了。2️⃣那如果是 ^ 32呢?原来是0的变成了1,原来是1的变成了0,所以就是大写字母变小写,小写字母变大写了。3️⃣如果是 & 32呢?是不变,因为1 & 1 = 1,1 & 0 = 0。那如果想保持大写怎么办? 答案是 & -33。-33的二进制是011111,确保从右往左第六位是0的同时,保证了右边五位是1。也就是 & -33之后,后五位不变,从右往左第六位保持为0,确保为大写字母了。

字母位运算技巧

--- 来自 id为“疯子” 的评论,更多位运算技巧看这里

另外值得一提的是本题的代码可以综合为一行:

class Solution: // Python3
    def toLowerCase(self, s: str) -> str:
        return "".join(chr(asc | 32) if 65 <= (asc := ord(ch)) <= 90 else ch for ch in s)

这里融合了赋值运算符号 := 像个小海象的脸一样。还有ord()方法获取ascii码。chr()方法把ascii码变为字母字符,再就是"".join()方法拼接字符串。

258. 各位相加
这题也非常有意思。首先暴力解法我就没写对,总是控制不好边界。正确写法为:

class Solution:
    def addDigits(self, num: int) -> int:
        while num >= 10:
            sum = 0
            while num:
                sum += num % 10
                num //= 10
            num = sum
        return num

作者:力扣官方题解

时间复杂度:O(log n)
这题巧妙的地方在于 利用数学原理,把它降为O(1)的时间复杂度。
利用到的数学原理:一个数 模 9 的结果 = 一个数的各位数字相加之和 模 9 的结果。
关于这点有一个很好理解/证明的方法:假设abcd是一个四位数,a,b,c,d分别代表一个数字,组成的四位数,那么abcd = 1000a+100b+10c+d = 999a+99b+9c+d+a+b+c。因为 模9 之后,999a, 99b, 9c 都等于0,所以一个数 模 9 以后的结果就等于 每位数字相加之和 模 9 的结果。
那么这个结论在这题怎么用呢?
假设第一个数为abcd四位数,记为num1。由上述推导知道 abcd % 9 = (a+b+c+d) % 9。而a+b+c+d 是一个新数字,我们用num2记录。假设num2 = mn 是个二位数。那么 把(a+b+c+d)用(mn)代替,就变成了abcd % 9 = (a+b+c+d) % 9 = mn % 9,再用一次上述原理,就是abcd % 9 = (a+b+c+d) % 9 = mn % 9 = (m+n) % 9 ... 直到只剩一位数。这就是为什么无论循环有几层,都可以用原数字num直接模9求得结果。
但是也有个例外,那就是num本身是9的倍数的话,按理来说模的范围是[1,8],但num各位数字相加直到结尾为一位数的时候 那个结尾可能为9。所以得对num是9的倍数做特殊处理。这也是为什么要先把num-1,然后再求完模之后再把1加回来。因此利用数学原理后的代码为:

class Solution: // Python3
    def addDigits(self, num: int) -> int:
        return (num - 1) % 9 + 1 if num else 0

作者:力扣官方题解

231. 2 的幂
数学原理:利用2的幂的二进制巧算。更多位运算技巧上述已经提到了文章了。

class Solution: // Python3
    def isPowerOfTwo(self, n: int) -> bool:
        return n > 0 and n & (n-1) == 0

326. 3 的幂
这个就没数学原理了。只能暴力解决,但是相比正向而言,逆向思维会方便一点:

class Solution: // Python3
    def isPowerOfThree(self, n: int) -> bool:
        return n > 0 and 3 ** 19 % n == 0

1422. 分割字符串的最大得分
除了暴力解法,还可以考虑每次往右多切一格,分数会发生什么变化?这样就可以把一个平方级别的复杂度优化为只需遍历两边字符串的复杂度了。第一遍遍历获取初始分数,第二次遍历获取每多切一格的分数变化。

class Solution: // Python3
    def maxScore(self, s: str) -> int:
        ans = score = (s[0] == '0') + s[1:].count('1')
        for i in s[1:-1]:
            score += 1 if i == '0' else -1
            ans = max(ans, score)
        return ans

2586. 统计范围内的元音字符串数
这题我做出来了,但是还要额外记录的理由是,又向灵神学习了一道 简化代码成一句代码 的题

class Solution: // Python3
    def vowelStrings(self, words: List[str], left: int, right: int) -> int:
        return sum(s[0] in 'aeiou' and s[-1] in 'aeiou' for s in words[left: right + 1])

852. 山脉数组的峰顶索引
二分法初体验:
注意判断arr[mid] ? arr[mid + 1]这里,只能是 '>' 。否则会有bug。至于理由的话,理解起来的话是当mid所指的可能是最大值时,才把mid值赋给ans才行。如果是arr[mid] < arr[mid + 1]的时候把mid赋值给ans可能会导致最后遍历完后ans还没来得及更新就return了。

class Solution: // Python3
    def peakIndexInMountainArray(self, arr: List[int]) -> int:
        n = len(arr)
        left, right, ans = 1, n - 2, 0
        
        while(left <= right):
            mid = (left + right) // 2
            if(arr[mid] > arr[mid + 1]):
                ans = mid
                right = mid - 1
            else:
                left = mid + 1
        return ans

一天班的时间刷完20题然后写完这篇文章,感觉良好,抓紧了时间,收获也颇多,但同时也感叹不知道刷完所有需要的题得多久。坚持加油吧!

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容