LeetCode力扣刷题笔记(1)

每日刷2道leetcode算法题,在博客分享一些题目的题解和总结,愿与君共进步!

1.两数之和

1.初步暴力解:

class Solution:
    def twoSum(self, nums, target):
        n=len(nums)
        for i in range(n):
            for j in range(i+1,n):
                if nums[i]+nums[j]==target:
                    return [i,j]

执行用时:3372 ms,内存消耗:15.5 MB
2.换个思路,第二次循环的时候直接在列表里搜索 t a r g e t − n u m [ i ] target-num[i] targetnum[i],这样可以减少一次循环:

class Solution:
    def twoSum(self, nums, target):
        n=len(nums)
        for i in range(n):
            if (target-nums[i]) in nums:
                j = nums.index(target-nums[i])
                if j != i:
                    return [i,j]

执行用时:736 ms,内存消耗:15.3 MB
3.进一步优化上述解法, n u m 2 num2 num2的查找并不需要每次从 n u m s nums nums查找一遍,只需要从 n u m 1 num1 num1位置之前或之后查找即可。但为了方便 i n d e x index index这里选择从 n u m 1 num1 num1位置之前查找

class Solution:
    def twoSum(self, nums, target):
        n=len(nums)
        for i in range(n):
            if (target-nums[i]) in nums[:i]:
                j = nums.index(target-nums[i])
                if j != i:
                    return [i,j]

执行用时:500 ms,内存消耗:15.6 MB
4.采用哈希表查询,用字典的形式存储与查询

class Solution:
    def twoSum(self, nums, target):
        hashmap={}
        for i,n in enumerate(nums):
            hashmap[n] = i
        for i,n in enumerate(nums):
            j = hashmap.get(target - n)
            if j is not None and i!=j:
                return [i,j]

执行用时:36 ms,内存消耗:16 MB

2.两数相加

# 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: ListNode, l2: ListNode) -> ListNode:
        # 初始化个位节点,先不做进位
        newPoint = ListNode(l1.val + l2.val)

        # rt用来作为最后return的节点,tp用来遍历节点
        rt, tp = newPoint, newPoint

        # l1,l2只要后面还有节点,就继续往后遍历;或者新链表还需要继续往后进位
        while (l1 and (l1.next != None)) or (l2 and (l2.next != None)) or (tp.val > 9):
            l1, l2 = l1.next if l1 else l1, l2.next if l2 else l2
            tmpsum = (l1.val if l1 else 0) + (l2.val if l2 else 0)
            # 计算新链表下个节点的值(当前节点的进位+当前l1 l2的值之和),先不做进位
            tp.next = ListNode(tp.val//10 + tmpsum)
            # 新链表当前节点的值取个位
            tp.val %= 10
            # 新链表往后遍历一个节点
            tp = tp.next
        return rt

执行用时:68 ms,内存消耗:14.9 MB
换个写法

class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        dummy = p = ListNode(None)
        s = 0

        while l1 or l2 or s != 0:
            s += (l1.val if l1 else 0) + (l2.val if l2 else 0)
            p.next = ListNode(s % 10)
            p = p.next
            if l1: l1 = l1.next
            if l2: l2 = l2.next
            s = s // 10

        return dummy.next

3.无重复字符的最长子串

哈希表+双指针

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        dic, res, i = {}, 0, -1   # i是左指针
        for j in range(len(s)):  # j是右指针
            if s[j] in dic:     # 若当前元素在之前出现过,更新左指针
                # 当之前出现的元素在左右指针中间,左指针更新为之前元素下标,若不在中间,左指针不变
                i = max(i, dic[s[j]]) 
            dic[s[j]] = j    # 将当前元素加入哈希表中
            res = max(res, j - i)   
        return res

执行用时:64 ms,内存消耗:15.1 MB

5. 最长回文子串

给你一个字符串 s s s,找到 s s s中最长的回文子串。

动态规划(填dp表、当前ij状态、过去ij状态、如何联合得到输出、边界条件)
1.定义状态:题目让我们求什么,就把什么设置为状态
题目求s中最长的回文子串,那就判断所有子串是否为回文子串,选出最长的
因此: d p [ i ] [ j ] dp[i][j] dp[i][j]表示 s [ i : j + 1 ] s[i:j+1] s[i:j+1]是否为回文子串(这里+1是为了构造闭区间)

2.状态转移方程:对空间进行分类讨论(当前ij状态、过去ij状态 如何联合得到输出)
当前ij状态:头尾必须相等( s [ i ] = = s [ j ] s[i]==s[j] s[i]==s[j]
过去ij状态:去掉头尾之后还是一个回文( d p [ i + 1 ] [ j − 1 ] i s T r u e dp[i+1][j-1] is True dp[i+1][j1]isTrue
边界条件:只要是找过去ij状态的时候,就会涉及边界条件(即超出边界情况处理)
当i==j时一定是回文
j-1-(i+1)<=0,即j-i<=2时,只要当 s [ i ] = = s [ j ] s[i]==s[j] s[i]==s[j]时就是回文,不用判断 d p [ i + 1 ] [ j − 1 ] dp[i+1][j-1] dp[i+1][j1], d p [ i ] [ j ] dp[i][j] dp[i][j]为截取的子串

3.初始状态:这里已经直接判断 j − i < = 2 j-i<=2 ji<=2的情况了,因此用不到初始状态,可以不设

4.输出内容:每次发现新回文都比较一下长度,记录i与长度

5.优化空间提速

class Solution:
    def longestPalindrome(self, s: str) -> str:
        size = len(s)
        # 特殊处理
        if size == 1:
            return s
        # 创建动态规划dp表
        dp = [[False for _ in range(size)] for _ in range(size)]
        # 初始长度为1,这样万一不存在回文,就返回第一个值(初始条件设置的时候一定要考虑输出)
        max_len = 1
        start = 0
        for j in range(1,size):
            for i in range(j):
                # 边界条件:
                # 只要头尾相等(s[i]==s[j])就能返回True
                if j-i<=2:
                    if s[i]==s[j]:
                        dp[i][j] = True
                        cur_len = j-i+1
                # 状态转移方程 
                # 当前dp[i][j]状态:头尾相等(s[i]==s[j])
                # 过去dp[i][j]状态:去掉头尾之后还是一个回文(dp[i+1][j-1] is True)
                else:
                    if s[i]==s[j] and dp[i+1][j-1]:
                        dp[i][j] = True
                        cur_len = j-i+1
                # 出现回文更新输出
                if dp[i][j]:
                    if cur_len > max_len:
                        max_len = cur_len
                        start = i

        return s[start:start+max_len]

647. 回文子串

法一:暴力法。暴力法主要浪费在判断回文串上,不能有效利用同中心的回文串的状态,简单来说就是此时我们假设前面的子串 s [ j , i ] s[j,i] s[j,i]是回文串,那么,子串 s [ j − 1 , i + 1 ] s[j-1,i+1] s[j1,i+1]也有可能是回文串,不难想出当且仅当子串 s [ j , i ] s[j,i] s[j,i]是回文串且 s [ j − 1 ] = s [ i + 1 ] s[j-1]=s[i+1] s[j1]=s[i+1]时,子串 s [ j − 1 , i + 1 ] s[j-1,i+1] s[j1,i+1]也是回文串,于是我们可以通过数组保存子串是否是回文串,然后通过递推上一次的状态,得到下一次的状态,属于动态规划的解法,令 d p [ j ] [ i ] dp[j][i] dp[j][i]表示子串 s [ j , i ] s[j,i] s[j,i]是否是回文串,状态转移如下:

1当 i = j i=j i=j时,单个字符肯定是回文串,可以看成奇数回文串的起点
2当 s [ i ] = s [ j ] s[i]=s[j] s[i]=s[j] i − j = 1 i-j=1 ij=1,则 d p [ j ] [ i ] dp[j][i] dp[j][i]是回文串,可以看成偶数回文串的起点
3当 s [ i ] = s [ j ] s[i]=s[j] s[i]=s[j] d p [ j + 1 ] [ i − 1 ] dp[j+1][i-1] dp[j+1][i1]是回文串,则 d p [ j ] [ i ] dp[j][i] dp[j][i]也是回文串
4其他情形都不是回文串

其中条件1、2、3是可以合并的:
s [ i ] = s [ j ] s[i]=s[j] s[i]=s[j]时, d p [ j + 1 ] [ i − 1 ] dp[j+1][i-1] dp[j+1][i1]是回文串或 i − j < 2 i-j<2 ij<2,则 d p [ j ] [ i ] dp[j][i] dp[j][i]也是回文串

    class Solution:
        def countSubstrings(self, s: str) -> int:
            res = 0
            n = len(s)
            dp = [[0] * n for _ in range(n)]
            for i in range(n):
                for j in range(i + 1):
                    if s[i] == s[j] and (i - j < 2 or dp[j + 1][i - 1]):
                        dp[j][i] = 1
                        res += 1
            return res

复杂度分析

时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( n 2 ) O(n^2) O(n2)

法二:动态规划的思想优点是比较好想,缺点也很明显,空间复杂度比较高,那么回顾一下,我们只是需要得到同中心的回文子串数目,显然只要我们找到子串的中心,然后使用两个指针不断向两端延伸即可,也就是中心扩展法,这样空间复杂度就降到 O ( 1 ) O(1) O(1)了。找中心很简单,字符串中的元素逐一遍历即可,但是要注意是奇数还是偶数回文串,奇数回文串中心只有一个,而偶数回文串中心有两个,我们事先不知道奇偶,解决办法可以参考官方题解的,也可以都计算一遍,这样比较好想。

class Solution:
        def countSubstrings(self, s: str) -> int:
            n = len(s)
            self.res = 0
            def helper(i,j):
                while i >= 0 and j < n and s[i] == s[j]:
                    i -= 1
                    j += 1
                    self.res += 1
            for i in range(n):
                helper(i,i)
                helper(i,i+1)
            return self.res

复杂度分析

时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( 1 ) O(1) O(1)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

richardxp888

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值