leetcode刷题


前言

    算法小白初入leetcode。本文主要记录个人在leetcode上使用python解题的思路和过程,如果有更好、更巧妙的的解题方法,欢迎大家在评论区给出代码或思路。🚀
在这里插入图片描述


两数之和

  • 题目描述

在这里插入图片描述

1️⃣ 暴力for循环

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        length = len(nums)
        for x in range(length):
            for y in range(x+1,length):
                if (nums[x] + nums[y]) == target:
                    return [x,y]

在这里插入图片描述

2️⃣ 解法2

  • 假设我们将列表中所有元素都减去目标值的一半得到新列表,这样的话,如果我们得到两个及以上的 0 0 0,那么 0 0 0所在的索引位置就是答案;否则,两个值一定是一正一负,所以将列表再分成只包含正数和只包含负数,然后从长度更短的那个列表开始进行遍历,得到相应的索引位置即可。
class Solution:
    def twoSum(self , nums: List[int], target: int) -> List[int]:
        num1 = list(map(lambda x: x - target/2, nums))
        
        num2 = list(filter(lambda x: x < 0, num1))
        num3 = list(filter(lambda x: x >= 0, num1))

        if num3.count(0) >= 2:
            return [num1.index(0) , len(num1[:num1.index(0)+1]) + num1[(num1.index(0)+1):].index(0)]
        else:
            for i in (num2 if len(num2) < len(num3) else num3):
                if -i in (num3 if len(num2) < len(num3) else num2):
                    return [num1.index(i),num1.index(-i)]
                    break

在这里插入图片描述

3️⃣ 构建哈希表

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        a = {}
        for i , num in enumerate(nums):
            if (target - num) in a:
                return [a[target - num] , i]
            a[nums[i]] = i
        return []

在这里插入图片描述

两数相加

在这里插入图片描述

1️⃣ 链表 → \xrightarrow{} 列表 → \xrightarrow{} 链表

  • 对于初入leetcode的小白来说,不懂链表,只懂列表🤷‍♂️,所以当做列表来处理并按照示例的步骤解题即可。
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
def to_list(head):
    # 初始化一个空列表
    result = []
    # 从头节点开始遍历链表
    current = head
    while current:
        # 将当前节点的值添加到列表中
        result.append(current.val)
        # 移动到下一个节点
        current = current.next
    return result
def list_to_linked_list(nums):
    # 初始化一个空的头节点
    dummy_head = ListNode()
    # 初始化当前节点为头节点
    current = dummy_head
    # 遍历列表中的每个元素
    for num in nums:
        # 创建一个新的节点,并将当前节点的下一个节点指向新节点
        current.next = ListNode(num)
        # 将当前节点指向新节点
        current = current.next
    # 返回头节点的下一个节点(第一个真正的节点)
    return dummy_head.next

class Solution:
    def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        # 将两个列表中的元素转换为字符串,并拼接成一个字符串,最后转换成整数
        l1_number = int(''.join(map(str, to_list(l1)[::-1])))
        l2_number = int(''.join(map(str, to_list(l2)[::-1])))
        sum = l1_number + l2_number
        result = list(map(int, list(str(sum))))[::-1]
        return list_to_linked_list(result)

在这里插入图片描述

2️⃣使用链表的特性来解决

  • 对于我这种算法小法来说,首先需要了解一些链表的相关知识:Python 中的链表 或者 链表
    根据题目中已给的ListNode就可以知道,一个链表的创建过程如下:(后面将链表中所有元素打印出来)
    在这里插入图片描述
  • 思路:对两个链表从头结点开始同步遍历,用一个变量val记录两个节点的和,另外一个变量carry记录进位的值,然后不断指向下一个节点,直到满足终止条件。按照该思路,两个链表的长度共有两种情形:
    • 两个一样长:遍历的节点完全是同步的,此时终止条件是两个链表的指针都指向空并且进位上的值等于0
    • 一长一短:该情况下由于短的链表先遍历结束,所以需要将短的链表的节点“补上0”,和长的链表对齐。
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        res_list = []  # 用于存储结果的列表
        res = None     # 用于存储结果的链表

        val = 0   # 用于存储当前位的和
        carry = 0 # 用于存储进位的数值
        current1 = l1
        current2 = l2
        while (current1 or current2 or carry):   # 当两个链表的指针都为空 并且 进位等于0时,结束循环;
            ## 如果短的链表遍历完了,将其指针指向0
            if not current1:
                current1 = ListNode(0)
            if not current2:
                current2 = ListNode(0)

            sum = current1.val + current2.val + carry    # 计算当前位的和
            val = sum % 10
            carry = sum // 10
            res_list.append(val)
            current1 = current1.next
            current2 = current2.next    

        for i in range(len(res_list)):
            if i == 0:
                res = ListNode(res_list[-1 - i])
            else:
                res = ListNode(res_list[-1 - i], res)
        return res

在这里插入图片描述
速度反而变慢了🤦‍♂️

  • 由于上面代码还是存在列表和链表之间的转换,导致效率并不高。既然都已经考虑用链表的特性来解决了,所以对上面代码再优化一下,直接使用链表存储结果。
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        # 初始化结果链表的头节点和当前节点
        dummy_head = ListNode()
        current = dummy_head

        val = 0   # 用于存储当前位的和
        carry = 0 # 用于存储进位的数值
        current1 = l1
        current2 = l2
        while (current1 or current2 or carry):   # 当两个链表的指针都为空 并且 进位等于0时,结束循环;
            # 如果短的链表遍历完了,将其指针指向0
            if not current1:
                current1 = ListNode(0)
            if not current2:
                current2 = ListNode(0)

            sum = current1.val + current2.val + carry    # 计算当前位的和
            val = sum % 10
            carry = sum // 10

            current.next = ListNode(val)  # 创建新的节点,并将其作为当前节点的下一个节点
            current = current.next  # 将当前节点指向新节点

            current1 = current1.next
            current2 = current2.next    

        return dummy_head.next  # 返回结果链表的头节点的下一个节点(第一个真正的节点)

在这里插入图片描述

无重复字符的最长子串

在这里插入图片描述

1️⃣暴力解法

  • 最大的子串长度开始遍历所有子串,依次判断是否满足条件,一旦满足条件退出循环,输出最长子串长度。
def condition(s: str) -> bool:   # 判断子串是否重复,True为无重复
    return len(set(s)) == len(s)

def sub_of_s(s: str, n: int) -> str:   # 返回一个字符串指定长度的所有子串
    return [s[i:i+n] for i in range(len(s) - n + 1)]

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        n = len(s)  
        result = 0
        while n > 0:
            for i in sub_of_s(s, n):
                if condition(i):
                    result = n
                    break
            n -= 1
            if result != 0:
                break 
        return result
  • 但是在面对一些长度很长的字符串时出现超时了🤦‍♂️

在这里插入图片描述

2️⃣ 微调第一种方法

  • 知道第一张方法效率不高,但是没想到直接会超时了,看看是否能优化一下。上面是从最大的子串长度开始遍历的,这里直接设置的是字符串的长度,但是仔细一想会发现,既然是不重复的子串长度,那么最长的长度的上界只能是所有字符类别的数目,而不是整个字符串的长度。
def condition(s: str) -> bool:   # 判断子串是否重复,True为无重复
    return len(set(s)) == len(s)

def sub_of_s(s: str, n: int) -> str:   # 返回一个字符串指定长度的所有子串
    return [s[i:i+n] for i in range(len(s) - n + 1)]

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        n = len(set(s))   			# 最长不重复子串长度的上界
        result = 0
        while n > 0:
            for i in sub_of_s(s, n):
                if condition(i):
                    result = n
                    break
            n -= 1
            if result != 0:
                break 
        return result

在这里插入图片描述

3️⃣滑动窗口+哈希表

  • 解法参考:滑动窗口+哈希表,图文并茂,可以说非常详细,容易理解了。但是少了一个条件判断,那就是:当长度值取到最大(也就是所有字符的类别数目)时,就可以直接退出循环不同遍历了,因为只需要确定长度即可。加上之后,时间确实更快了一点。
class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:

        dic = {}
        res = 0
        i = -1
        for j in range(len(s)):
            if s[j] in dic:
                i = max(dic[s[j]], i) # 更新左指针 i
            dic[s[j]] = j # 哈希表记录
            res = max(res, j - i) # 更新结果

        return res

在这里插入图片描述

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        max_length = len(set(s))    # 最长的长度
        dic = {}
        res = 0
        i = -1
        for j in range(len(s)):
            if s[j] in dic:
                i = max(dic[s[j]], i) # 更新左指针 i
            dic[s[j]] = j # 哈希表记录
            res = max(res, j - i) # 更新结果
            if res == max_length:
                break
        return res

在这里插入图片描述

最长回文子串

在这里插入图片描述

1️⃣暴力解法

  • 同前面一个问题中的解法一,更换一下判断条件即可,没什么可说的
def condition(s:str)  -> bool:   #判断子串是否是回文子串
    return s == s[::-1]
def sub_of_s(s: str, n: int) -> str:   # 返回一个字符串指定长度的所有子串
    return [s[i:i+n] for i in range(len(s) - n + 1)]
class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)   			
        result = None
        while n > 0:
            for i in sub_of_s(s, n):
                if condition(i):
                    result = i
                    break
            n -= 1
            if result != None:
                break 
        return result

在这里插入图片描述

  • 对于上面算法,注意到在遍历字符串指定长度的所有子串时会非常耗时,我们可以简单做个筛选:当开头字符和结尾字符不一致时,可以直接pass掉。
def condition(s:str)  -> bool:   #判断子串是否是回文子串
    return s == s[::-1]
def sub_of_s(s: str, n: int) -> str:   # 进行初步的筛选,并返回一个字符串指定长度的所有可能的回文子串
    res = []
    for i in range(len(s) - n + 1):
        if s[i] == s[i+n-1]:
            res.append(s[i:i+n])
    return  res
class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)   			
        result = None
        while n > 0:
            for i in sub_of_s(s, n):
                if condition(i):
                    result = i
                    break
            n -= 1
            if result != None:
                break 
        return result

在这里插入图片描述

2️⃣解法2

  • 因为求解的是最长的回文子串,所以上面是从可能的最大长度的子串开始往下遍历,一旦符合回文串的条件,即可退出循环,输出结果。
  • 还可以按照这个思路,不过遍历的不是整个子串,而是对称轴的位置,因为回文子串一定是对称的。并且对于任意一个字符串,包含的所有回文子串当中,其长度越长,对称轴肯定是越靠近整个字符串的中心位置的,当整个字符串刚好是一个回文串时,对称轴刚好是中心位置。
  • 按照以上思路,可以将对称轴从中心位置开始,按照左右来回横跳的方式一直遍历到首尾字符,只要中间过程中出现了回文串(判断对称轴左右侧字符是否满足条件),输出即可,这样应该就可以尽最大概率先找到最长的回文串;
  • 但是上面过程存在一个问题,比如在处理abbe时(为区分,假设第一个是b1,第二个是b2),那么依次判断ab1b2eab2b1eab2b2e发现都不满足条件直接输出空了,但是实际上是存在bb这个回文串的;因此,对于每个对称轴,判断时还需要不断砍去首尾字符,避免遗漏掉这种情况。过程例子如下:在这里插入图片描述
  • 代码:
import math
class Solution:
    def longestPalindrome(self, s: str) -> str:
        res = None
        res1 = s[0]
        length = len(s)
        
        i = (length - 1)/2   # 对称轴初始索引位置
        n = 1   
        T = -1  # 用于对称轴左右跳转
        while i >= 0:
            length_half = math.ceil(min(i , math.ceil(length - 1 - i)))    # 此时能够取到的最长回文子串的 1/2 长度
            s_1 = s[math.ceil(i) - length_half : math.ceil(i)]   # 子串的一半
            s_2 = s[int(i+1) : int(i+1)+length_half] # 子串的另一半
            if s_1 == s_2[::-1]: # 如果去掉这两行代码,最终速度会慢10倍!
                break
            
            for j in range(1,length_half):
                s_1 = s[math.ceil(i) - length_half + j : math.ceil(i)]   # 子串的一半
                s_2 = s[int(i+1) : int(i+1)+length_half - j] # 子串的另一半
                if s[math.ceil(i)- 1] !=  s[int(i+1)]: 
                    break
                if s_1 == s_2[::-1]:
                    if '5' == str(i)[-1]:
                        res1 = s_1 + s_2   if len(s_1 + s_2) > len(res1) else res1
                    else:
                        res1 = s_1 + s[int(i)] +s_2 if len(s_1 + s[int(i)] +s_2) > len(res1) else res1
                    break

            i += n*0.5*T
            n += 1
            T = -T
        ## 判断当前对称轴所在位置,如果是某个字符上,那么要加上这个字符;否则不用
        if '5' == str(i)[-1]:
            res = s_1 + s_2   if len(s_1 + s_2) > len(res1) else res1
        else:
            res = s_1 + s[int(i)] +s_2 if len(s_1 + s[int(i)] +s_2) > len(res1) else res1
        return res

又快了一些些🐱‍🏍在这里插入图片描述

整数反转

在这里插入图片描述

1️⃣常规解法

class Solution:
    def reverse(self, x: int) -> int:

        sign = -1 if x < 0 else 1
        reversed_str = str(abs(x))[::-1]
        reversed_int = int(reversed_str) * sign

        if reversed_int < -2**31 or reversed_int >2**31-1:
            return 0
        else :
            return reversed_int

在这里插入图片描述

  • 思路很简单,从字符串的角度考虑即可。但是,如果仅仅将上面的判断条件换一种方式,最终执行用时居然差异会这么大!不清楚这里的原因是什么,有没有人可以解答一下🍳
class Solution:
    def reverse(self, x: int) -> int:

        sign = -1 if x < 0 else 1
        reversed_str = str(abs(x))[::-1]
        reversed_int = int(reversed_str) * sign

        if -2**31 <= reversed_int <= 2**31 - 1:
            return reversed_int
        else :
            return 0

在这里插入图片描述

总结

算法小白初入leetcode,期待给出更精妙的算法🚀🚀🚀

  • 28
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值