LeetCode1-5


LeetCode: https://leetcode-cn.com/problemset/all/.

1.两数之和

难度:简单
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。

示例1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]:
示例2:
输入:nums = [3,3], target = 6
输出:[0,1]

方法1(暴力求解):
时间复杂度: O ( N 2 ) O(N^2) O(N2),空间复杂度: O ( 1 ) O(1) O(1)

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

方法2(哈希表):
创建一个哈希表,对于每一个 num,我们首先查询哈希表中是否存在 target - num,然后将 num 插入到哈希表中,即可保证不会让 num 和自己匹配。
时间复杂度: O ( N ) O(N) O(N),空间复杂度: O ( N ) O(N) O(N)

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

2.两数相加

难度:中等
给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照逆序的方式存储的,并且每个节点只能存储一位数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字0之外,这两个数都不会以0开头。

示例1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]

解决方法:
由于输入的两个链表都是逆序存储数字的位数的,因此两个链表中同一位置的数字可以直接相加。
我们同时遍历两个链表,逐位计算它们的和,并与当前位置的进位值相加。具体而言,如果当前两个链表处相应位置的数字为 n 1 , n 2 n_1,n_2 n1,n2进位值为 c a r r y carry carry,则它们的和为 n 1 + n 2 + c a r r y n_1+n_2+carry n1+n2+carry;其中,答案链表处相应位置的数字为 ( n 1 + n 2 + c a r r y )   m o d   10 (n_1+n_2+carry) \bmod 10 (n1+n2+carry)mod10,而新的进位值为 ⌊ n 1 + n 2 + c a r r y 10 ⌋ \lfloor\dfrac{n_1+n_2+carry}{10}\rfloor 10n1+n2+carry
此外,如果链表遍历结束后,有 c a r r y > 0 carry>0 carry>0,还需要在答案链表的后面附加一个节点,节点的值为 c a r r y carry carry
时间复杂度: O ( m a x ( m , n ) ) O(max(m,n)) O(max(m,n)),空间复杂度: O ( 1 ) O(1) O(1)

# 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:
        head = curr = ListNode()
        carry = val = 0
        while carry or l1 or l2:
            val = carry
            if l1:
                val += l1.val
                l1 = l1.next
            if l2:
                val += l2.val
                l2 = l2.next
            carry, val = divmod(val, 10)
            curr.next = ListNode(val)
            curr = curr.next
        if carry > 0:
            curr.next = ListNode(carry)
        return head.next

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

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

示例1:
输入: s = “abcabcbb”
输出: 3 (最长子串“abc”)
示例2:
输入: s = “pwwkew”
输出: 3(最长子串"kew")
示例3:
输入: s = “abcdefagh”
输出: 8(最长子串"bcdefagh")

方法1(暴力求解):
时间复杂度: O ( N 2 ) O(N^2) O(N2),空间复杂度: O ( N ) O(N) O(N)

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        lengthOfSubstring = 0
        lens = len(s)
        for index1, char1 in enumerate(s):
            Substring = ""
            Substring += char1
            for index2, char2 in enumerate(s[index1 + 1:]):
                if char2 in Substring:
                    lengthOfSubstring = max(lengthOfSubstring, len(Substring))
                    break
                else:
                    Substring += char2
                if index2 == lens - index1-2:
                    lengthOfSubstring = max(lengthOfSubstring, len(Substring))
                    break
        return lengthOfSubstring

方法2(滑动窗口):
时间复杂度: O ( N ) O(N) O(N),空间复杂度: O ( ∣ Σ ∣ ) O(|\Sigma|) O(Σ)

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        if not s:return 0
        left = 0
        slidingWin = set()
        lens = len(s)
        maxLen = 0
        curLen = 0
        for i in range(lens):
            curLen += 1
            while s[i] in slidingWin:
                slidingWin.remove(s[left])
                left += 1
                curLen -= 1
            if curLen > maxLen:
                maxLen = curLen
            slidingWin.add(s[i])
        return maxLen

4.寻找两个正序数组的中位数

难度:困难
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数
要求时间复杂度为 l o g ( m + n ) log(m+n) log(m+n)

示例1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
示例3:
输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000
示例4:
输入:nums1 = [], nums2 = [1]
输出:1.00000

方法1(直接求解):

归并后再找中位数。
时间复杂度: O ( N ) O(N) O(N),空间复杂度: O ( 1 ) O(1) O(1)

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        nums1.extend(nums2)
        nums1.sort()
        n = len(nums1)
        if n % 2 == 1 :
            return nums1[n // 2]
        else :
            return (nums1[n // 2] + nums1[n // 2 - 1])/2

方法2(对分查找):

题解:假设两个有序数组分别是A和B。要找到第k个元素,我们可以比较A[k/2−1] 和B[k/2−1],其中/表示整数除法。由于A[k/2−1] 和B[k/2−1] 的前面分别有A[0…k/2−2] 和B[0…k/2−2],即k/2−1 个元素,对于A[k/2−1] 和B[k/2−1] 中的较小值,最多只会有(k/2−1)+(k/2−1)≤k−2个元素比它小,那么它就不能是第k小的数了,则之前的数都可排除。

因此我们有以下三种情况需要特殊处理:

  • 如果A[k/2−1] 或者B[k/2−1] 越界,那么我们可以选取对应数组中的最后一个元素。在这种情况下,我们必须根据排除数的个数减少k 的值,而不能直接将k减去k/2。
  • 如果一个数组为空,说明该数组中的所有元素都被排除,我们可以直接返回另一个数组中第k 小的元素。
  • 如果k=1,我们只要返回两个数组首元素的最小值即可。

时间复杂度: O ( l o g ( m + n ) ) O(log(m+n)) O(log(m+n)),空间复杂度: O ( 1 ) O(1) O(1)

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        def getKthNum(k):#找到第k个数
            index1, index2 = 0, 0
            while True:
                # 特殊情况
                if index1 == m:
                    return nums2[index2 + k - 1]
                if index2 == n:
                    return nums1[index1 + k - 1]
                if k == 1:
                    return min(nums1[index1], nums2[index2])
                # 正常情况
                newIndex1 = min(index1 + k // 2 - 1, m - 1)
                newIndex2 = min(index2 + k // 2 - 1, n - 1)
                if nums1[newIndex1] <= nums2[newIndex2]:
                    k -= newIndex1 - index1 + 1
                    index1 = newIndex1 + 1
                else:
                    k -= newIndex2 - index2 + 1
                    index2 = newIndex2 + 1

        m, n = len(nums1), len(nums2)
        totalLength = m + n
        if totalLength % 2 == 1:
            return getKthNum((totalLength + 1) // 2)
        else:
            return (getKthNum(totalLength // 2) + getKthNum(totalLength // 2 + 1)) / 2

方法3(划分数组):
题解
时间复杂度: O ( l o g ( m i n ( m , n ) ) ) O(log(min(m,n))) O(log(min(m,n))),空间复杂度: O ( 1 ) O(1) O(1)

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        if len(nums1) > len(nums2):
            return self.findMedianSortedArrays(nums2, nums1)

        infinty = 2**40
        m, n = len(nums1), len(nums2)
        left, right = 0, m
        # median1:前一部分的最大值
        # median2:后一部分的最小值
        median1, median2 = 0, 0

        while left <= right:
            # 前一部分包含 nums1[0 .. i-1] 和 nums2[0 .. j-1]
            # // 后一部分包含 nums1[i .. m-1] 和 nums2[j .. n-1]
            i = (left + right) // 2
            j = (m + n + 1) // 2 - i

            # nums_im1, nums_i, nums_jm1, nums_j 分别表示 nums1[i-1], nums1[i], nums2[j-1], nums2[j]
            nums_im1 = (-infinty if i == 0 else nums1[i - 1])
            nums_i = (infinty if i == m else nums1[i])
            nums_jm1 = (-infinty if j == 0 else nums2[j - 1])
            nums_j = (infinty if j == n else nums2[j])

            if nums_im1 <= nums_j:
                median1, median2 = max(nums_im1, nums_jm1), min(nums_i, nums_j)
                left = i + 1
            else:
                right = i - 1

        return (median1 + median2) / 2 if (m + n) % 2 == 0 else median1

5.最长回文子串

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

示例1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例2:
输入:s = “cabbad”
输出:“abba”
示例3:
输入:s = “a”
输出:“a”

方法1(动态规划):
我们用 P ( i , j ) P(i,j) P(i,j)表示字符串 s s s的第 i i i j j j个字母组成的串(下文表示成 s [ i , j ] s[i,j] s[i,j])是否为回文串:
P ( i , j ) = { t r u e , s [ i , j ] 是 回 文 串 f a l s e , s [ i , j ] 不 是 回 文 串 或 i > j P(i,j) = \left\{\begin{matrix} true,&s[i,j]是回文串\qquad\qquad \\ false,&s[i,j]不是回文串或i>j \end{matrix}\right. P(i,j)={truefalses[i,j]s[i,j]i>j
那么我们就可以写出动态规划的状态转移方程:
P ( i , j ) = P ( i + 1 , j − 1 ) ∧ ( S i = = S j ) P(i,j)=P(i+1,j-1)\wedge (S_i==S_j) P(i,j)=P(i+1,j1)(Si==Sj)
也就是说,只有 s [ i + 1 : j − 1 ] s[i+1:j−1] s[i+1:j1]是回文串,并且 s s s的第 i i i j j j个字母相同时, s [ i : j ] s[i:j] s[i:j]才会是回文串。
另外动态规划的边界条件:
{ P ( i , i ) = t r u e P ( i , i + 1 ) = ( S i = = S i + 1 ) \left\{\begin{array}{l} P(i,i)=true \\ P(i,i+1)=(S_i==S_{i+1}) \end{array}\right. {P(i,i)=trueP(i,i+1)=(Si==Si+1)
最终的答案即为所有 P ( i , j ) = t r u e P(i,j)=true P(i,j)=true j − i + 1 j−i+1 ji+1(即子串长度)的最大值
时间复杂度: O ( n 2 ) O(n^2) O(n2),空间复杂度: O ( n 2 ) O(n^2) O(n2)

class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        if n < 2:
            return s
        max_len = 1
        begin = 0
        # dp[i][j] 表示 s[i..j] 是否是回文串
        dp = [[False] * n for _ in range(n)]
        for i in range(n):
            dp[i][i] = True
        # 先枚举子串长度
        for L in range(2, n + 1):
            # 枚举左边界,左边界的上限设置可以宽松一些
            for i in range(n):
                # 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
                j = L + i - 1
                # 如果右边界越界,就可以退出当前循环
                if j >= n:
                    break
                if s[i] != s[j]:
                    dp[i][j] = False 
                else:
                    if j - i < 3:
                        dp[i][j] = True
                    else:
                        dp[i][j] = dp[i + 1][j - 1]
                # 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
                if dp[i][j] and j - i + 1 > max_len:
                    max_len = j - i + 1
                    begin = i
        return s[begin:begin + max_len]

方法2(最长公共子串):
将原字符串倒置后求出最长公共子串即可。
时间复杂度: O ( n 2 ) O(n^2) O(n2),空间复杂度: O ( n ) O(n) O(n)

    if not s:
        return ""
    length = len(s)
    sRev = s[::-1]
    maxLen, maxEnd = 0, 0
    arr = [0 for _ in range(length)]
    for i in range(length):
        for j in range(length - 1, 0, -1):
            if sRev[j] == s[i]:
                if i == 0 or j == 0:
                    arr[j] = 1
                else:
                    arr[j] = arr[j - 1] + 1
            else:
                arr[j] = 0
            if arr[j] > maxLen:
                indexBef = length - 1 - j
                if indexBef + arr[j] - 1 == i:
                    maxLen = arr[j]
                    maxEnd = i
    return s[maxEnd - maxLen + 1:maxEnd + 1]

方法3(中心扩展算法):
我们枚举每一种子串长度为 1 或 2 的情况,并从对应的子串开始不断地向两边扩展。如果两边的字母相同,我们就可以继续扩展,例如从 P ( i + 1 , j − 1 ) P(i+1,j−1) P(i+1,j1) 扩展到 P ( i , j ) P(i,j) P(i,j);如果两边的字母不同,我们就可以停止扩展,因为在这之后的子串都不能是回文串了。
时间复杂度: O ( n 2 ) O(n^2) O(n2),空间复杂度: O ( 1 ) O(1) O(1)

class Solution:
    def expandAroundCenter(self, s, left, right):
        while left >= 0 and right < len(s) and s[left] == s[right]:
            left -= 1
            right += 1
        return left + 1, right - 1
        
    def longestPalindrome(self, s: str) -> str:
        start, end = 0, 0
        for i in range(len(s)):
            left1, right1 = self.expandAroundCenter(s, i, i)
            left2, right2 = self.expandAroundCenter(s, i, i + 1)
            if right1 - left1 > end - start:
                start, end = left1, right1
            if right2 - left2 > end - start:
                start, end = left2, right2
        return s[start: end + 1]

方法4(Manacher算法(马拉车算法)):
在这里插入图片描述
如果位置 j j j 的臂长为 l e n g t h length length,并且有 j + l e n g t h > i j + length > i j+length>i
当在位置 i i i 开始进行中心拓展时,我们可以先找到 i i i 关于 j j j 的对称点 2 ∗ j − i 2 * j - i 2ji。那么如果点 2 ∗ j − i 2 * j - i 2ji 的臂长等于 n n n,我们就可以知道,点 i i i 的臂长至少为 m i n ( j + l e n g t h − i , n ) min(j + length - i, n) min(j+lengthi,n)。那么我们就可以直接跳过 i i i i + m i n ( j + l e n g t h − i , n ) i + min(j + length - i, n) i+min(j+lengthi,n) 这部分,从 i + m i n ( j + l e n g t h − i , n ) + 1 i + min(j + length - i, n) + 1 i+min(j+lengthi,n)+1 开始拓展。

如何处理长度为偶数的回文字符串呢?
我们可以通过一个特别的操作将奇偶数的情况统一起来:我们向字符串的头尾以及每两个字符中间添加一个特殊字符 # \# #,比如字符串 a a b a aaba aaba 处理后会变成 # a # a # b # a # \#a\#a\#b\#a\# #a#a#b#a#。那么原先长度为偶数的回文字符串 a a aa aa 会变成长度为奇数的回文字符串 # a # a # \#a\#a\# #a#a#,而长度为奇数的回文字符串 a b a aba aba 会变成长度仍然为奇数的回文字符串 # a # b # a # \#a\#b\#a\# #a#b#a#,我们就不需要再考虑长度为偶数的回文字符串了。
时间复杂度: O ( n ) O(n) O(n),空间复杂度: O ( n ) O(n) O(n)

class Solution:
    def expand(self, s, left, right):
        while left >= 0 and right < len(s) and s[left] == s[right]:
            left -= 1
            right += 1
        return (right - left - 2) // 2

    def longestPalindrome(self, s: str) -> str:
        end, start = -1, 0
        s = '#' + '#'.join(list(s)) + '#'
        arm_len = []
        right = -1
        j = -1
        for i in range(len(s)):
            if right >= i:
                i_sym = 2 * j - i
                min_arm_len = min(arm_len[i_sym], right - i)
                cur_arm_len = self.expand(s, i - min_arm_len, i + min_arm_len)
            else:
                cur_arm_len = self.expand(s, i, i)
            arm_len.append(cur_arm_len)
            if i + cur_arm_len > right:
                j = i
                right = i + cur_arm_len
            if 2 * cur_arm_len + 1 > end - start:
                start = i - cur_arm_len
                end = i + cur_arm_len
        return s[start+1:end+1:2]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LeetCode-Editor是一种在线编码工具,它提供了一个用户友好的界面编写和运行代码。在使用LeetCode-Editor时,有时候会出现乱码的问题。 乱码的原因可能是由于编码格式不兼容或者编码错误导致的。在这种情况下,我们可以尝试以下几种解决方法: 1. 检查文件编码格式:首先,我们可以检查所编辑的文件的编码格式。通常来说,常用的编码格式有UTF-8和ASCII等。我们可以将编码格式更改为正确的格式。在LeetCode-Editor,可以通过界面设置或编辑器设置来更改编码格式。 2. 使用正确的字符集:如果乱码是由于使用了不同的字符集导致的,我们可以尝试更改使用正确的字符集。常见的字符集如Unicode或者UTF-8等。在LeetCode-Editor,可以在编辑器选择正确的字符集。 3. 使用合适的编辑器:有时候,乱码问题可能与LeetCode-Editor自身相关。我们可以尝试使用其他编码工具,如Text Editor、Sublime Text或者IDE,看是否能够解决乱码问题。 4. 查找特殊字符:如果乱码问题只出现在某些特殊字符上,我们可以尝试找到并替换这些字符。通过仔细检查代码,我们可以找到导致乱码的特定字符,并进行修正或替换。 总之,解决LeetCode-Editor乱码问题的方法有很多。根据具体情况,我们可以尝试更改文件编码格式、使用正确的字符集、更换编辑器或者查找并替换特殊字符等方法来解决这个问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值