LeetCode系列(python解法) 1、2、3

这篇博客详细介绍了LeetCode中三道经典算法题目的解法,包括1. 两数之和(使用哈希表实现的一次遍历解决方案),2. 两数相加(链表相加),3. 无重复字符的最长子串(滑动窗口法)。文章深入探讨了时间复杂度和空间复杂度,并提供了具体的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目及解题思路来源:LeetCode

1. 两数之和

问题: 给定一个整数数组 n u m s nums nums和一个目标值 t a r g e t target target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9,所以返回 [0, 1]

解决方法
1. 暴力法
  遍历每个元素 x x x,并查找是否存在一个值与 t a r g e t − x target-x targetx相等的目标元素

class Solution:
	    def twoSum(self, nums, target):
	        """
	        :type nums: List[int]
	        :type target: int
	        :rtype: List[int]
	        """
	        for i in range(len(nums)):
	            for j in range(i+1, len(nums)):
	                if nums[j]==target-nums[i]:
	                    return [i, j]
	        return None

复杂度分析:

  • 时间复杂度: O ( n 2 ) O(n^{2}) O(n2),对于每个元素,我们试图通过遍历数组的其余部分来寻找它所对应的目标元素,这将耗费 O ( n ) O(n) O(n)的时间。因此时间复杂度为 O ( n 2 ) O(n^{2}) O(n2)
  • 空间复杂度: O ( 1 ) O(1) O(1)

2. 两遍哈希表
  为了对时间复杂度进行优化,我们需要一种更有效的方法检查数组中是否存在目标元素,我们需要找出它的索引。保持数组中的每个元素与其索引相互对应的最好方法是什么?哈希表。
  通过以空间换取速度的方式,我们可以将查找时间从 O ( n ) O(n) O(n)降到 O ( 1 ) O(1) O(1)。哈希表正是为此目的而构建的,它支持近似恒定的时间进行快速查找。“近似”原因是一旦出现冲突,查找用时可能会退化到 O ( n ) O(n) O(n)。但是只要仔细地挑选哈希函数,在哈希表中进行查找的用时应当被摊销为 O ( 1 ) O(1) O(1)
  两次迭代。第一次迭代,将每个元素的值和它的索引添加到表中。然后,在第二次迭代中,检查每个元素对应的目标元素 ( t a r g e t − n u m s [ i ] ) (target-nums[i]) (targetnums[i])是否存在表中。注意,该元素不能是 n u m s [ i ] nums[i] nums[i]本身!

# 利用字典实现哈希表
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        hashmap = {}
        for index, num in enumerate(nums):
            hashmap[num] = index
        
        for index, num in enumerate(nums):
            j = hashmap.get(target - num)
            if j is not None and index != j:
                return [index, j]         
        return None

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n),我们把包含有 n n n个元素的列表遍历两次。由于哈希表将查找时间缩短到 O ( 1 ) O(1) O(1),所以时间复杂度为 O ( 1 ) O(1) O(1)
  • 空间复杂度: O ( n ) O(n) O(n),所需的额外空间取决于哈希表中存储的元素数量,该表中存储了 n n n个元素。

3. 一遍哈希表
  事实证明,我们可以一次完成。再进行迭代并将元素插入到表中的同时,我们还会回头检查表中是否已经存在当前元素所对应的目标元素。如果它存在,那我们已经找到了对应解,并立即将其返回。

# 利用字典实现哈希表
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        hashmap = {}
        for index, num in enumerate(nums):
            another_num = target - num
            if another_num in hashmap:
                return [hashmap[another_num], index]
            hashmap[num] = index
        return None

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n),我们只遍历了包含有 n n n个元素的列表一次。在表中进行的每次查找只花费 O ( 1 ) O(1) O(1)的时间。
  • 空间复杂度: O ( n ) O(n) O(n),所需的额外空间取决于哈希表中存储的元素数量,该表中存储了 n n n个元素。
2. 两数相加

问题: 给出两个非空的链表用来表示两个非负的整数。其中,它们各自的位数是按照逆序的方式存储的,并且它们的每个节点只能存储 一位 数字。如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 0 -> 8
原因: 342 + 465 = 807

解决方法:

测试用例说明
l 1 = [ 0 , 1 ] , l 2 = [ 0 , 1 , 2 ] l1=[0,1], l2=[0,1,2] l1=[0,1],l2=[0,1,2]当一个列表比另一个列表长时
l 1 = [ ] , l 2 = [ 0 , 1 ] l1=[], l2=[0,1] l1=[],l2=[0,1]当一个列表为空时,即出现空列表
l 1 = [ 9 , 9 ] , l 2 = [ 1 ] l1=[9,9], l2=[1] l1=[9,9],l2=[1]求和运算最后可能出现额外的进位,这一点很容易忘记
class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        l_sum = ListNode(0)
        p = l1
        q = l2
        curr = l_sum
        carry = 0
        while(p or q):
            x = p.val if p else 0
            y = q.val if q else 0
            node_sum = x + y + carry
            carry = node_sum // 10
            curr.next = ListNode(node_sum % 10)
            curr = curr.next
            p = p.next if p else None
            q = q.next if q else None
        if carry>0:
            curr.next = ListNode(carry)
        return l_sum.next

复杂度分析:

  • 时间复杂度: O ( m a x ( m , n ) ) O(max(m,n)) O(max(m,n)),假设 m m m n n n分别表示 l 1 l1 l1 l 2 l2 l2的长度,上面的算法最多重复 m a x ( m , n ) max(m,n) max(m,n)次。
  • 空间复杂度: O ( m a x ( m , n ) ) O(max(m,n)) O(max(m,n)),新列表的长度最多为 m a x ( m , n ) + 1 max(m,n)+1 max(m,n)+1
3. 无重复字符的最长字串

问题: 给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。

示例:1
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例:2
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是子串的长度,“pwke” 是一个子序列,不是子串。

解决方法
1. 暴力法
  逐个检查所有的子字符串,看它是否不含有重复的字符。
  假设我们有一个函数 boolean allUnique(String substring),如果字符串中的字符都是唯一的,它会返回true,否则会返回false。我们可以遍历给定字符串s的所有可能的字符串并调用函数allUnique。如果事实证明返回值为true,那么我们将会更新无重复字符串的最大长度的答案。
  缺少的部分:
  1. 为了给定字符串的所有子字符串,我们需要枚举它们开始和结束的索引。假设开始和结束的索引分别为 i i i j j j。那么我们有 0 ≤ i &lt; j ≤ n 0\leq i&lt;j\leq n 0i<jn(这里的结束索引 j j j是按惯例排除的)。因此,使用 i i i从0到 n − 1 n-1 n1以及 j j j i + 1 i+1 i+1 n n n的这两个嵌套的循环,我们可以枚举出s的所有子字符串。
  2. 要检查一个字符串是否有重复字符,我们可以使用集合。我们遍历字符串中的所有字符串,并将它们逐个放入set中。在放置一个字符前,我们检查该集合是否已经包含它。如果包含,我们会返回false。循环结束后,我们返回true

#用到python中的set
class Solution:  
    def lengthOfLongestSubstring(self, s: str) -> int:
        len_Substring = 0
        for i in range(len(s)):
            for j in range(i+1, len(s)+1):
                if (len(set(s[i:j])) == len(s[i:j])):
                    len_Substring = max(len_Substring, j-i)
        return len_Substring

复杂度分析:

  • 时间复杂度(个人理解): O ( n 3 ) O(n^{3}) O(n3)
     要验证索引范围在 [ i , j ) [i,j) [i,j)内的字符是否都是唯一的,我们需要检查范围中的所有字符。python切片花费 O ( j − i ) O(j-i) O(ji)的时间。
     对于给定的i,对于所有 j ∈ [ i + 1 , n ] j\in[i+1, n] j[i+1,n]所耗费的时间总和为: ∑ i + 1 n O ( j − 1 ) \sum_{i+1}^nO(j-1) i+1nO(j1)
     因此,执行所有步骤耗去的时间总和为: O ( ∑ i = 0 n − 1 ( ∑ j = i + 1 n ( j − i ) ) ) = O ( ∑ i = 0 n − 1 ( 1 + n − i ) ( n − i ) 2 ) = O ( n 3 ) O\Big(\sum_{i=0}^{n-1}\Big(\sum_{j=i+1}^n(j-i)\Big)\Big)=O\Big(\sum_{i=0}^{n-1}\frac{(1+n-i)(n-i)}{2}\Big)=O(n^3) O(i=0n1(j=i+1n(ji)))=O(i=0n12(1+ni)(ni))=O(n3)
  • 空间复杂度: O ( m i n ( n , m ) ) O(min(n,m)) O(min(n,m)),我们需要 O ( k ) O(k) O(k)的空间来检查子字符串中是否有重复字符,其中 k k k表示Set的大小。而Set的大小取决于字符串n的大小以及字符集/字母m的大小。

2. 滑窗法
什么是滑动窗口?
  其实就是一个队列,比如例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列!
如何移动?
  我们只要把队列的左边的元素移出就行了,直到满足题目要求!一直维持这样的队列,找出队列出现最长的长度时候,求出解!

#利用python中的set进行判断
class Solution:  
    def lengthOfLongestSubstring(self, s: str) -> int:
        left = 0
        lookup = set()
        max_len = 0
        cur_len = 0
        for i in range(len(s)):
            cur_len += 1
            while s[i] in lookup:
                lookup.remove(s[left])
                left += 1
                cur_len -= 1
            if cur_len > max_len:
                max_len = cur_len
            lookup.add(s[i])
        return max_len

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n),只进行一次的遍历。
  • 空间复杂度: O ( m , n ) O(m,n) O(m,n)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值