LeetCode刷题之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
  • os 模块有提供一样的函数
    class Solution:
        def longestCommonPrefix(self, strs: List[str]) -> str:
    	return os.path.commonprefix(strs)
    

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 mergeKLists(self, lists: List[ListNode]) -> ListNode:
        r, n, p = [], lists and lists.pop(), None
        while lists or n: r[len(r):], n = ([n], n.next or lists and lists.pop()) if n else ([], lists.pop())
        for n in sorted(r, key=lambda x: x.val, reverse=True): n.next, p = p, n
        return n if r else []
  • 本题思路:

    1. 把题目给的所有链表中的所有节点放进一个列表 r。
    2. 对这个列表 r 中的所有节点进行从大到小的排序。O(NlogN)
    3. 把每个节点的指针指向前一个节点。(第一个节点,也就是最大的那个,指向None。)
    4. 返回最后一个节点,也就是整个新链表的开头。
  • 如何把所有节点放进 r(result link)?

    我们首先初始化 r 为空列表,初始化 n(node) 为题目所给的第一个链表的开头节点,并删除lists中的这个节点,接着进入while循环,如果 n 不为空,那么 r += [n],这里使用了切片的技巧(r[len®:]=[n]相当于r=r+[n]),n=n.next,如果n是第一个链表的最后一个节点的话n.next就是None,下一次while的时候如果lists不为空就说明还有别的链表,此时n为None,我们让 r 不变,n=lists.pop(),也就是从lists中再取下一个节点赋值给n,重复以上步骤直到 lists 为空,我们就把所有节点放进 r 了。

  • 怎么对 r 排序?

    用了sorted函数,其中key定义了排序时用来比较的是每个元素的val属性,同时设置reverse为True代表降序排序。

  • 如何修改每个节点的指针?

    我们初始化 p(previous node) 为None。遍历降序排好的列表 r,r中的第一个元素就是值最大的元素,也就是我们应该返回的链表的结尾,我们设置它指向None,然后让p=这个节点,继续for循环。之后每经过一个节点 n 就把这个节点的next属性设置为上一个节点 p,遍历完成之后的 n,也就是我们遍历经过的最后一个元素,拥有最小的值,自然就是整个新链表的起始节点,我们将其作为输出值,函数返回。

26. Remove Duplicates from Sorted Array 3行

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        for i in range(len(nums)-1, 0, -1):
            if nums[i] == nums[i-1]: nums.pop(i)
        return len(nums)
  • 时间效率O(N)空间效率O(1),逆遍历可以防止删除某个元素后影响下一步索引的定位
  • 每次删除数组元素会引发大量的数据迁移操作,使用以下算法解题效率更高
    class Solution:
        def removeDuplicates(self, nums: List[int]) -> int:
    	i = 0
    	for j in range(1, len(nums)):
    	    if nums[j] != nums[i]:
    		nums[i + 1] = nums[j]
    		i += 1
    	return i + 1 if nums else 0
    
    • 此解法思路同官方题解
    • 数组完成排序后,我们可以放置两个指针 i 和 j,其中 i 是慢指针,而 j 是快指针。只要 nums[i] = nums[j],我们就增加 j 以跳过重复项。当我们遇到 nums[j] != nums[i]时,跳过重复项的运行已经结束,因此我们必须把它(nums[j])的值复制到 nums[i + 1]。然后递增 i,接着我们将再次重复相同的过程,直到 j 到达数组的末尾为止

28. Implement strStr() 1行

class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
		return haystack.find(needle)
  • 不用内置函数也可以

    class Solution:
    	def strStr(self, haystack: 'str', needle: 'str') -> 'int':
    	    for i in range(0, len(haystack) - len(needle) + 1):
    	        if haystack[i:i+len(needle)] == needle:
    	            return i
        	return -1
    

29. Divide Two Integers 5行

class Solution:
    def divide(self, dividend: int, divisor: int) -> int:
        a, b, r, t = abs(dividend), abs(divisor), 0, 1
        while a >= b or t > 1:
            if a >= b: r += t; a -= b; t += t; b += b
            else: t >>= 1; b >>= 1
        return min((-r, r)[dividend ^ divisor >= 0], (1 << 31) - 1)
  • 让被除数不断减去除数,直到不够减。每次减完后除数翻倍,并记录当前为初始除数的几倍(用 t 表示倍数 time),若发现不够减且 t 不为 1 则让除数变为原来的一半, t 也减半
  • a 为被除数绝对值,b 为除数绝对值,r 表示 result,t 表示当前除数对于原始除数的倍数
  • a << b 相当于 a * 2**b,a >> b 相当于 a // 2**b
  • 异或操作 ^ 可以判断俩数字是否异号

33. Search in Rotated Sorted Array 3行

class Solution:
    def search(self, nums, target):
        self.__class__.__getitem__ = lambda self, m: not(target < nums[0] <= nums[m] or nums[0] <= nums[m] < target or nums[m] < target <= nums[-1])
        i = bisect.bisect_left(self, True, 0, len(nums))
        return i if target in nums[i:i+1] else -1
  • 作出数列的函数图像,可以看作是一个含断点的局部递增函数,形如⚡️,前面一段总是比较高
  • python 中 bisect 模块针对的是 list, 如果直接构造 list,相当于遍历所有元素,时间复杂度为 O(N) 而不是 O(logN),因此我们修改当前类的魔法方法伪造 list,然后用当前类代替list
  • 用二分搜索时,m 代表 middle,low 代表 low,hi 代表 high,当满足任一条件{① targe < middle 且 middle 在前一段上 且 target < nums[0] ② target > middle 且 middle 在第一段上 ③ target > middle 且 middle 在第二段上 且 target <= nums[-1]}时,应该向右搜索,因此 getitem 返回 False。
  • 另外还有一种简单的思路:二分法找到断点的位置恢复原始数组,然后正常二分法即可
    class Solution:
        def search(self, nums, target):
    	lo, hi, k = 0, len(nums) - 1, -1
    	while lo <= hi:
    	    m = (lo + hi) // 2
    	    if m == len(nums) - 1 or nums[m] > nums[m+1]:
    		k = m + 1
    		break
    	    elif m == 0 or nums[m] < nums[m-1]:
    		k = m
    		break
    	    if nums[m] > nums[0]:
    		lo = m + 1
    	    else:
    		hi = m - 1
    	i = (bisect.bisect_left(nums[k:] + nums[:k], target) + k) % max(len(nums), 1)
    	return i if nums and nums[i] == target else -1
    

38. Count and Say 1行

class Solution:
    def countAndSay(self, n: int) -> str:
        return '1' * (n is 1) or re.sub(r'(.)\1*', lambda m: str(len(m.group())) + m.group(1), self.countAndSay(n - 1))
  • 正则表达式 re.sub(正则,替换字符串或函数,被替换字符串,是否区分大小写)
  • . 可匹配任意一个除了\n的字符
    (.) 匹配任意一个除了\n的字符并把这个匹配结果放进第一组
    (.)\1 匹配一个任意字符的二次重复并把那个字符放入数组
    (.)\1* 匹配一个任意字符的多次重复并把那个字符放入数组
  • group(default=0)可以取匹配文本 group(1)取第一个括号内的文本

43. Multiply Strings 5行

class Solution:
    def multiply(self, num1: str, num2: str) -> str:
        d = {
   }
        for i, n1 in enumerate(num1[::-1]):
            for j, n2 in enumerate(num2[::-1]): d[i + j] = d.get(i + j, 0) + int(n1) * int(n2)
        for k in [*d]: d[k + 1], d[k] = d.get(k + 1, 0) + int(d[k] * 0.1), d[k] % 10
        return re.sub('^0*', '', ''.join(map(str, d.values()))[::-1]) or '0'
  • 本题的难点在于计算整数的时候不能超过32bits,因此使用竖式计算
  • 我们遍历num1中的每个数字n1,然后带着这个数字遍历num2中的每个数字n2做乘法,所得乘积放进 d 中相应的位置然后逐位计算结果
  • i + j 正好对应俩个数字相乘后所在的位置,比如 0 + 0 就应该是个位, 0 + 1 就是十位, 1 + 1 百位。这里所说的位置可以参考这篇博客中的过程图
  • 若完全不想使用int()可以参考:
    class Solution:
        def multiply(self, num1: str, num2: str) -> str:
    	d = {
         }
    	for i, n1 in enumerate(num1[::-1]):
    	    for j, n2 in enumerate(num2[::-1]):
    		d[i + j] = d.get(i + j, 0) + (ord(n1) - 48) * (ord(n2) - 48)
    	for k in [*d]:
    	    d[k + 1], d[k] = d.get(k + 1, 0) + math.floor(d[k] * 0.1), d[k] % 10
    	return re.sub('^0*', '', ''.join(map(str, d.values()))[::-1]) or '0'
    

46. Permutations 1行

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        return [[n] + sub for i, n in enumerate(nums) for sub in self.permute(nums[:i] + nums[i+1:])]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值