Leetcode HOT 100


03/05/2024

哈希

1. 两数之和

题目描述

解法

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        num_dic = {}                                    #字典哈希来记录每隔元素的下标,key是元素的值,value是元素的下标
        for i in range(len(nums)):                      #遍历元素
            if target - nums[i] in num_dic:             #如果target - 当前元素的值在num_dic中,也就是在key中
                return[i, num_dic[target - nums[i]]]    #返回当前元素的下标i和target - 当前元素的下标也就是num_dic对应的value
            num_dic[nums[i]] = i                        #把num_dic的元素赋值为下标

2. 字母异位词分组

题目描述

解法

方法一 暴力
class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        hash_map = [[0] * 26 for _ in range(len(strs))]
        result = []
        for i in range(len(strs)):
            for j in strs[i]:
                hash_map[i][ord(j) - ord('a')] += 1                 #二维哈希map记录每个单词的哈希映射表
        for i in range(len(hash_map)):                              #遍历哈希表,哈希序列相同的元素放在subset里,需要n^2的复杂度,并且还要保证用过的单词不被使用
            subset = []
            if hash_map[i] != -1:
                subset.append(strs[i])
            for j in range(i + 1, len(hash_map)):
                if hash_map[j] == hash_map[i] and hash_map[j] != -1:
                    subset.append(strs[j])
                    hash_map[j] = -1
            if subset != []:
                result.append(subset.copy())
        return result
方法二

:利用字母异位词排序之后是相同的这一特点构建哈希字典,键为排序后的单词组成的字符串

找数组或者列表中的相同元素,可以考虑用相同的属性作为dict的key,利用哈希分组。

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        word_dic = {}
        for word in strs:
            key = ''.join(sorted(word))    #用排序后的单词组成的字符作为key,因为异位词排序之后是一样的
            if key in word_dic:            #如果当前词在key中,那么key对应的value也就是word的list中加入新的word
                word_dic[key].append(word) 
            else:                          #如果当前词不在,创建当前的key对应的word的list
                word_dic[key] = [word]
        return list(word_dic.values())     #最后返回value的list

3. 最长连续序列

题目描述

解法

class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        # 利用python set的特性,因为是hashtable形式存储,查找复杂度为O(1)
        nums_set = set(nums)
        result = 0
        for num in nums_set:
            # 如果num-1不在nums_set中才说明是起点
            if num - 1 not in nums_set:
                #cur记录当前的连续长度
                cur = 1
                #循环判断num的下一个在不在set中,在的话就长度加一,并num也加一继续判断
                while num + 1 in nums_set:
                    num += 1
                    cur += 1
                #result记录最长的长度
                result = max(result, cur)
        return result

 03/06/2024

双指针

4. 移动零

题目描述

解法

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        #双指针
        i = 0
        j = 0
        #右指针一直走,所以循环主体右指针,O(n)复杂度
        while(j < len(nums)):
            #当不遇到0的时候,做交换,把0换到右边
            if nums[j] != 0:
                nums[i], nums[j] = nums[j], nums[i]
                #此时左指针才移动(操作完才移动),否则不移动
                i += 1
            j += 1
        return nums

5. 盛水最多的容器

题目描述

解法

解法一

暴力解法思想简单,遍历所有的可能组合,找面积最大的,不出意外的超时了,考虑用双指针降低时间复杂度

#暴力求解
class Solution:
    def maxArea(self, height: List[int]) -> int:
        _max = 0                                            #记录最大面积
        _cur = 0                                            #记录当前遍历的面积
        for i in range(len(height)):
            for j in range(len(height)):
                cur = min(height[i], height[j]) * (j - i)   #当前面积为高度短的边乘以宽度
                if cur > _max:
                    _max = cur
        return _max
解法二

双指针解法:

class Solution:
    def maxArea(self, height: List[int]) -> int:
        # 问题的先验信息在于 面积最大肯定是从两边往中间缩小,所以考虑左指针逐渐增加,右指针逐渐减小
        left = 0
        right = len(height) - 1

        _cur = 0                                                        #记录当前面积
        _max = 0                                                        #记录最大面积
        while(left <= right):
            _cur = (right - left) * min(height[left], height[right])    #面积计算
            if _cur >= _max:                                            #记录最大面积
                _max = _cur
            if (height[left] <= height[right]):                         #如果左边高度低,继续往右边移动,寻找更高的
                left += 1
            else:                                                       #右边高度低就往左边移动,寻找更高的
                right -= 1  
        return _max
                

6. 三数之和

题目描述

解法

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        #三数之和实际上就是用外层循环固定第一个数,变成两数之和
        nums.sort()
        result = []
        for i in range(len(nums)):
            left = i + 1                                                    #定义左右双指针
            right = len(nums) - 1                                           
            if i > 0 and nums[i] == nums[i - 1]:                            #去重,如果第一个数碰到一样的值,直接跳过
                continue
            while(left < right):                                            #双指针左右往中间移动
                if nums[i] + nums[left] + nums[right] > 0:                  #值大了,右指针左移
                    right -= 1
                elif nums[i] + nums[left] + nums[right] < 0:                #值小了,左指针右移
                    left += 1
                else:                                                       #相等的情况先去重
                    while(left < right and nums[left] == nums[left + 1]):   #左值和后面的值相等
                        left += 1
                    while(left < right and nums[right] == nums[right - 1]): #右值和前面的值相等
                        right -= 1
                    result.append([nums[i], nums[left], nums[right]])       #记录结果
                    left += 1                                               #两个指针往中间移动
                    right -= 1
        return result


03/08/2024

7. 接雨水

题目描述

解法

超时了,有几个例子没有AC,等后面有时间再补上

class Solution:
    def trap(self, height: List[int]) -> int:
        result = 0
        _max = max(height) + 1
        for i in range(1, _max):                    # 分层去遍历
            left_max = 0                            # 记录当前层左侧的最高高度
            temp = 0                                # 蓄水器
            for j in range(len(height)):            # 每个高度都遍历一遍找当前高度加了多少水
                if height[j] >= i:                  # 如果遍历到的高度大于当前层高
                    left_max = i                    # 更新左侧最高高度为当前层高
                    result += temp                  # 结果加上蓄水器中的水,因为碰到更高的就可以把蓄水器的水加到结果中
                    temp = 0                        # 重置蓄水器
                if height[j] < left_max:
                    temp += 1                       # 如果遍历到的高度小于当前层高,蓄一格水,直到后面碰到更高的柱子就把蓄水器中水加到结果中,否则就一直存在蓄水器中
        return result

滑动窗口

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

题目描述

解法

解法一

解法1:两层循环遍历,左边界的循环导致复杂度比较高,可以用字典记录每个字符前一次出现的位置来降低复杂度

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        if len(s) == 0:                                     #考虑两个边界情况
            return 0
        if len(s) == 1:
            return 1
        left = 0
        right = 0
        result = 0
        cur = 0
        while(right < len(s) - 1):                          #双指针,右指针一直移动
            while s[right + 1] in list(s[left:right + 1]):  #如果下一个字符已经在区间里面了
                left += 1                                   #循环移动左边界,直到下一个字符不在里面
            right += 1                                      #右边界向右移动一格
            cur = right - left + 1                          #记录当前的长度
            result = max(result, cur)
        return result
解法二

: 用一个字典记录每个字符所在的位置,每次left就取差找到的位置+1和自己的相比更大的那个

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        left = 0
        right = 0
        cur = 0
        result = 0
        left_index = {}
        while right < len(s):
            if s[right] in left_index:
                left = max(left, left_index[s[right]] + 1)
            left_index[s[right]] = right
            cur = right - left + 1
            result = max(result,cur)
            right += 1
        return result

9. 找到字符串中所有字母异位词

题目描述

解法

解法一

解法1 暴力求解:直接遍历一遍,比较所有的s的子串sorted之后和p sorted之后是否相等,但是超市了

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        result = []
        gap = len(p)
        for i in range(len(s) - gap + 1):
            if sorted(s[i: i + gap]) == sorted(p):
                result.append(i)
        return result

复杂度在于每次都需要排序后比较,实际上可以通过hash数组来比较,每次遍历的时候,数组对出去的字符和进来的字符进行一次更新就好了

解法二

解法2 hash + 滑动窗口的思想

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        m, n, res = len(s), len(p), []              # m,n表示串s和串p的长度  
        if m < n:                                   # 如果m < n直接返回空
            return res
        s_map = [0] * 26                            # 定义两个hash数组记录两个串的hash值
        p_map = [0] * 26
        for i in range(n):
            s_map[ord(s[i]) - ord('a')] += 1
            p_map[ord(p[i]) - ord('a')] += 1
        if s_map == p_map:                          # 如果一开始就相等,加入索引0
            res.append(0)
        for i in range(n, m):                       # 相当于窗口遍历一遍,每次去掉最左边的字母的hash值和加入最右边字母的hash值,然后比较是否相等
            s_map[ord(s[i - n]) - ord('a')] -= 1   
            s_map[ord(s[i]) - ord('a')] += 1
            if s_map == p_map:
                res.append(i - n + 1)
        return res 

子串

10. 和为k的子数组

11. 滑动窗口最大值

12. 最小覆盖子串

普通数组

13. 最大子数组和

题目描述

解法

解法一

贪心,要注意result的初始化为无穷小,这样就可以从第一个索引开始判断

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        result = float('-inf')
        cur = 0
        for i in range(len(nums)):
            cur += nums[i]
            result = max(cur, result)
            if cur < 0:
                cur = 0
        return result
解法二

动态规划

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        dp = [0] * len(nums)                #以i结尾连续子数组的最大和
        dp[0] = nums[0]
        #dp[i] = max(nums[i], dp[i - 1] + nums[i])
        for i in range(1, len(nums)):
            dp[i] =  max(nums[i], dp[i - 1] + nums[i])
        return max(dp)

14. 合并区间

题目描述

解法

解法一:
class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        intervals.sort(key = lambda x: (x[0], x[1]))                            #按区间头排序,区间头相同则按区间尾排
        result = []                                                             #记录结果
        if len(intervals) == 1:                                                 #如果区间只有一个,那么直接返回
            return intervals
        for i in range(len(intervals) - 1):
            if intervals[i + 1][0] > intervals[i][1]:                           #区间不重合的情况,就把前一个区间加入
                result.append(intervals[i])
                if i == len(intervals) - 2:                                     #如果遍历到最后一个也不重合,那就把最后一个区间也加入
                    result.append(intervals[i + 1])
            else:                                                               #区间重合了
                intervals[i + 1][0] = intervals[i][0]                           #把下一个区间的头变成两个区间最小的头,尾变成最大的尾,也就是合并
                intervals[i + 1][1] = max(intervals[i][1], intervals[i + 1][1])  
                if i == len(intervals) - 2:                                     #如果遍历到最后一个了,就把合并完的区间加入
                    result.append(intervals[i + 1])
        return result 
解法二:
class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        intervals.sort(key = lambda x : x[0])
        merged = []                                         #定义一个结果栈,把每个区间看成入栈的元素
        for i in intervals:
            if not merged or merged[-1][1] < i[0]:          #如果栈为空或者栈顶元素的右区间小于当前元素的左区间,满足入栈条件,新元素入栈
                merged.append(i)
            else:                                           #不满足条件的话,把栈顶元素的右区间更新为两者更大的那个
                merged[-1][1] = max(i[1], merged[-1][1])
        return merged

15. 轮转数组

题目描述

解法

解法1

三次反转,之前字符串换位置也用到了

def reverse(nums, left, right):
    i, j = left, right
    while(i <= j):
        nums[i], nums[j] = nums[j], nums[i]
        i += 1
        j -= 1
class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        k = k % len(nums)                               #有效反转步数
        reverse(nums, 0, len(nums) - k - 1)             #反转倒数k个前面的
        reverse(nums, len(nums) - k, len(nums) - 1)     #反转倒数k个
        reverse(nums, 0 , len(nums) - 1)                #整个反转
            
解法2

直接用python的切片交换位置

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        if step := k % len(nums):                                    #计算表达式并赋值,如果是0的话就不做操作,对应的就是0步=不动
            nums[:step], nums[step:] = nums[-step:], nums[:-step]
            

16. 除自身以外数组的乘积

题目描述

解法

不能使用除法,结果等于左边元素的乘积再乘以右边元素的乘积,第一次循环计算每个元素左边元素的乘积并保存,第二次循环记录每个元素右边元素的乘积,并乘到第一次记录的结果中作为最后的结果

class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        #不能使用除法,即每个数除自身外的乘积为左边的值乘积乘以右边的值乘积
        ans = [1] * len(nums)
        tmp = 1
        for i in range(1, len(nums)):
            ans[i] = ans[i - 1] * nums[i - 1]
        for i in range(len(nums) - 2, -1, -1):
            tmp *= nums[i + 1]
            ans[i] *= tmp
        return ans

17. 缺失的第一个正数

题目描述

解法

哈希 set O(1)复杂度

class Solution:
    def firstMissingPositive(self, nums: List[int]) -> int:
        nums = set(nums)        #转化为hash set 查找复杂度O(1)
        i = 1
        import sys
        while(i < sys.maxsize):
            if i in nums:
                i += 1
            else:
                return i

矩阵

18. 矩阵置零

19. 螺旋矩阵

20. 旋转图像

21. 搜索二维矩阵Ⅱ

链表

22. 相交链表

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
        visited_A = set()            #集合记录A链表中所有节点
        while(headA):
            visited_A.add(headA)
            headA = headA.next
        while(headB):                #遍历B,第一个在A中的节点就是入口
            if headB in visited_A:
                return headB
            headB = headB.next
        return None
class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        # 处理边缘情况
        if not headA or not headB:
            return None
        
        # 在每个链表的头部初始化两个指针
        pointerA = headA
        pointerB = headB
        
        # 遍历两个链表直到指针相交
        while pointerA != pointerB:
            # 将指针向前移动一个节点
            pointerA = pointerA.next if pointerA else headB
            pointerB = pointerB.next if pointerB else headA
        
        # 如果相交,指针将位于交点节点,如果没有交点,值为None
        return pointerA


# 大神的解法,核心在于让两个指针都走A+B的长度,或者是说相交前的长度

23. 反转链表

题目描述

解法

class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        left = None            #左指针指向空
        right = head           #右指针指向头节点
        while(right):          #右指针不到最后一个
            tmp = right.next   #记录右指针下一个节点的位置
            right.next = left  #右指针的节点的next为左指针指向的位置
            left = right       #移动左右指针
            right = tmp
        return left

24. 回文链表

25. 环形链表

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        visited = set()            #集合记录每个visited过的节点
        while(head):                
            if head in visited:    #如果已经纪录过就retrun True
                return True
            visited.add(head)      #遍历把节点加入到集合中
            head = head.next
        return None

26. 环形链表Ⅱ

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        slow = head                    #慢指针一次移动一个节点
        fast = head                    #快指针一次移动两个节点
        while(fast and fast.next):     #当快指针不为空或者下一步不为空
            slow = slow.next           #移动两个指针
            fast = fast.next.next
            if slow == fast:           #如果相遇了,此时从相遇节点出发一个节点
                slow = head            #也从头节点出发一个节点
                while(slow != fast):   #再次相遇的时候,头节点出发的节点就会到环入口处
                    slow = slow.next
                    fast = fast.next
                return slow
        return None

# x:头节点到环入口节点的距离
# y:环入口节点到第一次相遇节点的距离
# z:环内剩下节点

# x+y: 慢指针到相遇时走过的节点个数
# x+y+n(y+z): 快指针到相遇时走过的节点个数,n为多走的环圈数
# 2(x+y) = x+y + n(y+z) :慢指针因为一次走一个,所以需要两倍
# x=(n-1)(y+z) + z
# if n==1: x=z 即代码的结论

27. 合并两个有序链表

28. 两数相加

class Solution:
    def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        cur = Dummy = ListNode(-1)                                    #定义新的头节点
        carry = 0                                                     #记录每次的和
        while(l1 or l2 or carry):                                     #如果有进位或者还有没加完的值
            carry += (l1.val if l1 else 0) + (l2.val if l2 else 0)    #carry记录当前和
            cur.next = ListNode(carry % 10)                           #定义下一个节点为carry % 10
            carry //=10                                               #还余下的值为进位,到下一轮求和
            cur = cur.next                                            #顺序推进一个个进行
            if l1: l1 = l1.next
            if l2: l2 = l2.next
        return Dummy.next

29. 删除链表的倒数第N个结点

class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        DummyHead = ListNode(next = head)        #定义虚拟头节点
        left = DummyHead                         #快慢指针
        right = head
        for _ in range(n):                       #让快指针先走n步
            right = right.next
        while(right):                            #此时快慢指针同时走
            right = right.next
            left = left.next
        left.next = left.next.next               #快指针到最后的时候 慢指针就指向要删除节点的前一个
        return DummyHead.next

30. 两两交换链表中的节点

class Solution:
    def swapPairs(self, head: ListNode) -> ListNode:
        dummyHead = ListNode(0)                #虚拟头节点
        dummyHead.next = head
        temp = dummyHead                       #指针指向虚拟头节点
        while temp.next and temp.next.next:    #当下一个和下下个都存在
            node1 = temp.next                  #定义需要交换的两个节点,避免混淆
            node2 = temp.next.next    
            temp.next = node2                  #temp指向第二个节点
            node1.next = node2.next            #第一个节点的下一个指向第二个节点的下一个
            node2.next = node1                 #第二个节点的下一个指向节点一
            temp = node1                       #temp指向接下来要交换的两个节点的前一个,即node1
        return dummyHead.next

31. K个一组翻转链表

32. 随机链表的复制

33. 排序链表

34. 合并K个升序链表

35. LRU缓存

二叉树

36. 二叉树中序遍历

 递归法:

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        result = []
        def Traversal(result, cur):
            if cur == None:
                return
            Traversal(result, cur.left)
            result.append(cur.val)
            Traversal(result, cur.right)
        Traversal(result, root)
        return result

37. 二叉树最大深度

class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        def traversal(root):
            if not root:
                return 0
            left_length = traversal(root.left)
            right_length = traversal(root.right)
            return max(left_length, right_length) + 1   #抽象为最小子问题的话,深度就是左右子树的深度最大的+1
        return traversal(root)

38. 翻转二叉树

class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        def invert(root):           #深搜
            if not root:
                return              #空节点返回
            tmp = root.left         #针对递归的最小子问题实现翻转
            root.left = root.right
            root.right = tmp    
            invert(root.left)       #递归函数分别实现左右子树
            invert(root.right)
        invert(root)
        return(root)

39. 对称二叉树

class Solution:
    def isSymmetric(self, root: Optional[TreeNode]) -> bool:
        def dfs(root1, root2):                      #分别深搜左右节点,返回是否对称
            if not root1 and not root2:             #左右都为空 返回True
                return True
            elif not root2 and root1:               #左右不相等 返回False
                return False
            elif not root1 and root2:
                return False
            elif root1.val != root2.val:
                return False
            flag1 = dfs(root1.left, root2.right)    #深搜左子树的左节点和右子树的右节点
            flag2 = dfs(root1.right, root2.left)    #左子树的右节点和右子树的左节点
            return (flag1 and flag2)                #两个都对称的话返回true
        return dfs(root.left, root.right)

40.二叉树直径

class Solution:
    def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
        def dfs(root, result):          #返回两个左树和右树深度和, result 记录最大值
            if not root:
                return -1
            left_length = dfs(root.left, result) + 1
            right_length = dfs(root.right, result) + 1
            max_length = left_length + right_length
            result.append(max_length)
            return max(left_length, right_length)
        result = []
        dfs(root, result)
        return max(result)

41. 二叉树层序遍历

class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        result = []
        if not root:
            return result
        from collections import deque
        q = deque()
        q.append(root)
        while q:
            subset = []
            for _ in range(len(q)):
                cur = q.popleft()
                if cur.left:
                    q.append(cur.left)
                if cur.right:
                    q.append(cur.right)
                subset.append(cur.val)
            result.append(subset)
        return result

42. 平衡二叉树

class Solution:
    def isBalanced(self, root: Optional[TreeNode]) -> bool:
        def dfs(root):
            if not root:
                return 0
            left_height = dfs(root.left)
            right_height = dfs(root.right)
            if left_height == -1 or right_height == -1 or left_height - right_height > 1 or right_height - left_height > 1:
                return -1
            else:
                return max(left_height, right_height) + 1
        return dfs(root) >= 0

回溯

55. 全排列

56. 子集

题目描述

解法

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        def back_tracking(result, subset, nums, start_index):
            result.append(subset.copy())
            for i in range(start_index, len(nums)):
                subset.append(nums[i])
                back_tracking(result, subset, nums, i + 1)
                subset.pop()
        result = []
        subset = []
        start_index = 0
        back_tracking(result, subset, nums, start_index)
        return result

57. 电话号码的字母组合

58. 组合总和

题目描述

解法

标准的回溯算法解法,不能重复使用,所以start_index为i + 1

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        candidates.sort()                                                       #排序,从小到大
        def back_tracking(candidates, target, result, subset, start_index):     #组合问题 回溯
            if sum(subset) == target:                                           #子集和为target, 记录结果
                result.append(subset.copy())
                return
            if sum(subset) > target:                                            #和大于target, 直接返回
                return
            for i in range(start_index, len(candidates)):                       #横向遍历,每次往subset中加一个candidate
                subset.append(candidates[i])
                back_tracking(candidates, target, result, subset, i)            #因为可以重复选用, start_index就从i开始,不能的话就i+1
                subset.pop()                                                    #回溯返回之后回退一个
        result = []
        subset = []
        back_tracking(candidates, target, result, subset, 0)
        return result

59. 括号生成

60. 单词搜索

61. 分割回文串

62. N 皇后

二分查找

63. 搜索插入位置

64. 搜索二维矩阵

65. 排序数组中查第一个和最后一个位置

66. 搜索旋转排序数组

67. 旋转排序数组最小值

68. 两个正序数组中位数

69. 有效的括号

70. 最小栈

71. 字符串解码

72. 每日温度

73. 柱状图中最大的矩形

74. 数组中的第K个最大元素

75. 前K个高频元素

题目描述

解法

class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        hash_ = {}
        for i in range(len(nums)):                                     #字典统计每个词出现的频率
            if nums[i] not in hash_:
                hash_[nums[i]] = 1
            else:
                hash_[nums[i]] += 1
        hash_sort = dict(sorted(hash_.items(), key = lambda x: -x[1])) #对字典按value排序,即按出现的频率排序,注意sorted函数返回值是list,可转化回dict
        result = []
        for i, val in enumerate(hash_sort.keys()):                     #打印前k个key,就是前k个高频元素
            if i >= k:
                break
            result.append(val)
        return result
            

76. 数据流的中位数

贪心算法

77. 买卖股票的最佳时机

题目描述

解法

解法一

贪心算法:

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        max_profit = 0                              #记录最大收益
        cur_profit = 0                              #记录当前累计的收益
        for i in range(1, len(prices)):
            day_profit = prices[i] - prices[i - 1]  #每日收益
            cur_profit += day_profit                #每日收益累加
            if cur_profit < 0:                      #如果收益小于0, 直接置零,含义是之前不买卖
                cur_profit = 0
            max_profit = max(max_profit, cur_profit)#记录最大的累积收益,和连续最大子数组和比较类似,数组的元素就是每日的profit
        return max_profit
解法二

动态规划:dp数组记录两个状态,即每天持有和不持有的手里最大现金

每天持有的手里最大现金应该是昨天就持有dp[i-1][1]和当天买入0-nums[i]

因为只能买卖一次,所以当天买入前的手里现金就是0

如果买卖多次的话,当天买入就是dp[i-1][0] - nums[i]

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        #dp[i][0] 第i天不持有股票 手里的最大现金
        #dp[i][1] 第i天持有股票 手里的最大现金
        # dp[i][0] = max(dp[i-1][0], dp[i - 1][1] + nums[i])
        # dp[i][1] = max(dp[i-1][1], dp[i - 1][0] - nums[i])
        # dp[0][0] = 0
        # dp[0][1] = -nums[0]
        dp = [[0] * 2 for i in range(len(prices))]
        dp[0][1] = -prices[0]
        for i in range(1, len(prices)):
            dp[i][0] = max(dp[i-1][0], dp[i - 1][1] + prices[i])
            dp[i][1] = max(dp[i-1][1], 0 - prices[i])
        return dp[-1][0]

78. 跳跃游戏

题目描述

解法

class Solution:
    def canJump(self, nums: List[int]) -> bool:
        if len(nums) == 1:
            return True
        max_length = 0                                  #每次更新一下最远能到的索引
        i = 0
        while i <= max_length:                          #i如果小于这个索引
            max_length = max(max_length, i + nums[i])   #更新索引为更大的值
            if max_length >= len(nums) - 1:             #如果索引超过了数组最后的索引
                return True                             #能到达
            i += 1      
        return False                                    #最后没到的话返回False

79. 跳跃游戏2

题目描述

解法

class Solution:
    def jump(self, nums: List[int]) -> int:
        if len(nums) == 1:
            return 0
        max_length = 0              #已遍历过的元素能到的最远距离
        i = 0
        count = 0
        cur = 0                     #当前步骤能到的最远距离
        while(i < len(nums)):
            max_length = max(max_length, i + nums[i])
            if i == cur:                                #如果i走到了当前步骤能到的最远距离
                cur = max_length                        #要跳跃了,更新cur为max_length
                count += 1                          
                if max_length >= len(nums) - 1:         #如果max_length大于最后一个值的索引,返回结果
                    return count
            i += 1
        

80.划分字母区间

题目描述

解法

class Solution:
    def partitionLabels(self, s: str) -> List[int]:
        max_right_index = [0] * 26                      #哈希数组记录每个字符最右边到达的位置
        for i in range(len(s)):
            max_right_index[ord(s[i]) -  ord('a')] = i  #遍历记录
        left, right = 0, 0                              #两个指针用来计算区间长度
        max_right = 0
        result = []                                     #结果数组
        while(right < len(s)):                          #右指针持续移动,每次更新已经遍历过的字符最右边的那个在哪
            max_right = max(max_right, \
            max_right_index[ord(s[right]) - ord('a')])  
            if right == max_right:                      #如果右指针走到了已经遍历过的字符最右边的那个
                result.append(right - left + 1)         #记录当前区间长度
                left = right + 1                        #更新区间七点
            right += 1
        return result

动态规划

81. 爬楼梯

题目描述

解法

class Solution:
    def climbStairs(self, n: int) -> int:
        # dp: 到达第i个台阶有多少种方法
        # dp[i] = dp[i - 1] + dp[i - 2]     到达第i个台阶要么从i-1来,要么从i-2来
        dp = [0] * (n + 2)
        dp[1] = 1                       
        dp[2] = 2
        if n <= 2:
            return n
        for i in range(3, n + 1):
            dp[i] = dp[i - 1] + dp[i - 2]
        return dp[n]

82. 杨辉三角

题目描述

解法

class Solution:
    def generate(self, numRows: int) -> List[List[int]]:
        #dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]         #dp递推公式,每一行元素等于左上角加右上角
        dp = [[1] * (i + 1) for i in range(numRows)]        #初始化三角数组  
        if numRows < 3:                                     #1行或者2行直接返回dp数组
            return dp
        for i in range(2, numRows):                         #大于两行,递推一下
            for j in range(1, i):
                dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]
        return dp

83. 打家劫舍

题目描述

解法

class Solution:
    def rob(self, nums: List[int]) -> int:
        #dp[i] 偷到第i间房屋能偷到的最大金额
        #dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]
        if len(nums) == 1:                                  #两个边界条件返回
            return nums[0]
        if len(nums) == 2:
            return max(nums[0], nums[1])
        dp = [0] * (len(nums) + 1)                          #dp数组表示偷到第i间房屋能偷到的最大金额
        dp[0] = nums[0]
        dp[1] = max(nums[0], nums[1])                       #初始化dp数组
        for i in range(2, len(nums)):
            dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])     #递推
        return dp[len(nums) - 1]

拓展:

213 打家劫舍2️⃣ 首尾房屋相邻

class Solution:
    def rob(self, nums: List[int]) -> int:
        # dp[i] 偷到第i间屋子的最大金额
        # dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])
        # 和第一题的区别在于首位房屋相邻,分别考虑两种情况 即偷第一家不偷最后一家和不偷第一家 偷最后一家
        dp = [0] * (len(nums) + 1)
        if len(nums) == 1:
            return nums[0]
        if len(nums) == 2:
            return max(nums[0], nums[1])
        dp[0] = nums[0]
        dp[1] = max(nums[0], nums[1])
        # candidate_1 偷第一家不偷最后一家
        for i in range(2, len(nums) - 1):
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])
        candidate_1 = dp[len(nums) - 2]
        # candidate_2 不偷第一家偷最后一家
        dp = [0] * (len(nums) + 1)
        dp[0] = 0
        dp[1] = nums[1]
        for i in range(2, len(nums)):
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])
        candidate_2 = dp[len(nums) - 1]

        return max(candidate_1, candidate_2)

337 打家劫舍3️⃣ 二叉树

84. 完全平方数

题目描述

解法

class Solution:
    def numSquares(self, n: int) -> int:
        dp = [0] + [float('inf')] * n                   #计算最小数量的dp数组初始化
        for i in range(int(n ** 0.5) + 1):              #遍历物品,物品是正整数的平方,由于int向下取整,所以+1
            for j in range(i ** 2, n + 1):              #遍历背包
                dp[j] = min(dp[j], dp[j - i ** 2] + 1)  #递推公式,求装满背包的最少数量
        return dp[-1] if dp[-1] != float('inf') else -1 #如果不是完全平方数就return -1

85. 零钱兑换

题目描述

解法

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        # dp[j] 把背包装满的最少硬币个数
        # dp[j] = min(dp[j - coin] + 1, dp[j]) 装当前硬币的最少个数 +1 和不装当前硬币的最少个数中取最小的
        dp = [0] + [float('inf')] * amount
        for coin in coins:
            for j in range(coin, amount + 1):
                dp[j] = min(dp[j], dp[j - coin] + 1)
        if dp[-1] == float('inf'):
                    return -1
        return dp[-1]

86. 单词拆分

题目描述

解法

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        #dp[j]: 从word中任取,能否能把0-j的背包{字符串切片}装满
        dp = [True] + [False] * len(s)
        for i in range(len(s) + 1):
            for j in range(i + 1):
                if dp[j] and s[j:i] in wordDict:
                    dp[i] = True
        return dp[-1]

87. 最长递增子序列

题目描述

解法

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        #dp[j]以j结尾的最长递增子序列的长度
        dp = [1] * len(nums)                        #初始化为1
        for j in range(len(nums)):                  #顺序遍历J
            for i in range(j):                      #每次需要找j前面所有的元素,以其结尾的最长递增子序列的长度,如果nums[j]大于结尾元素,更新当前长度为最长的那个+1
                if nums[j] > nums[i]:
                    dp[j] = max(dp[j], dp[i] + 1)   #这里需要max是要找前面更新过的dp[j]中的最长的,因为nums[j] > nums[i]的情况很多,之前dp[j]可能就已经更新过了,这里取一个max可以保证最后取到的是最长的
        return max(dp)

88. 乘积最大子数组

题目描述

解法

class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        max_i = nums[0]                                    #记录当前遍历元素的最大乘积
        min_i = nums[0]                                    #记录当前遍历元素的最小乘积(考虑负数情况)
        result = nums[0]                                   #记录返回结果
        for i in range(1, len(nums)):
            mx = max_i                                     #当前记录的最大乘积,用于下面的计算
            mn = min_i                                     #当前记录的最小乘积,同上
            max_i = max( nums[i] * mx, nums[i], nums[i] * mn)   #下一个最大乘积从上一个最大乘积乘以当前值、上一个最小乘积乘以当前值、当前值三个中选最大
            min_i = min( nums[i] * mn, nums[i], nums[i] * mx)   #更新下一个最小乘积
            result = max(result, max_i)                         #记录最大结果
        return result

89. 分割等和子集

题目描述

解法

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        val = sum(nums) / 2                            #背包容量
        if val != int(val):                            #如果背包容量不为整数,返回False
            return False
        val = int(val)
        dp = [0] * (val+1)                             #初始化背包
        for num in nums:                               #先遍历物品
            for j in range(val, num - 1, -1):          #后倒序(只能拿取一次0-1背包)遍历背包
                dp[j] = max(dp[j], dp[j - num] + num)  #经典递推公式,装与不装当前物品的最大价值,这里价值和重量都是num
        return dp[-1] == val                           #背包的最大价值是否等于val,等于说明装满了

90. 最长有效括号

题目描述

解法

多维动态规划

91. 不同路径

题目描述

解法

92. 最小路径和

题目描述

解法

class Solution:
    def minPathSum(self, grid: List[List[int]]) -> int:
        # dp[i][j] = min(dp[i-1][j], dp[i][j - 1]) + grid[i][j] 动规递推公式,dp[i][j]表示到达[i][j]的最小路径和,包含grid[i][j]
        dp = [[0] * len(grid[0]) for _ in range(len(grid))]
        ####初始化dp数组,第一行累加,第一列累加
        if len(grid) <= 1 and len(grid[0]) <=1 :
            return grid[0][0]
        cur = grid[0][0]
        for i in range(1, len(grid)):
            cur += grid[i][0]
            dp[i][0] = cur
        cur = grid[0][0]
        for j in range(1, len(grid[0])):
            cur += grid[0][j]
            dp[0][j] = cur
        #递推
        for i in range(1, len(grid)):
            for j in range(1, len(grid[0])):
                dp[i][j] = min(dp[i-1][j], dp[i][j - 1]) + grid[i][j]
        return dp[-1][-1]

93. 最长回文子串

题目描述

解法

class Solution:
    def longestPalindrome(self, s: str) -> str:
        max_len = 0                                        #记录当前遍历字串的长度
        for j in range(len(s) + 1):                        #快指针一直走
            for i in range(j + 1):                         #慢指针每次都从头遍历到快指针
                st = s[i:j+1]                              #截出当前字串
                if st == st[::-1] and len(st) >= max_len:  #如果是回文子串并且长度大于已记录的长度
                    result = st                            #更新结果为当前最大字串
                    max_len = len(st)                      #更新最大长度

        return result

94. 最长公共子序列

题目描述

解法

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        #dp[i][j] 以i-1 和j-1字符结尾的两个序列拥有的最长公共子序列长度
        dp = [[0]*(len(text1) + 1) for _ in range(len(text2) + 1)]
        #这里要+1是因为dp[i]表示以i-1结尾,所以最后一个dp[len(text1)]表示len(text1) - 1结尾的字符
        for i in range(1, len(text2) + 1):        #遍历到len(text) 索引
            for j in range(1, len(text1) + 1):
                if text2[i - 1] == text1[j - 1]:  #如果i-1和j-1两个字符相等,因为dp数组定义,所以比较这两个
                    dp[i][j] = dp[i - 1][j - 1] + 1    #以i-1和j-1字符结尾的是i-1和j-1结尾的+1
                else: #不相等的情况要考虑是因为本题可以不连续,所以在不连续的时候要把之前的最大值保留下来
                    dp[i][j] = max(dp[i][j - 1], dp[i - 1][j])
        return dp[-1][-1]

95. 编辑距离

题目描述

解法

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        #dp[i][j]: 以i-1结尾的word1的字串变到以j-1结尾的word2字串的最小操作数
        dp = [[0] * (len(word2) + 1) for _ in range(len(word1) + 1)]
        # 初始化 空字符串变到另一个串需要的操作数
        for i in range(len(word1) + 1):
            dp[i][0] = i
        for j in range(len(word2) + 1):
            dp[0][j] = j

        #和最长公共子序列类似
        for i in range(1, len(word1) + 1):
            for j in range(1, len(word2) + 1):
                #如果下两个字符相等,不用操作
                if word1[i - 1] == word2[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1]
                #不相等的话,从删word1 删word2和替换三种操作中选最小的+1
                else:
                    dp[i][j] = min(dp[i - 1][j], dp[i - 1][j - 1], dp[i][j - 1]) + 1
        return dp[-1][-1]

技巧

96. 只出现一次的数字

题目描述

解法

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        # ^异或算符,不同返回1 相同返回0
        # 任何数和0异或还是这个数
        # 这题就把所有数异或一下,最后剩下的就是那个独一无二的
        # 异或还可以用来判断奇偶,异或1 返回1就是奇数 返回0就是偶数,因为二进制偶数的最低位肯定是0,而奇数是1,异或一下就判断奇偶了
        cur = 0
        for num in nums:
            cur ^= num
        return cur

97. 多数元素

题目描述

解法

98. 颜色分类

题目描述

解法

99. 下一个排列

题目描述

解法

100. 寻找重复数

题目描述

解法

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值