leetcode刷题

目录

简化路径

基本运算器

旋转链表

反转链表II

随机链表的复制

判断链表是否有环

链表两数相加

爬楼梯

罗马数字转整数

整数转罗马数字

复原IP地址

存在重复元素 II

最长递增子序列

​​​​​找出字符串中第一个匹配项的下标

最长连续序列

快乐数

最长回文子串

反转字符串中的单词

搜索旋转排序数组

全排列

x的平方根

73. 矩阵置零

编辑距离

排列序列

回文链表

二叉树的最大深度

翻转二叉树

相同的树

逆波兰表达式

最小栈

三数之和

有效的括号

字母异位词分组

单词规律

同构字符串

统计优美子数组

爱生气的书店老板

水果成篮(最大滑窗)

串联所有单词的子串

盛水最多的容器

两数之和(输入有序数组)

判断子序列

最长公共前缀

文本左右对齐

最后一个单词的长度

除自身之外数组乘积

O(1) 时间插入、删除和获取随机元素

跳跃游戏

买卖股票的最佳时机

多数元素

删除有序数组中的重复项I + II

数组--移除元素+合并两个有序数组

随机链表的复制

最长交替子数组

队列中可以看到的人数

找出缺失的观测数据

最小覆盖子串

无重复字符的最长子串

长度最小的子数组

二叉树中的最大路径和

k个一组翻转

合并区间+插入区间+汇总区间+射出最少的箭

合并区间        

插入区间

汇总区间

射出最少的箭

买卖股票问题

轮转数组+翻转数组(中等难度)

腐烂的橘子

H指数

接雨水

最大正方形

打家劫舍

打家劫舍II

有效的数独
​​​​​​​​​​​​​​


​​​​​​​

因为刷题的时候看答案通常官解给的看不懂,所以会去看别人的思路和解法。以下汇总了我所有刷的题能看懂捋顺的比较容易理解的代码答案,也方便我回顾以及二刷的时候回来看,有不明白的小伙伴欢迎私信或评论区交流

简化路径

names 中包含的字符串只能为以下几种:

        空字符串。例如当出现多个连续的 /,就会分割出空字符串;
        一个点 .;
        两个点 ..;
        只包含英文字母、数字或 _ 的目录名。

对于「空字符串」以及「一个点」,我们实际上无需对它们进行处理,因为「空字符串」没有任何含义,而「一个点」表示当前目录本身,我们无需切换目录。

对于「两个点」或者「目录名」,我们则可以用一个栈来维护路径中的每一个目录名。当我们遇到「两个点」时,需要将目录切换到上一级,因此只要栈不为空,我们就弹出栈顶的目录。当我们遇到「目录名」时,就把它放入栈。

这样一来,我们只需要遍历 names 中的每个字符串并进行上述操作即可。在所有的操作完成后,我们将从栈底到栈顶的字符串用 / 进行连接,再在最前面加上 / 表示根目录,就可以得到简化后的规范路径。

class Solution:
    def simplifyPath(self, path: str) -> str:
        names = path.split('/')
        stack = []
        for name in names:
            if name == '..':
                if stack:
                    stack.pop()
            elif name and name != '.':
                stack.append(name)
        return '/' + '/'.join(stack)

基本运算器

题目描述:给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

注意:不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval() 。

提示:

    1 <= s.length <= 3 * 105
    s 由数字、'+'、'-'、'('、')'、和 ' ' 组成
    s 表示一个有效的表达式
    '+' 不能用作一元运算(例如, "+1" 和 "+(2 + 3)" 无效)
    '-' 可以用作一元运算(即 "-1" 和 "-(2 + 3)" 是有效的)
    输入中不存在两个连续的操作符
    每个数字和运行的计算将适合于一个有符号的 32位 整数

一个表达式分为三部分:

左边表达式①,运算符③,右边表达式②

操作的步骤是:

    如果当前是数字,那么更新计算当前数字;
    如果当前是操作符+或者-,那么需要更新计算当前计算的结果 res,并把当前数字 num 设为 0,sign 设为正负,重新开始;
    如果当前是 ( ,那么说明遇到了右边的表达式,而后面的小括号里的内容需要优先计算,所以要把 res,sign 进栈,更新 res 和 sign 为新的开始;
    如果当前是 ) ,那么说明右边的表达式结束,即当前括号里的内容已经计算完毕,所以要把之前的结果出栈,然后计算整个式子的结果;
    最后,当所有数字结束的时候,需要把最后的一个 num 也更新到 res 中。

class Solution(object):
    def calculate(self, s):
        res, num, sign = 0, 0, 1
        stack = []
        for c in s:
            if c.isdigit():
                num = 10 * num + int(c)
            elif c == "+" or c == "-":
                res += sign * num
                num = 0
                sign = 1 if c == "+" else -1
            elif c == "(":
                stack.append(res)
                stack.append(sign)
                res = 0
                sign = 1
            elif c == ")":
                res += sign * num
                num = 0
                res *= stack.pop()
                res += stack.pop()
        res += sign * num
        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 rotateRight(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
        if not head or not head.next:
            return head
        n = 0
        ghead = ListNode(0)
        ghead.next = head
        node = ghead
        while(node.next):
            node = node.next
            n += 1
        k %= n
        step = n-k
        knode = ghead
        for i in range(step):
            knode = knode.next
        node.next = ghead.next
        ghead.next = knode.next
        knode.next = None
        return ghead.next

反转链表II

class Solution:
    def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
        pre = ListNode(0)
        pre.next = head
        dummy = pre
        n = left
        while(n-1):
            pre = pre.next
            n -= 1
        cur = pre.next
        betwn = right-left
        while betwn:
            gnode = cur.next
            cur.next = gnode.next
            gnode.next = pre.next
            pre.next = gnode
            betwn -= 1
        return dummy.next

随机链表的复制

解法1:可以先构建一个可以查询的字典dic,先遍历一遍,在dic中存入所有节点,然后再遍历一遍,将所有节点直接的关系连接起来:

"""
# Definition for a Node.
class Node:
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
        self.val = int(x)
        self.next = next
        self.random = random
"""

class Solution:
    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
        # return copy.deepcopy(head)
        if not head:
            return 
        dic = {}
        cur = head
        while cur:
            dic[cur] = Node(cur.val)
            cur = cur.next
        cur = head
        while cur:
            dic[cur].next = dic.get(cur.next)
            dic[cur].random = dic.get(cur.random)
            cur = cur.next
        return dic[head]

解法2:加trick

"""
# Definition for a Node.
class Node:
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
        self.val = int(x)
        self.next = next
        self.random = random
"""

class Solution:
    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
        return copy.deepcopy(head)

判断链表是否有环

方法1:使用set集合存储记录过的节点

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        seen = set()
        while head:
            if head in seen:
                return True
            seen.add(head)
            head = head.next
        return False
        
        

方法2:使用快慢指针

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        slow = fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if fast is slow:
                return True
        return False

链表两数相加

# 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], carry = 0) -> Optional[ListNode]:
        if l1 is None and l2 is None:
            return ListNode(carry) if carry else None
        if l1 is None:
            l1, l2 = l2, l1
        s = carry + l1.val + (l2.val if l2 else 0)
        l1.val = s%10
        l1.next = self.addTwoNumbers(l1.next, l2.next if l2 else None, s//10)
        return l1

爬楼梯

加上@cache后可以有效解决超时报错的问题

class Solution:
    def climbStairs(self, n: int) -> int:
        @cache
        def dfs(x: int) -> int :
            if x <= 1:
                return 1
            return dfs(x-1) + dfs(x-2)
        return dfs(n)

罗马数字转整数

class Solution:
    SYMBOL ={
            'I':1,
            'V':5,
            'X':10,
            'L':50,
            'C':100,
            'D':500,
            'M':1000
        }
    def romanToInt(self, s: str) -> int:
        ans = 0
        n = len(s)
        for i, ch in enumerate(s):
            value = Solution.SYMBOL[ch]
            if i < n-1 and value < Solution.SYMBOL[s[i+1]]:
                ans -= value
            else:
                ans += value
        return ans

整数转罗马数字

class Solution:
    SYMBOL = {
        'M':1000,
        'CM':900,
        'D':500,
        'CD':400,
        'C':100,
        'XC':90,
        'L':50,
        'XL':40,
        'X':10,
        'IX':9,
        'V':5,
        'IV':4,
        'I':1
    }
    def intToRoman(self, num: int) -> str:
        roman = list()
        for i, ch in enumerate(Solution.SYMBOL):
            while Solution.SYMBOL[ch] <= num:
                num -= Solution.SYMBOL[ch]
                roman.append(ch)
            if num == 0:
                break
        return "".join(roman)

复原IP地址

边界条件:遍历完所有数字,且path中正好有4个数字
通过306累加数中,了解到了,可以在选择数的过程中,增加判断条件,避免都堆到最后进行判断,造成超时
对于每个选择数字的判断:1、不能有“01”类型数字的出现 2、path长度不能超过4,3、数值int在范围内
完成判断,解决问题

class Solution:
    def restoreIpAddresses(self, s: str) -> List[str]:
        n = len(s)
        ans = []
        path = []
        def dfs(i):
            if i == n and len(path) == 4:
                ans.append(".".join(str(x) for x in path))
                return
            for j in range(i, n):
                t = s[i:j+1]
                if str(int(t)) != t:
                    break
                if len(path)<4 and 0<=int(t)<=255:
                    path.append(t)
                    dfs(j+1)
                    path.pop()
        dfs(0)
        return ans

存在重复元素 II

class Solution:
    def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
        num_to_index = {}
        for i, ch in enumerate(nums):
            if ch in num_to_index and i-num_to_index[ch] <= k:
                return True
            num_to_index[ch] = i
        return False

最长递增子序列

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        if not nums:
            return 0
        dp = [1] *len(nums)
        for i in range(len(nums)):
            for j in range(i):
                if nums[j] < nums[i]:
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp)

​​​​​​​​​​​​找出字符串中第一个匹配项的下标

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

最长连续序列

class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        longe = 0
        num_set = set(nums)
        for num in num_set:
            if num-1 not in num_set:
                current_num = num
                current_streak = 1
                while current_num+1 in num_set:
                    current_num += 1
                    current_streak += 1
                longe = max(longe, current_streak)

        return longe

快乐数

class Solution:
    def isHappy(self, n: int) -> bool:
        def get_next(n):
            total_num = 0
            while n > 0:
                n, digit = divmod(n, 10)
                total_num += digit ** 2
            return total_num

        seen = set()
        while n != 1 and n not in seen:
            seen.add(n)
            n = get_next(n)
        return n == 1

最长回文子串

class Solution(object):
    def longestPalindrome(self, s):
        res = ''
        for i in range(len(s)):
            start = max(i - len(res) -1, 0)
            temp = s[start: i+1]
            if temp == temp[::-1]:
                res = temp
            else:
                temp = temp[1:]
                if temp == temp[::-1]:
                    res = temp
        return res

反转字符串中的单词

给你一个字符串 s ,请你反转字符串中 单词 的顺序。

class Solution(object):
    def reverseWords(self, s):
        """
        :type s: str
        :rtype: str
        """
        return ' '.join(reversed(s.split()))

搜索旋转排序数组

在二分查找的上下文中,if target > nums[mid] and target <= nums[right]: 这一行通常用于检查目标值 target 是否位于当前中间元素 nums[mid] 的右侧,但不超过数组右边界 nums[right] 的情况。这种检查方式通常用于处理数组是部分有序(如旋转排序数组)或者有其他特定排序规则的情况。

这里的 else 分支则对应了所有不满足 if 条件的情况。具体来说,它涵盖了以下几种情况:

  1. 目标值小于中间值 (target < nums[mid]):
    • 这意味着目标值可能位于当前中间元素的左侧。在标准的二分查找中,你会将 right 更新为 mid - 1 来缩小搜索范围到左半部分。但在处理旋转排序数组或其他特殊情况下,可能需要根据具体情况决定是更新 left 还是 right
  2. 目标值大于右边界值 (target > nums[right]):
    • 这种情况表明目标值在当前搜索的右半部分之外,且由于数组已经排序(或部分排序),我们可以确定目标值不在数组中。不过,在某些特殊情况下(如数组可能包含重复元素),可能需要进一步处理来确保准确性。
  3. 目标值等于中间值 (target == nums[mid]):
    • 这种情况在 if 条件中没有直接处理,因为它既不属于 target > nums[mid] 也不属于 target <= nums[right]。因此,它会被 else 分支捕获。在二分查找中,如果找到目标值,通常会立即返回该值的位置。但是,如果你的逻辑是寻找第一个等于目标值的元素(例如,在有序数组中),则可能需要进一步处理来确保返回的索引是正确的。
  4. 目标值大于中间值但大于右边界值 (target > nums[mid] && target > nums[right]):
    • 这种情况在逻辑上是不可能的,因为 target <= nums[right] 已经在 if 条件中排除了 target > nums[right] 的情况。但是,它说明了 if 条件与 else 分支之间的逻辑关系。
class Solution:
    def search(self, nums: List[int], target: int) -> bool:
        if not nums: return -1
        left, right = 0, len(nums)-1
        while left<=right:
            mid = (left + right)// 2
            if nums[mid] == target:
                return True
            elif nums[left] == nums[right]:
                left += 1
                continue
            elif nums[mid] <= nums[right]:
                if nums[mid]<target<= nums[right]:
                    left = mid+1
                else:
                    right = mid-1
            else:
                if nums[left]<=target< nums[mid]:
                    right = mid-1
                else:
                    left = mid+1
        return False

全排列

解法一:

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        # return list(permutations(nums))
        def dfs(x):
            if x==len(nums)-1:
                res.append(list(nums))
                return
            for i in range(x, len(nums)):
                nums[i], nums[x] = nums[x], nums[i]
                dfs(x+1)
                nums[i], nums[x] = nums[x], nums[i]
        res = []
        dfs(0)
        return res

解法二:

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        return list(permutations(nums))
      

x的平方根

class Solution:
    def mySqrt(self, x: int) -> int:
        # return int(x**0.5)
        i, j = 1, x
        while i<=j:
            m = (i+j) // 2
            if m*m==x:
                return m
            elif m*m < x:
                i = m+1
            else:
                j = m-1
        return j

73. 矩阵置零

class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        """
        zero = []
        for i in range(len(matrix)):
            for j in range(len(matrix[0])):
                if matrix[i][j] == 0:
                    zero.append((i,j))
        while len(zero) > 0:
            x, y = zero.pop()
            for i in range(len(matrix)):
                for j in range(len(matrix[i])):
                    if i == x or j == y:
                        matrix[i][j] = 0
        return  

编辑距离

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        n, m = len(word1), len(word2)
        word1 = '#' + word1
        word2 = '#' + word2
        # 初始化动态规划表
        dp = [[0] * (m + 1) for _ in range(n + 1)]
        # 填充dp表
        for i in range(n + 1):
            for j in range(m + 1):
                if i == 0 and j == 0:
                    dp[i][j] = 0
                elif i == 0:
                    dp[i][j] = j
                elif j == 0:
                    dp[i][j] = i
                else:
                    if word1[i] == word2[j]:
                        dp[i][j] = dp[i - 1][j - 1]
                    else:
                        dp[i][j] = 1 + min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1])

        return dp[n][m]
# 示例使用
solution = Solution()
print(solution.minDistance("horse", "ros"))  # 输出应为 3

排列序列

class Solution:
    def getPermutation(self, n: int, k: int) -> str:
        fact = n *[1]
        for i in range(1,n):
            fact[i] = fact[i-1] * i
        ans = list()
        k -= 1
        valid = [1] * (n+1)
        for i in range(1, n+1):
            order = k // fact[n-i] + 1
            for j in range(1, n+1):
                order -= valid[j]
                if order == 0:
                    ans.append(str(j))
                    valid[j] = 0
                    break
            k %= fact[n-i]

        return "".join(ans)

回文链表

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def isPalindrome(self, head: Optional[ListNode]) -> bool:
        vals = []
        current_node = head
        while current_node is not None:
            vals.append(current_node.val)
            current_node = current_node.next
        return vals == vals[::-1]

二叉树的最大深度

树的后序遍历 / 深度优先搜索往往利用 递归 或 栈 实现,本文使用递归实现。

关键点: 此树的深度和其左(右)子树的深度之间的关系。显然,此树的深度 等于 左子树的深度 与 右子树的深度中的 最大值 +1

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def maxDepth(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """
        if not root:
            return 0
        return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1

翻转二叉树

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def invertTree(self, root):
        """
        :type root: TreeNode
        :rtype: TreeNode
        """
        if not root:
            return
        tmp = root.left
        root.left = self.invertTree(root.right)
        root.right = self.invertTree(tmp)

        return root

相同的树

给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def isSameTree(self, p, q):
        """
        :type p: TreeNode
        :type q: TreeNode
        :rtype: bool
        """
        if p is None or q is None:
            return p is q
        return p.val == q.val and self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
        

逆波兰表达式

今天学逆波兰表达式撰写还顺便复习了一下二叉树的前中后序遍历:

前序遍历:根左右

中序遍历:左根右

后序遍历:左右根

本题两个要点:

        a.判断字符串中的元素是数字还是字符串

        因为Python 中没有一个函数可以判断一个字符串是否为合理的整数(包括正、负数)。str.isdigit() 可以判断正数,但是无法判断负数。

解决方法:

使用 int() 函数,并做 try-except 。

    如果是整数,那么可以用 int() 转成数字;
    如果是运算符,那么 int() 会报错,从而进入 except 中。

         b.python 的整数除法是向下取整,而不是向零取整。

            python2 的除法 "/" 是整数除法, "-3 / 2 = -2" ;
            python3 的地板除 "//" 是整数除法, "-3 // 2 = -2" ;
            python3 的除法 "/" 是浮点除法, "-3 / 2 = -1.5" ;

        而 C++/Java 中的整数除法是向零取整。C++/Java 中 "-3 / 2 = -1" .本题的题意(一般情况)都是要求向零取整的。

解决方法:

对 Python 的整数除法问题,可以用 int(num1 / float(num2)) 来做,即先用浮点数除法,然后取整。

    无论如何,浮点数除法都会得到一个浮点数,比如 "-3 / 2.0 = 1.5" ;
    此时再取整,就会得到整数部分,即 float(-1.5) = -1 。

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        for token in tokens:
            try:
                stack.append(int(token))
            except:
                num2 = stack.pop()
                num1 = stack.pop()
                stack.append(self.evaluate(num1,num2, token))
        return stack[0]
    def evaluate(self,num1,num2,op):
        if op == "+":
            return num1+num2
        if op == "-":
            return num1-num2
        if op == "*":
            return num1 *num2
        if op == "/":
            return int(num1/float(num2)) 

最小栈

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

    MinStack() 初始化堆栈对象。
    void push(int val) 将元素val推入堆栈。
    void pop() 删除堆栈顶部的元素。
    int top() 获取堆栈顶部的元素。
    int getMin() 获取堆栈中的最小元素

核心思想就是:

push 方法通过将一个元组 (x, min_value) 添加到栈中来同时保存新元素 x 和当前栈中的最小值 min_value。

class MinStack:
 
    def __init__(self):
        self.stack = []
 
 
    def push(self, val: int) -> None:
        if not self.stack:
            self.stack.append((val,val))
        else:
            self.stack.append((val,min(val, self.stack[-1][1])))
 
    def pop(self) -> None:
        self.stack.pop()
 
    def top(self) -> int:
        if self.stack:
            return self.stack[-1][0]
        else:
            return None
 
 
    def getMin(self) -> int:
        return self.stack[-1][1]
 
 
 
# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(val)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.getMin()

三数之和

这道题记得之前做过,但是想不起来了。。总结一下:

函数的主要步骤和关键点:

    排序:对输入的整数数组nums进行排序。这是非常重要的,因为它允许我们使用双指针技巧来高效地找到满足条件的三元组。
    初始化:定义ans列表来存储所有找到的三元组,并初始化三个指针first、second和third。
    枚举第一个数:使用first指针遍历整个数组。为了避免重复的三元组(例如[-1, 0, 1]和[0, -1, 1]),我们需要跳过所有与前一个数相同的数。
    设置目标和双指针:将目标和target设置为-nums[first],然后初始化third指针为数组的最后一个元素的索引。此时,我们需要找到两个数(nums[second]和nums[third]),它们的和等于target。
    枚举第二个数:使用second指针从first + 1开始遍历数组。同样地,为了避免重复的三元组,我们需要跳过所有与前一个数相同的数。
    双指针技巧:当nums[second] + nums[third] > target时,说明third指向的数太大了,我们需要将third向左移动;否则,我们检查是否找到了一个满足条件的三元组。
    避免重复:当second和third相遇或nums[second] + nums[third] == target时,我们需要检查是否找到了一个有效的三元组,并将其添加到ans列表中。然后,我们继续移动second指针,但在这之前,我们需要跳过所有与当前nums[second]相同的数,以避免找到重复的三元组。
    返回结果:返回存储了所有满足条件的三元组的ans列表。

改进点:这个算法的时间复杂度是O(n^2),其中n是数组nums的长度。

    设 s = nums[first] + nums[first+1] + nums[first+2],如果 s > 0,由于数组已经排序,后面无论怎么选,选出的三个数的和不会比 s 还小,所以只要 s > 0 就可以直接 break 外层循环了。

    如果 nums[first] + nums[n-2] + nums[n-1] < 0,由于数组已经排序,nums[first] 加上后面任意两个数都是小于 0 的,所以下面的双指针就不需要跑了。但是后面可能有更大的 nums[first],所以还需要继续枚举,continue 外层循环。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        nums.sort()
        ans = []
        n = len(nums)
        for i in range(n-2):
            x = nums[i]
            if i > 0 and x == nums[i-1]:
                continue
            if x + nums[i+1] + nums[i+2] > 0:
                break
            if x + nums[-1] + nums[-2] < 0:
                continue
            j = i+1
            k = n-1
            while j<k:
                s = x + nums[j] + nums[k]
                if s < 0:
                    j += 1
                elif s > 0:
                    k -= 1
                else:
                    ans.append([x,nums[j],nums[k]])
                    j += 1
                    while j < k and nums[j] == nums[j-1]:
                        j += 1
                    k -= 1
                    while k > j and nums[k] == nums[k+1]:
                        k -= 1
        return ans
 

有效的括号

最近换实习很久不刷leetcode。。真的有点手生了,还是要坚持刷阿!

有效的括号这道题就是实现了一个相互匹配,那么基本上就是用字典,那么如何灵活的用字典,可以使用括号对应数字取加和判断,也可以就单独压入右括号的方法。

class Solution:
    def isValid(self, s: str) -> bool:
        dic = {'{':'}','(':')','[':']'}
        stack = []
        for c in s:
            if c in dic:
                stack.append(dic[c])
            elif not stack or stack.pop()!= c:
                return False
        return len(stack) == 0

一开始觉得数字的简单,但是后面耐心了解了一下右括号的发现更简单!

数字加和C++版:

class Solution {
public:
    bool isValid(string s) {
        unordered_map<char, int> myMap = {
            {'(', 1},
            {'{', 2},
            {'[', 3},
            {']', 4},
            {'}', 5},
            {')', 6}
        };
        stack<char> mySk;
        for(auto element:s)
        {
            if(myMap[element] < 4)
            {
               mySk.push(element); 
            }
            else
            {
                if(mySk.empty() || myMap[element] + myMap[mySk.top()] != 7)
                {
                    return false;
                }
                else
                {
                    mySk.pop();
                }
            }
        }
        return mySk.empty();
    }
};

当然也可以不用字典或者map,因为毕竟元素比较少,直接手敲也可以。

public boolean isValid(String s) {
        if(s.isEmpty())
            return true;
        Stack<Character> stack=new Stack<Character>();
        for(char c:s.toCharArray()){
            if(c=='(')
                stack.push(')');
            else if(c=='{')
                stack.push('}');
            else if(c=='[')
                stack.push(']');
            else if(stack.empty()||c!=stack.pop())
                return false;
        }
        if(stack.empty())
            return true;
        return false;
    }

字母异位词分组

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        np = collections.defaultdict(list)
        for st in strs:
            name = "".join(sorted(st))
            np[name].append(st)
        return list(np.values())

单词规律

跟上一个字符串的思路一致,只是要进行单词的拆分,用.split()函数即可。

class Solution:
    def wordPattern(self, pattern: str, s: str) -> bool:
        word = s.split()
        if(len(pattern) != len(word)):
            return False
        return len(set(pattern)) == len(set(word)) == len(set(zip(pattern,word)))

同构字符串

要求:判断两个字符串的形式是不是一致,即是不是AABC或者ABBBCC这种。

trick:使用set()结合zip()。

set()用法:用于创建一个不包含重复元素的集合

zip()用法:用于将可迭代的对象作为参数,将对象中的元素打包成一个个元组,然后返回这些元组组成的对象。

s="abc"
t="xyz"
zipped = zip(s,t)
list_1 = list(zipped)
print(list_1) #输出[('a','x'),('b','y'),('c','z')]

统计优美子数组

解题思路
一、滑动窗口

不断右移 right 指针来扩大滑动窗口,使其包含 k 个奇数;

若当前滑动窗口包含了 k 个奇数,则如下「计算当前窗口的优美子数组个数」:

统计第 1 个奇数左边的偶数个数 leftEvenCnt。 这 leftEvenCnt 个偶数都可以作为「优美子数组」的起点,因此起点的选择有 leftEvenCnt + 1 种(因为可以一个偶数都不取,因此别忘了 +1 )。
统计第 k 个奇数右边的偶数个数 rightEvenCnt 。 这 rightEvenCnt 个偶数都可以作为「优美子数组」的终点,因此终点的选择有 rightEvenCnt + 1 种(因为可以一个偶数都不取,因此别忘了 +1 )。
因此「优美子数组」左右起点的选择组合数为 (leftEvenCnt + 1) * (rightEvenCnt + 1)。

class Solution:  
    def numberOfSubarrays(self, nums: List[int], k: int) -> int:  
        left = right = odd_cnt = res = 0  
        while right < len(nums):  
            if nums[right] % 2 == 1:  
                odd_cnt += 1  
            if odd_cnt == k:  
                tmp = right  
                while right < len(nums) and nums[right] % 2 == 0:  
                    right += 1  
                right_even_cnt = right - tmp  
                left_even_cnt = 0  
                while left < len(nums) and nums[left] % 2 == 0:  
                    left_even_cnt += 1  
                    left += 1   
                res += (left_even_cnt + 1) * (right_even_cnt + 1)  
                left += 1  
                odd_cnt -= 1  
            right += 1  
        return res  

爱生气的书店老板

解题思路
重点:

不生气时顾客会留下,生气时会赶走顾客。
「秘密技巧」可以使老板在窗口大小 X 的时间内不生气。我们使用「秘密技巧」的原则是:寻找一个时间长度为 X 的窗口,能留住更多的原本因为老板生气而被赶走顾客。
使用「秘密技巧」能得到的最终的顾客数 = 所有不生气时间内的顾客总数 + 在窗口 X 内使用「秘密技巧」挽留住的原本因为生气而被赶走顾客数。
因此,可以把题目分为以下两部分求解:

所有不生气时间内的顾客总数:使用 iii 遍历[0,customers.length)[0, customers.length)[0,customers.length),累加grumpy[i]==0grumpy[i] == 0grumpy[i]==0时的customers[i]customers[i]customers[i]。
在窗口 X 内因为生气而被赶走的顾客数:使用大小为 X 的滑动窗口,计算滑动窗口内的grumpy[i]==1grumpy[i] == 1grumpy[i]==1时的customers[i]customers[i]customers[i],得到在滑动窗口内老板生气时对应的顾客数。

滑动窗口遍历:

        从第x个元素开始遍历到最后一个元素,每一步中,我们执行以下操作:

        如果新进入窗口的元素(即customers[i]和grumpy[i])对应的员工是生气的就将其服务的顾客数加到curValue中。

        如果离开窗口的元素(即customers[i-X]和grumpy[i-X]对应的员工是生气的,则从其服务的顾客数中减去该值(从curValue中))

class Solution:
    def maxSatisfied(self, customers: List[int], grumpy: List[int], X: int) -> int:
        N = len(customers)
        sum_ = 0
        # 所有不生气时间内的顾客总数
        for i in range(N):
            sum_ += customers[i] * (1 - grumpy[i])
        # 生气的 X 分钟内,会让多少顾客不满意
        curValue = 0
        # 先计算起始的 [0, X) 区间
        for i in range(X):
            curValue += customers[i] * grumpy[i]
        resValue = curValue
        # 然后利用滑动窗口,每次向右移动一步
        for i in range(X, N):
            # 如果新进入窗口的元素是生气的,累加不满意的顾客到滑动窗口中
            # 如果离开窗口的元素是生气的,则从滑动窗口中减去该不满意的顾客数
            curValue = curValue + customers[i] * grumpy[i] - customers[i - X] * grumpy[i - X]
            # 求所有窗口内不满意顾客的最大值
            resValue = max(resValue, curValue)
        # 最终结果是:不生气时的顾客总数 + 窗口X内挽留的因为生气被赶走的顾客数
        return sum_ + resValue
 

水果成篮(最大滑窗)

白话题意:求满足某个条件(数组值最多就两类的连续数组,例如[1,2,2,1,2])的最长数组长度

class Solution:
    def totalFruit(self, fruits: List[int]) -> int:
        # 初始化
        i, j = 0, 0
        res = 0
        classMap = defaultdict(int)
        classCnt = 0
        
        # 移动滑窗右边界 
        while j < len(fruits):
            # 判断当前是否满足条件
            if classMap[fruits[j]] == 0:
                classCnt += 1
            classMap[fruits[j]] += 1
 
            # 若不满足条件,移动i
            while classCnt > 2:
                if classMap[fruits[i]] == 1:
                    classCnt -= 1
                classMap[fruits[i]] -= 1
                i += 1
 
            # 一旦满足条件,更新结果
            res = max(res, j - i + 1)
            j += 1
        return res

串联所有单词的子串

有没有一样喜欢看示例的,,看题目就觉得很难懂。大致就是words要进行排列组合,返回s中所有包含这个排列组合的首标。

顺完逻辑蛮好懂的,应该不算困难题,只是不知道用什么模块实现。

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        if not s or not words: return []
        one_word = len(words[0])
        all_len = one_word * len(words)
        n = len(s)
        words = Counter(words)
        res = []
        for i in range(0, n-all_len+1):
            tmp = s[i:i+all_len]
            c_tmp = []
            for j in range(0, all_len, one_word):
                c_tmp.append(tmp[j:j+one_word])
            if Counter(c_tmp) == words:
                res.append(i)
        return res

盛水最多的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

写出来了一半,想到用双指针,没想好怎么移动,后面看懂啦,小的先移

class Solution:
    def maxArea(self, height: List[int]) -> int:
        i,n = 0,len(height)-1
        max1pool = 0
        while i != n:
            min2 = min(height[i], height[n])
            max1pool = max(max1pool, (n-i) *  min2)
            if height[i] < height[n]:
                i += 1
            else:
                n -= 1
        return max1pool

两数之和(输入有序数组)

给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列  ,请你从数组中找出满足相加之和等于目标数 target 的两个数。以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

思路:双指针!发现双指针频率真的很高

class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        low,high = 0,len(numbers) -1
        while low<high:
            total = numbers[high] + numbers[low]
            if total == target:
                return [low+1,high+1]
            elif total < target:
                low+=1
            else:
                high-=1
        return [-1,-1] 

判断子序列

写了一版,发现这个记录的顺序不对,又去调试才看出来的,逻辑写错了,最近脑子真的不转。。。

class Solution:
    def isSubsequence(self, s: str, t: str) -> bool:
        r = []
        for i in range(len(s)):
            if s[i] not in t:
                return False
            else:
                r.append(s[i])
        if "".join(r) == s:
            return True
        else:
            return False

改进(双指针):

class Solution:
    def isSubsequence(self, s: str, t: str) -> bool:
        n,m = len(s),len(t)
        i = j = 0
        while i < n and j < m:
            if s[i] == t[j]:
                i += 1
            j += 1
        return i == n

最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀,如果不存在返回空字符串“”。

一开始没有思路,但看完答案就懂了,简单题

class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        if not strs:
            return ""
        common_lm = strs[0]
        for i in range(len(strs)):
            common_lm = self.get(common_lm, strs[i])
            if common_lm == "":
                return ""
        return common_lm
    def get(self, str1,str2):
        i = 0
        while(i < len(str1) and i < len(str2) and str1[i] == str2[i]):
            i += 1
        return str1[:i]

文本左右对齐

给定一个单词数组 words 和一个长度 maxWidth ,重新排版单词,使其成为每行恰好有 maxWidth 个字符,且左右两端对齐的文本。

你应该使用 “贪心算法” 来放置给定的单词;也就是说,尽可能多地往每行中放置单词。必要时可用空格 ' ' 填充,使得每行恰好有 maxWidth 个字符。

要求尽可能均匀分配单词间的空格数量。如果某一行单词间的空格不能均匀分配,则左侧放置的空格数要多于右侧的空格数。

文本的最后一行应为左对齐,且单词之间不插入额外的空格。

注意:

  • 单词是指由非空格字符组成的字符序列。
  • 每个单词的长度大于 0,小于等于 maxWidth
  • 输入单词数组 words 至少包含一个单词。
class Solution(object):
    def fullJustify(self, words, maxWidth):
        res, cur, num_of_letters = [], [], 0
        for w in words:
            if num_of_letters + len(w) + len(cur) > maxWidth:
                for i in range(maxWidth - num_of_letters):
                    cur[i%(len(cur)-1 or 1)] += ' '
                res.append(''.join(cur))
                cur, num_of_letters = [], 0
            cur += [w]
            num_of_letters += len(w)
        return res + [' '.join(cur).ljust(maxWidth)]

[' '.join(cur).ljust(maxWidth)]这个表达式的意思是:首先,将cur中的元素以空格为分隔符连接成一个字符串;然后,将这个字符串在右侧填充空格(如果需要的话),直到它的长度达到maxWidth指定的宽度;最后,将这个处理后的字符串作为列表中唯一的元素返回。

cur[i%(len(cur)-1 or 1)] += ' '这行代码实现了根据空格的数量来实现遍历字符加空格。

最后一个单词的长度

给你一个字符串s,由若干单词组成,单词前后用一些空格字符隔开,返回字符串中最后一个单词的长度。

 第一次解法:

class Solution:
    def lengthOfLastWord(self, s: str) -> int:
        n = len(s)
        j = 0
        for i in range(n-1, -1, -1):
            if s[i] != ' ':
                j += 1
            elif j==0 and s[i] == ' ':
                continue
            else:
                break
        return j

然后看见别人的一行成功。。。果然还是加trick香啊

class Solution:
    def lengthOfLastWord(self, s: str) -> int:
        return len(s.split()[-1])

除自身之外数组乘积

题意理解还是蛮容易的,但是果然超时了。。。

第一次解答:

class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        result = []
        lens = len(nums)
        lun = 0
        for i in range(lens):
            j = 0
            total = 1
            while(j < lens):
                if j != i and nums[j] != 0:
                    total *= nums[j]
                elif j != i and nums[j] == 0:
                    total = 0
                    break
                else:
                    total *= 1
                j += 1
            result.append(total)
        return result

改进:看到评论有一个双指针,写的很好TAT,

第一遍从前往后迭代,第二遍for从后往前乘。

注意:先将beforesum(当前元素左边的所有元素的乘积)乘ans[i]更新ans[i]的值,

再更新beforesum的值,将当前元素乘进去,方便下一个元素计算。

class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        n = len(nums)
        ans = [1] * n  # 初始化ans列表,所有元素都是1
        
        before_num = 1
        for i in range(n):
            ans[i] = before_num  # 更新ans[i]为当前的before_num
            before_num *= nums[i]  # 更新before_num
        
        after_num = 1
        for j in range(n-1, -1, -1):
            ans[j] *= after_num  # 更新ans[j],乘以当前的after_num
            after_num *= nums[j]  # 更新after_num
        
        return ans   

O(1) 时间插入、删除和获取随机元素

这道题要求实现一个类,满足插入、删除和获取随机元素操作的平均时间复杂度为 O(1)。

变长数组可以在 O(1) 的时间内完成获取随机元素操作,但是由于无法在 O(1)的时间内判断元素是否存在,因此不能在 O(1) 的时间内完成插入和删除操作。哈希表可以在 O(1) 的时间内完成插入和删除操作,但是由于无法根据下标定位到特定元素,因此不能在 O(1) 的时间内完成获取随机元素操作。为了满足插入、删除和获取随机元素操作的时间复杂度都是 O(1),需要将变长数组和哈希表结合,变长数组中存储元素,哈希表中存储每个元素在变长数组中的下标。

总括:数组提供快速访问,哈希表提供快速查找。

首先创建两个实例化对象:

    self.nums 是一个列表,用于存储插入的数字。
    self.indices 是一个字典,用于存储插入的数字以及其在 self.nums 列表中的索引。

创建RandomizedSet类的实例时,会自动创建一个空的nums列表和一个空的indices字典,为后续插入删除和随机获取做准备。

choice 是 Python 标准库中的 random 模块中的函数,用于从列表中随机选择一个元素并返回。

比较难理解的是remove这个类,过程:

首先检查给定的值是否存在indices字典中,如果不在,就返回False,表示删除失败

如果值存在,他会取出该值在nums列表中的索引id,然后他将nums列表中索引为id的元素替换为nums列表中的最后一个元素的值,这样就删除了原始的val,更新indices字典最后一个元素的索引值为id。删除nums列表中的最后一个元素,然后从indices列表中删除给定值。

这如果还不理解的话,可以自己画一个图就理解了。

例如nums:2,1,3

indices:(2:0),(1:1),(3:2)

举例:

(1)insert,插入4:

        在字典里增加值为“4”的点indices[4]的值为len(nums)= 3,  nums增加4。

(2)remove。删除1:

        获取“1”的值(序号),id为1。在nums找到序号为1的元素,将值更改为最后一个元素的值,即3.

变为:nums:2,3,3

        indices:(2:0),(1:1),(3:2)

nums[1]值为1,indice里面“3”对应的值改为id=1

变为:nums:2,3,3

        indices:(2:0),(1:1),(3:1)

然后弹出nums最后一个元素,将indices中元素“1”删除。

变为:nums:2,3

        indices:(2:0),(3:1)

class RandomizedSet:
    def __init__(self):
        self.nums = []
        self.indices = {}
 
    def insert(self, val: int) -> bool:
        if val in self.indices:
            return False
        self.indices[val] = len(self.nums)
        self.nums.append(val)
        return True
 
    def remove(self, val: int) -> bool:
        if val not in self.indices:
            return False
        id = self.indices[val]
        self.nums[id] = self.nums[-1]
        self.indices[self.nums[id]] = id
        self.nums.pop()
        del self.indices[val]
        return True
 
    def getRandom(self) -> int:
        return choice(self.nums)

跳跃游戏

思路:贪心算法,尽可能达到最远的位置(因为如果可以达到某个位置,一定可以达到之前的位置)

class Solution:
    def canJump(self, nums: List[int]) -> bool:
        max_i = 0
        for i ,jump in enumerate(nums):
            if max_i>=i and (i + jump) > max_i:
                max_i = i + jump
        return max_i >= i

这里的return max_i >= i,注意可以换成return max_i >= len(nums)-1.

首先初始化当前能到达的最远距离,i为当前位置,jump是当前位置的跳数

if判断如果当前位置到达,并且当前位置+跳数>最远位置

然后更新最远能到达位置

买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

思路:暴力破解,,超出时间限制了

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        lens = len(prices)
        max1 = 0
        for i in range(lens):
            for j in range(i, lens):
                if prices[j] > prices[i]:
                    max1 = max(max1, prices[j] - prices[i])
        return max1

改进:

双重循环换成ifelse加单循环,这个思路很好理解就不巴巴了

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if not prices:
            return 0
        max_profit = 0
        min_profit = prices[0]
 
        for price in prices:
            if price < min_profit:
                min_profit = price
            else:
                max_profit = max(max_profit, price - min_profit)
        return max_profit 

多数元素

返回数组中超过长度半数数量的元素的值

一开始想的是进行sort排序,然后取中间值与两头的分别比对一下,如果一致就返回。

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        nums.sort(reverse = True)
        n = len(nums)
        mid = int(n // 2)
        if nums[n-1] == nums[mid] or nums[0] == nums[mid]:
            return nums[mid]
        else:
            return 0

但是提交的时候发现还有这种情况:

于是调整一下思路:

因为排序后的数组,如果存在一个数超过数组长度一半,中位数一定是那个数,所以使用Counter计数,只需要判断中位数的次数有没有超过长度一半即可

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        nums.sort(reverse = True)
        n = len(nums)
        mid = int(n // 2)
        counts = Counter(nums)
        if counts[nums[mid]]>= mid :
            return nums[mid]
        else:
            return 0

删除有序数组中的重复项I + II

删除有序数组中的重复项I

给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

思路:双指针(不要被名字吓到,其实就是两个ij变量)

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

定义两个指针 i 和 j,其中 i 是慢指针,而 j 是快指针。当 nums[i] == nums[j] 时,递增 j 以跳过重复项;当 nums[i] != nums[j] 时,将 nums[j] 的值复制到 nums[i+1] 处,并递增 i。最后返回 i+1 即为去重后数组的新长度。

        最后应该只会检查前i+1个元素是否符合,如果系统不是这样检测,可以更改一下nums截取

nums[:] = nums[i+1:]

删除有序数组中的重复项II

题目省略了,就是现在可以最多重复两次。

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        len1=0
        for i in range(len(nums)):
            if(len1<2 or nums[len1 - 2] != nums[i]):
                nums[len1] = nums[i]
                len1 += 1
        return len1


数组--移除元素+合并两个有序数组

移除元素:

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        nums[:] = [num for num in nums if num!=val]
        return len(nums)

nums[:]实现对nums元素的复制。

合并两个有序数组

class Solution:
    def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
        """
        Do not return anything, modify nums1 in-place instead.
        """
        nums1[m:] = nums2
        nums1.sort()

随机链表的复制

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。

例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。

返回复制链表的头节点。

疑问:为什么不能写成dic[cur] = cur.val,若这样实际上将cur.val赋值给了dic的键cur,而不是将一个新的Node(cur.val)对象赋给dic的键cur。这就意味着若cur.val的值一旦改变,字典dic中的值也会发生改变,因为是一个对象的引用。

实现哈希表可以有以下两种方式:

    数组 + 链表
    数组 + 二叉树

所以,哈希表本质上就是一个数组。只不过数组存放的是单一的数据,而哈希表中存放的是键值对。


class Solution:
    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
        if not head:
            return
        dic = {}
        cur = head
        while cur:
            dic[cur] = Node(cur.val)
            cur = cur.next
        cur = head
        while cur:
            dic[cur].next = dic.get(cur.next)
            dic[cur].random = dic.get(cur.random)
            cur = cur.next
        return dic[head]

第一个while循环作用:遍历原链表,将原链表中的每个节点cur复制为一个新节点,并将这个新节点存储到字典dic中。

第二个while循环作用:处理复制链表中的next和random指针,在原链表中,每个节点的next和random指针可能指向其他节点,需要在复制链表中正确指向对应的新节点。

使用dic.get(cur.random)是为了处理可能存在空指针的情况。若原指针指向的是空,则返回None,就避免了直接使用dic.get(cur.random)可能导致的keyerror错误。

最长交替子数组

要求:给定一个数组,找出符合【x, x+1,x,x-1】这样循环的最大交替数组长度。

思路:用两层while循环,第一个while用来找到符合这个循环的开头位置,第二个用来找到该循环的结束位置,并比较一下max进行记录。

易错:要进行减一,因为上一个字符串最后一个结束的数字可能是下一个字符串的开头。

class Solution:
    def alternatingSubarray(self, nums: List[int]) -> int:
        ans = 0
        i ,n = 0, len(nums)
        while i < n-1:
            if nums[i+1]-nums[i] != 1:
                i += 1
                continue
            i0 = i
            i += 2
            while i < n and nums[i] == nums[i - 2]:
                i += 1
            ans = max(ans, i-i0)
            i -= 1
        return ans

自己重写的时候出现的写错句子:

队列中可以看到的人数

有n人排成一个队列,从左到右编号为0到n-1,height数组记录每个人的身高,返回一个数组,记录每个人能看到几个人。

类比:山峰问题,高的后面的矮的看不见。

从后往前,最后一个元素入栈,若前面的比他小,加入,元素自增一,若比他大,将栈顶元素出栈,大的元素加入,循环判断出栈一个加一。

找出缺失的观测数据

给你一个长度为 m 的整数数组 rolls ,其中 rolls[i] 是第 i 次观测的值。同时给你两个整数 mean 和 n 。返回一个长度为 n 的数组,包含所有缺失的观测数据,且满足这 n + m 次投掷的 平均值 是 mean 。如果存在多组符合要求的答案,只需要返回其中任意一组即可。如果不存在答案,返回一个空数组。

第一反应就是对剩余总和取平均,写完看了下题解确实是这样,只不过我的时间有些长。。。

可以加一些trick,例如不用一个一个加,使用sum()函数计算总和,使用divmod()计算平均和取mod,return的时候直接返回x*n这样的格式。

自写版:

最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

注意:

    对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
    如果 s 中存在这样的子串,我们保证它是唯一的答案。

这道题思路有点没搞懂,滑动窗口问题.  逐行解释:

cnt_s用于统计滑动窗口中字符出现字数,cnt_t用于统计字符串t中每个字符出现的次数。

for循环中,是遍历字符串s,right是当前字符的索引,c是当前字符。

cnt_s[c]将当前字符c在滑动窗口中的计数加1

while代表当滑动窗口中的字符至少和t中的字符计数相等时,进入循环。

if right-left < ans_right-ans_left;如果当前窗口的长度小于已知的最短窗口长度,则更新最短窗口的边界。

ans_right、ans_left更新最短窗口的左右边界。cns_s[s[left]]将滑动窗口左端点的字符计数减一,因为窗口要向右滑动。

return 最后返回找到的最短子串。如果没有找到任何子串(ans_left仍为-1),则返回空字符串。

class Solution:
    def minWindow(self, s: str, t: str) -> str:
        ans_left,ans_right = -1,len(s)
        left = 0
        cnt_s = Counter()
        cnt_t = Counter(t)
        for right,c in enumerate(s):
            cnt_s[c] += 1
            while cnt_s >= cnt_t:
                if right-left < ans_right-ans_left:
                    ans_left,ans_right = left,right
                cnt_s[s[left]] -= 1
                left += 1
        return "" if ans_left<0 else s[ans_left:ans_right+1]

无重复字符的最长子串

class Solution:

    def lengthOfLongestSubstring(self, s: str) -> int:
        ans = left = 0
        window = set()  # 维护从下标 left 到下标 right 的字符
        for right, c in enumerate(s):
            # 如果窗口内已经包含 c,那么再加入一个 c 会导致窗口内有重复元素
            # 所以要在加入 c 之前,先移出窗口内的 c
            while c in window:  # 窗口内有 c
                window.remove(s[left])
                left += 1  # 缩小窗口
            window.add(c)  # 加入 c
            ans = max(ans, right - left + 1)  # 更新窗口长度最大值
        return ans

这题二刷的时候想错了思路,要记得是滑动窗口问题,二刷思路(错误):

# if len(s) == 0:
        #     return 0
        # d = s[0]
        # ans = 1
        # max_ans = 1
        # dt = set()
        # dt.add(d)
        # for e,i in enumerate(s):
        #     if e == 0:
        #         continue
        #     if i != d and i not in dt:
        #         ans += 1
        #         d = i
        #         dt.add(i)
        #     else:
        #         ans = 1
        #         dt = set()
        #         dt.add(i)
        #     max_ans = max(max_ans, ans)
        # return max_ans

长度最小的子数组

class Solution:  
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:  
        if not nums:  
            return 0  # 如果数组为空,返回0或根据需求处理  
        sum1 = 0  
        left = 0  
        min_len = float('inf')  # 初始化为无穷大,方便找到最小长度      
        for right, c in enumerate(nums):  
            sum1 += c  # 累加当前元素  
            while sum1 >= target:  
                min_len = min(min_len, right - left + 1)  # 更新最小长度  
                sum1 -= nums[left]  # 缩小窗口  
                left += 1  # 移动左边界  
        # 如果min_len没有被更新过,说明没有找到符合条件的子数组  
        return min_len if min_len != float('inf') else 0

二叉树中的最大路径和

二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。

路径和 是路径中各节点值的总和。

给你一个二叉树的根节点 root ,返回其 最大路径和 。

题目理解与分析:就是在二叉树中找到一条和最大的线。

解题思路:从上往下使用递归,1.迭代计算最大的左孩子长度,迭代计算最大的右孩子长度  2.计算每个节点加上左右孩子的最大长度作为最大值,并每个计算完与最大值比较更新。3. 判断左节点和右节点孰大孰小,更新节点的最大路径。

因为最长的线可能出现在:以叶节点为根的单个路径、以叶节点的父节点为根的回旋路径、以根节点为父节点的回旋路径/单个路径。所以归根到底是记录以每个节点为根的最大路径。

class TreeNode(object):
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right
class Solution:
    def __init__(self):
        self.maxSum = float("-inf")
 
    def maxPathSum(self, root: TreeNode) -> int:
        def maxGain(node):
            if not node:
                return 0
            leftGain = max(maxGain(node.left), 0)
            rightGain = max(maxGain(node.right), 0)
            priceNewPath = node.val + leftGain + rightGain
            self.maxSum = max(self.maxSum, priceNewPath)
            return node.val + max(leftGain, rightGain)
        maxGain(root)
        return self.maxSum

k个一组翻转

涉及指针以及单向链表,主要思路应该是建立一个新链表,每次从旧链表拿出来一个next指向之前进来的。

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next
class Solution:
    def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
        if not head:
            return None
        p = head
        i = 0
        while p and i < k:
            p = p.next
            i += 1
        if i < k:
            return head
        
        prev,curr = None, head
        for _ in range(k):
            next_node = curr.next
            curr.next = prev
            prev = curr
            curr = next_node
        head.next = self.reverseKGroup(curr,k)
        return prev

举例:

        输入链表:1->2->3->4->5->6->7

        输入k值:3

第一次for循环之后的链表:

        1->NULL

        2->3->4->5->6->7->NULL

        head.next递归调用函数,继续翻转剩余的k个节点。并将反转后的链表连接到当前反转的k个节点后。

第二次for循环之后的链表:

        2->1->NULL

        3->4->5->6->7->NULL

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
        n = 0
        cur = head
        while cur:
            n += 1
            cur = cur.next
        p0 = dummy = ListNode(next=head)
        pre = None
        cur = head
        while n>=k:
            n-=k 
            for _ in range(k):
                nxt = cur.next
                cur.next = pre
                pre = cur
                cur = nxt
            nxt = p0.next
            nxt.next = cur
            p0.next = pre
            p0 = nxt
        return dummy.next

合并区间+插入区间+汇总区间+射出最少的箭

合并区间        

        第一步应该想到要按区间的第一个元素进行排序,然后若后一个区间的起始元素小于前一个区间的结尾元素,判断一下两个区间的结尾元素孰大孰小,选择大的作为新区间的结尾元素。

class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        intervals.sort(key = lambda x: x[0])
        merged = []
        for interval in intervals:
            if not merged or merged[-1][1] < interval[0]:
                merged.append(interval)
            else:
                merged[-1][1] = max(merged[-1][1], interval[1])
        return merged
插入区间

和合并区间一样,先把插入的区间加入,后面在sort和合并区间套路一样。

class Solution:
    def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
        res = []
        intervals.append(newInterval)
        intervals.sort(key = lambda x:x[0])
        for interval in intervals:
            if not res or res[-1][1] < interval[0]:
                res.append(interval)
            else:
                res[-1][1] = max(res[-1][1], interval[1])
        return res
汇总区间
class Solution:
    def summaryRanges(self, nums: List[int]) -> List[str]:
        def f(i:int,j:int)->str:
            return str(nums[i]) if i==j else f'{nums[i]}->{nums[j]}'
        i = 0
        n = len(nums)
        ans = []
        while i < n:
        # for i in range(n-1):
            j = i
            while j + 1 < n and nums[j+1] == nums[j]+1:
                j += 1
            ans.append(f(i,j))
            i = j+1
        return ans
射出最少的箭

相比较合并区间是合并取最大,那么射箭就是合并取最小。并集改为交集,左区间找最大,右区间找最小。

class Solution:
    def findMinArrowShots(self, points: List[List[int]]) -> int:
        points.sort(key = lambda x : x[0])
        merged = []
        for point in points:
            if not merged or merged[-1][1] < point[0]:
                merged.append(point)
            else:
                merged[-1][0] = max(merged[-1][0], point[0])
                merged[-1][1] = min(merged[-1][1], point[1])
        y = len(merged)
        return y

买卖股票问题

还有很多类似的题,实际上就是一个折线图问题,出现增加才进行计算:

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        ans = 0
        n = len(prices)
        for i in range(1, n):
            if prices[i] > prices[i-1]:
                ans += prices[i] - prices[i-1]
            # ans += max(0, prices[i] - prices[i-1])
        return ans

轮转数组+翻转数组(中等难度)

也就是输入k值,让数组实现轮转k次,类似于队列出去k次再进。

用python简单截断,

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        n = len(nums)
        ans = n - k%n
        nums[:] = nums[ans:] + nums[:ans]

类似的题:实现数组翻转

找到中位数进行左右互换

from typing import List
 
class Solution:
    def reverse_array(self, nums: List[int]) -> None:
        n = len(nums)
        mid = n // 2
        for i in range(mid):
            nums[i], nums[n - 1 - i] = nums[n - 1 - i], nums[i]
 
# 测试示例
nums = [1, 2, 3, 4, 5]
sol = Solution()
sol.reverse_array(nums)
print(nums)

腐烂的橘子

在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一:

    值 0 代表空单元格;
    值 1 代表新鲜橘子;
    值 2 代表腐烂的橘子。

每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。

返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1

r是x轴坐标,c是y轴坐标

class Solution:
    def orangesRotting(self, grid: List[List[int]]) -> int:
        x = len(grid)
        y = len(grid[0])
        queue = []
        count = 0
 
        for i in range(x):
            for j in range(y):
                if grid[i][j] == 1:
                    count += 1
                elif grid[i][j] == 2:
                    queue.append([i,j])
        round = 0
        while count > 0 and len(queue) > 0:
            round += 1
            n = len(queue)
            for i in range(n):
                r, c = queue.pop(0)
                if r-1>=0 and grid[r-1][c] == 1:
                    grid[r-1][c] = 2
                    count -= 1
                    queue.append((r-1,c))
                if r+1<x and grid[r+1][c] == 1:
                    grid[r+1][c] = 2
                    count -= 1
                    queue.append((r+1,c))
                if c-1>=0 and grid[r][c-1] == 1:
                    grid[r][c-1] = 2
                    count -= 1
                    queue.append((r,c-1))
                if c+1<y and grid[r][c+1] == 1:
                    grid[r][c+1] = 2
                    count -= 1
                    queue.append((r,c+1))
        if count > 0:
            return -1
        else: 
            return round

H指数

给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。

一名科研人员的 h 指数 是指他(她)至少发表了 h 篇论文,并且 至少 有 h 篇论文被引用次数大于等于 h 。如果 h 有多种可能的值,h 指数 是其中最大的那个。

灵活应用while循环和排序:

reverse – 排序规则,reverse = True 降序, reverse = False 升序(默认)。

注意的是while中的语句不能互换,否则出现超出范围报错。

class Solution:
    def hIndex(self, citations: List[int]) -> int:
        sorted_citations = sorted(citations, reverse= True)
        n = len(citations)
        a = 0
        while a < n and sorted_citations[a] > a:
            a += 1
        return a

接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

具体步骤如下:

    如果输入数组为空,直接返回 0。
    初始化左右指针 left 和 right,分别指向数组的左右端点。
    初始化 left_max 和 right_max 分别记录左右两侧的最大高度。
    初始化 result 变量,用来累积存储的雨水单位。
    当 left 小于 right 时,执行以下操作:
        如果 height[left] 小于 height[right],说明左侧是瓶颈,更新 left_max 并计算左侧能存储的雨水单位,然后将 left 指针右移。
        否则,说明右侧是瓶颈,更新 right_max 并计算右侧能存储的雨水单位,然后将 right 指针左移。
    最终返回 result 作为答案。

from typing import List
 
class Solution:
    def trap(self, height: List[int]) -> int:
        if not height:
            return 0
        
        left, right = 0, len(height) - 1
        left_max, right_max = 0, 0
        result = 0
        
        while left < right:
            if height[left] < height[right]:
                if height[left] >= left_max:
                    left_max = height[left]
                else:
                    result += left_max - height[left]
                left += 1
            else:
                if height[right] >= right_max:
                    right_max = height[right]
                else:
                    result += right_max - height[right]
                right -= 1
        
        return result
 
# 测试代码
sol = Solution()
height = [0,1,0,2,1,0,1,3,2,1,2,1]
result = sol.trap(height)
print(result)

最大正方形

class Solution:  
    def maximalSquare(self, matrix: List[List[str]]) -> int:  
        if not matrix or not matrix[0]:  
            return 0  
          
        m, n = len(matrix), len(matrix[0])  
        dp = [[0] * (n + 1) for _ in range(m + 1)]  # 初始化dp数组,大小为(m+1)x(n+1)  
        ans = 0  
          
        for i in range(1, m + 1):  
            for j in range(1, n + 1):  
                if matrix[i - 1][j - 1] == '1':  
                    dp[i][j] = 1  
                    if i > 1 and j > 1:  
                        dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1  
                    ans = max(ans, dp[i][j])  
          
        return ans  # 直接返回最大正方形的边长

注释版:

class Solution:  
    def maximalSquare(self, matrix: List[List[str]]) -> int:  
        # 检查输入矩阵是否为空或第一行是否为空(未定义),如果是,则无法形成正方形,直接返回0  
        if not matrix or not matrix[0]:  
            return 0  
          
        # 获取矩阵的行数和列数  
        m, n = len(matrix), len(matrix[0])  
          
        # 初始化一个二维动态规划数组dp,大小为(m+1)x(n+1)。  
        # 多加的一行一列用于简化边界条件的处理,使得dp[i][j]可以直接对应matrix[i-1][j-1]  
        dp = [[0] * (n + 1) for _ in range(m + 1)]  
          
        # 初始化最大正方形的边长为0  
        ans = 0  
          
        # 遍历矩阵中的每个元素(除了dp的第一行和第一列,它们保持为0)  
        for i in range(1, m + 1):  
            for j in range(1, n + 1):  
                # 如果当前元素是'1',则进行动态规划的计算  
                if matrix[i - 1][j - 1] == '1':  
                    # 初始化当前位置的最小可能边长为1  
                    dp[i][j] = 1  
                      
                    # 如果当前位置的上方、左方和左上方都有'1',则计算这三个方向上正方形的最小边长并加1  
                    if i > 1 and j > 1:  
                        dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1  
                      
                    # 更新最大正方形的边长  
                    ans = max(ans, dp[i][j])  
          
        # 返回最大正方形的边长  
        return ans

使用了动态规划的思想来解决“最大正方形”问题。它维护了一个二维数组dp,其中dp[i][j]表示以(i-1, j-1)为右下角的最大正方形的边长(注意,这里的索引转换是因为dp数组比matrix多了一行一列)。

在遍历matrix时,对于每个值为'1'的元素,代码会检查其上方、左方和左上方的元素是否也为'1'。如果是,则这三个方向上可能存在一个更大的正方形,其边长可以通过这三个方向上已有的正方形的最小边长加1来得到。这个过程会一直进行,直到遍历完整个matrix

打家劫舍

需要考虑两种情况:

        1.如果偷窃第i个房屋,那就不能偷窃第i-1个房屋,所以最大金额是dp[i-2]+nums[i](即偷窃到第i-2个房屋时的最大金额加上第i个房屋的金额)

        2.如果不偷窃第i个房屋,那么最大金额就是偷窃到第i-1个房屋时的最大金额,即dp[i-1]

class Solution:
    def rob(self, nums: List[int]) -> int:
        f = [0] * (len(nums) + 2)
        for i, x in enumerate(nums):
            f[i+2] = max(f[i+1], f[i] + x)
        return f[-1]

打家劫舍II

由于是环形的, 第一个和最后一个只能选一个,所以考虑两种情况:

class Solution:
    def rob(self, nums: List[int]) -> int:
        prev = nums[1:]
        back = nums[:-1]
        len1 = len(nums)-1
        f1 = [0] * (len1+2)
        f2 = [0] * (len1+2)
        if len1 == 0:
            return nums[0]
        else:
            for i, x in enumerate(prev):
                f1[i+2] = max(f1[i+1], f1[i]+x)
            for i, x in enumerate(back):
                f2[i+2] = max(f2[i+1], f2[i]+x)
            return f1[-1] if f1[-1]>f2[-1] else f2[-1]

有效的数独

请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

class Solution:
    def isValidSudoku(self, board: List[List[str]]) -> bool:
    # 检查行  
        for i in range(9):  
            row_set = set()  
            for j in range(9):  
                num = board[i][j]  
                if num != '.':  
                    num = int(num)  
                    if num in row_set:  
                        return False  
                    row_set.add(num)  
        
        # 检查列  
        for j in range(9):  
            col_set = set()  
            for i in range(9):  
                num = board[i][j]  
                if num != '.':  
                    num = int(num)  
                    if num in col_set:  
                        return False  
                    col_set.add(num)  
        
        # 检查九宫格  
        for block_i in range(3):  
            for block_j in range(3):  
                block_set = set()  
                for i in range(block_i * 3, block_i * 3 + 3):  
                    for j in range(block_j * 3, block_j * 3 + 3):  
                        num = board[i][j]  
                        if num != '.':  
                            num = int(num)  
                            if num in block_set:  
                                return False  
                            block_set.add(num)  
        
        return True  

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值