Leet Code 刷题笔记 - - 不求最快最省,但求最短最优雅
前言
- 代码精炼是 Python 的核心,同时能够反应对于语言的熟练程度,本项目目的在于汇总 leet code 最短最优雅的解法,拒绝长篇大论,缩短学习周期,掌握各种技巧,助您在面试中写出令人眼前一亮的解答,给考官留个好印象。
- 为什么我们追求最短?1.短代码更pythonic,而且通常能够避免一些冗余过程。2.除了刷题掌握算法思路之外,我们更追求深入理解和掌握python,学会套用技巧,举一反三。3.真正面试的时候不一定要这么短,可以适当展开几行(O_o 除非你就是想秀其他人一脸 ?),保证思路更清晰,相信就算展开几行也会比其他题解短很多吧。4.刷题很累,找点乐子,送给自己一些成就感吧。5.所有已收录代码都是优中选优,题库解析部分除了短代码外,也有常规解法作为补充。6.若基础知识不够扎实,可以先看专题探索部分,然后再转题库解析学习 Python 中隐藏的先进技巧。
- 项目持续更新中,优先使用 python3,不支持的题目使用 python2 代替,如果您有更短更优雅的解法希望分享的话欢迎联系更新~ [直接发issue 或 fork,记得留下署名和联系方式 ?] 鉴于追求的主题,此项目收录 1.在代码量(不是行数)上明显优于现有解的最短代码 2.思路更高效的一般解法(作为补充放在首选解之后) [题目自带的代码不计入代码量]
- 如果您对当前解析有任何疑问,咱们 issue 见~
- 由于CSDN博客更新需要人工审核比较慢,所以迁移到github上,优先更新github内容。
- 为了快速找到题目可以按 [Ctrl键 + F键] 输入题目序号或名字定位。
- 欢迎加入QQ交流群:902025048 ∷二维码 群内提供更多相关资料~
重要说明
- 由于项目需要频繁更新,CSDN每次都需要人工审核,更新的时候打不开,不太方便,项目迁移至 Github,优先更新github。
- 由于写法不同,博客中的超链接可能失效。
? 里程碑
推荐
- 与本项目有关联的,? 是一个 C++最清晰题解汇总 ?。Python篇注重熟悉语言特性,充分利用高级语言提供的已内置的功能避免冗余编码,最低成本地解决问题。C++篇注重通用思想,分专题逐个击破,深入探究算法流程。俩者同时服用效果更佳,只想学一门也不必担心,俩个项目相辅相成,Python篇会在题解之后添加常规解法作为补充,并根据官方推荐的路线总结一套专题探索,C++篇会利用python题解的思想优化代码,保证代码简洁,可读性高。
- ? 推荐刷题路线:专题探索 → 腾讯精选50题 → 题库解析
题库解析
此专栏追求代码的精简和技巧性,默认已看过题目,? 没看过的话点标题可以跳转链接,咱们一起体验炫酷的 Python
1. Two Sum 4行
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
d = {
}
for i, n in enumerate(nums):
if n in d: return [d[n], i]
d[target-n] = i
- O(N)时间效率的快速解法,用字典记录 {需要的值:当前索引}
2. Add Two Numbers 5行
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode, carry=0) -> ListNode:
if not (l1 or l2): return ListNode(1) if carry else None
l1, l2 = l1 or ListNode(0), l2 or ListNode(0)
val = l1.val + l2.val + carry
l1.val, l1.next = val % 10, self.addTwoNumbers(l1.next, l2.next, val > 9)
return l1
- int(True) 等于 1
- None or 7 等于 7
- 用 carry 记录是否应该进位
3. Longest Substring Without Repeating Characters 3行
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
b, m, d = 0, 0, {
}
for i, l in enumerate(s): b, m, d[l] = max(b, d.get(l, -1) + 1), max(m, i - b), i
return max(m, len(s) - b)
- b代表起始位置,m代表上一步的最大无重复子串,d是一个字典,记录着到当前步骤出现过的字符对应的最大位置
- 每次迭代过程中,遇到遇见过的字符时,b就会变为那个字符上一次出现位置+1,m记录上一次应该达到的全局最大值,所以最后需要再比较一次
4. Median of Two Sorted Arrays 5行
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
a, b, m = *sorted((nums1, nums2), key=len), (len(nums1) + len(nums2) - 1) // 2
self.__class__.__getitem__ = lambda self, i: m-i-1 < 0 or a[i] >= b[m-i-1]
i = bisect.bisect_left(self, True, 0, len(a))
r = sorted(a[i:i+2] + b[m-i:m-i+2])
return (r[0] + r[1 - (len(a) + len(b)) % 2]) / 2
- 本题思路与官方题解类似,时间复杂度O(log(min(m, n))),没看过的话建议先大体了解一下
- python 中 bisect 模块针对的是 list, 如果直接构造 list,时间复杂度为 O(min(m, n)),因此我们修改当前类的魔法方法伪造 list
- 在一个有序递增数列中,中位数左边的那部分的最大值一定小于或等于右边部分的最小值
- 如果总数组长度为奇数,m 代表中位数的索引,否则 m 代表用于计算中位数的那两个数字的左边一个。比如输入为[1,2],[3],那么m应该为[1,2,3]中位数2的索引1,如果输入为[1,3],[2,4],那么m应该为[1,2,3,4]中2的索引1
- 使用二分搜索找到 m 对应的值在a或b中对应的索引,也就是说,我们要找的中位数或中位数左部应该是 a[i] 或者 b[m-i]
- bisect.bisect_left 搜索列表中保持列表升序的情况下,True应该插入的位置(从左侧),比如 [F,F,T] 返回 2,[F,F] 返回 2
- 这里保证 a 是 nums1 和 nums2 中较短的那个,是为了防止二分搜索的时候索引越界
- sorted返回一个list,假设返回值是 [nums1, nums2],那么前面加个 * 号就代表取出列表的所有内容,相当于一个迭代器,结果相当于直接写 nums1, nums2
5. Longest Palindromic Substring 5行
class Solution:
def longestPalindrome(self, s: str) -> str:
r = ''
for i, j in [(i, j) for i in range(len(s)) for j in (0, 1)]:
while i > -1 and i + j < len(s) and s[i] == s[i + j]: i, j = i - 1, j + 2
r = max(r, s[i + 1:i + j], key=len)
return '' if not s else r
- 遍历字符串的每个索引 i,判断能否以 s[i] 或 s[i:i+j+1] 为中心向往拓展回文字符串
7. Reverse Integer 2行
class Solution:
def reverse(self, x):
r = x // max(1, abs(x)) * int(str(abs(x))[::-1])
return r if r.bit_length() < 32 or r == -2**31 else 0
- x // max(1, abs(x))意味着 0:x为0, 1:x为正, -1:x为负,相当于被废弃的函数cmp
- [::-1]代表序列反转
- 2^31 和 -2^31 的比特数为32,其中正负号占用了一位
- 32位整数范围 [−2^31, 2^31 − 1] 中正数范围小一个是因为0的存在
8. String to Integer (atoi) 1行
class Solution:
def myAtoi(self, s: str) -> int:
return max(min(int(*re.findall('^[\+\-]?\d+', s.lstrip())), 2**31 - 1), -2**31)
- 使用正则表达式
^:匹配字符串开头,[\+\-]:代表一个+字符或-字符,?:前面一个字符可有可无,\d:一个数字,+:前面一个字符的一个或多个,\D:一个非数字字符,*:前面一个字符的0个或多个
max(min(数字, 2**31 - 1), -2**31)
用来防止结果越界
9. Palindrome Number 1行
class Solution:
def isPalindrome(self, x: int) -> bool:
return str(x) == str(x)[::-1]
不使用字符串的进阶解法:
class Solution:
def isPalindrome(self, x: int) -> bool:
r = list(map(lambda i: int(10**-i * x % 10), range(int(math.log10(x)), -1, -1))) if x > 0 else [0, x]
return r == r[::-1]
- 思路是一样的,这里把整数转成了列表而不是字符串
- 比如一个整数12321,我想取出百位数可以这么做:12321 * 10^{int(log_{10}12321)} % 10 = 123 % 10 = 3
11. Container With Most Water 3行
class Solution:
def maxArea(self, height: List[int]) -> int:
res, l, r = 0, 0, len(height) - 1
while l < r: res, l, r = (max(res, height[l] * (r - l)), l + 1, r) if height[l] < height[r] else (max(res, height[r] * (r - l)), l, r - 1)
return res
- 双指针 O(N) 解法
- res:结果,l:容器左壁索引,r:容器右壁索引
- 如果 height[l] < height[r] 那么 l += 1 否则 r -= 1,说明:如果 height[0] < height[3] 那么(0, 1), (0, 2)对应的容器体积一定小于(0, 3)的,因为此时计算体积的时候高为 height(0),容器的宽减少而高不增加,面积必然缩小
13. Roman to Integer 2行
class Solution:
def romanToInt(self, s: str) -> int:
d = {
'I':1, 'IV':3, 'V':5, 'IX':8, 'X':10, 'XL':30, 'L':50, 'XC':80, 'C':100, 'CD':300, 'D':500, 'CM':800, 'M':1000}
return sum([d.get(s[max(i-1, 0):i+1], d[n]) for i, n in enumerate(s)])
- 构建一个字典记录所有罗马数字子串,注意长度为2的子串记录的值是(实际值-子串内左边罗马数字代表的数值)
- 这样一来,遍历整个s的时候判断当前位置和前一个位置的两个字符组成的字符串是否在字典内,如果在就记录值,不在就说明当前位置不存在小数字在前面的情况,直接记录当前位置字符对应值
- 举个例子,遍历经过IV的时候先记录I的对应值1再往前移动一步记录IV的值3,加起来正好是IV的真实值4。max函数在这里是为了防止遍历第一个字符的时候出现[-1:0]的情况
14. Longest Common Prefix 2行
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
r = [len(set(c)) == 1 for c in zip(*strs)] + [0]
return strs[0][:r.index(0)] if strs else ''
- 利用好zip和set
15. 3Sum 5行
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums, r = sorted(nums), set()
for i in [i for i in range(len(nums)-2) if i < 1 or nums[i] > nums[i-1]]:
d = {
-nums[i]-n: j for j, n in enumerate(nums[i + 1:])}
r.update([(nums[i], n, -nums[i]-n) for j, n in enumerate(nums[i+1:]) if n in d and d[n] > j])
return list(map(list, r))
- 时间复杂度:O(N^2)
- 这里 sort 一是为了避免重复,这一点可以体现在我们输出的结果都是升序的,如果不这么做 set 无法排除一些相同结果,而是为了节省计算,防止超时
- for 循环内部的代码思想同
第一题 Two Sum
,用字典记录{需要的值:当前索引},如果字典中存在相同的数字,那么将会记录比较大的那个索引,因此可以用d[n] > i
来避免一个元素重复选择 (nums[i], n, -nums[i]-n)
保证了列表升序
16. 3Sum Closest 7行
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums, r, end = sorted(nums), float('inf'), len(nums) - 1
for c in range(len(nums) - 2):
i, j = max(c + 1, bisect.bisect_left(nums, target - nums[end] - nums[c], c + 1, end) - 1), end
while r != target and i < j:
s = nums[c] + nums[i] + nums[j]
r, i, j = min(r, s, key=lambda x: abs(x - target)), i + (s < target), j - (s > target)
return r
- float(‘inf’) = 正无穷
- 排序,遍历,双指针,O(N^2) 时间复杂度,二分法初始化
- 排序是为了使用双指针,首先遍历得到索引 c,然后计算 c,左指针 i,右指针 j 对应数字之和,如果大于 target,j 向内移动,否则 i 向内移动
- i 的初始值不是 c + 1,是为了减少计算量,用二分法得到一个合理的初始值
20. Valid Parentheses 2行
class Solution:
def isValid(self, s: str) -> bool:
while any(('()' in s, '[]' in s, '{}' in s)): s = s.replace('()', '').replace('[]', '').replace('{}', '')
return not s
-
不断删除有效括号直到不能删除,思路简单效率低。另外,stack的方法也很简单,而且快多了。
class Solution: def isValid(self, s: str) -> bool: stack, d = [], { '{': '}', '[': ']', '(': ')'} for p in s: if p in '{[(': stack += [p]; elif not (stack and d[stack.pop()] == p): return False return not stack
21. Merge Two Sorted Lists 4行
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
if l1 and l2:
if l1.val > l2.val: l1, l2 = l2, l1
l1.next = self.mergeTwoLists(l1.next, l2)
return l1 or l2
- 7 or 9 等于 7
- None and 7 等于 None
- sorted用在这里为了保证 l1 的值小于等于 l2 的值
23. Merge k Sorted Lists 4行
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def