Python数据结构及算法刷题记录

Array数组

485. 最大连续 1 的个数

class Solution:
    def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
        # 如果数组为空或长度为0,则直接返回0
        if nums is None or len(nums) == 0:
            return 0
        # count为当前连续的1的个数,res是上一个连续的1的个数
        count = 0 
        res = 0
        for num in nums:
            if num == 1:
                count += 1 # 遍历到的num为1则count+1
            else:
                res = max(res, count) # 遇到0后,将res和count中的最大值赋值给res
                count = 0 # count重新为0
        return max(res, count) # 因为最后一个数字为1时,并没有进行res=max(res,count),所以返回时需要再max一下

283. 移动零

1、两次遍历

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        # 两次遍历
        index = 0
        # 第一次遍历,将所有非0的元素均赋值给nums[index]
        for i in range(len(nums)):
            if nums[i] != 0:
                nums[i], nums[index] = nums[index], nums[i]
                index += 1
        # 第二次遍历,将索引index之后的元素全赋值为0
        for i in range(index,len(nums)):
            nums[i] = 0
        return nums

2、一次遍历

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        # 一次遍历
        # 两个指针i和j
        j = 0
        for i in range(len(nums)):
            # 当前元素!=0,就把其交换到左边,等于0的交换到右边
            if nums[i] != 0:
                nums[j],nums[i] = nums[i],nums[j]
                j += 1

27. 移除元素

1、首尾双指针

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        # 首尾双指针
        if nums is None or len(nums) == 0:
            return 0
        l = 0
        r = len(nums) - 1
        while(l < r):
            # l从头到尾找等于val的元素的索引
            while(l < r and nums[l] != val):
                l += 1
            # r从尾到头找不等于val的元素的索引
            while(l < r and nums[r] == val):
                r -= 1
            nums[l], nums[r] = nums[r], nums[l]
        # 因为跳出循环时,i,j可能都指向val或不指向val
        if nums[l] == val:
            return l
        else:
            return l+1

2、快慢双指针

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        # 快慢指针
        if not nums:
            return 0       
        n = len(nums)
        fast = slow = 0 #两个指针初始位置都为0,fast指针用来遍历数组
        while fast < n:
            if nums[fast] != val: #当fast指向的元素不等于val时,将fast的值赋予slow
                nums[slow] = nums[fast]
                slow += 1
            fast += 1        
        return slow

1. 两数之和

1、暴力解法:时间复杂度为N(N*N)

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

2、哈希字典

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        hashtable = {}  # hashmap = dict(),创建一个空字典
        for i, num in enumerate(nums): # enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for循环当中。
            if target - num in hashtable: # 循环遍历num时,如果hashtable中已经有另一个元素(target-num)在哈希表中,则直接返回这两个元素的索引
                return [hashtable[target - num], i]
            hashtable[num] = i # 否则将该元素和索引添加到字典中
        return []

268. 丢失的数字

 1、排序

class Solution:
    def missingNumber(self, nums: List[int]) -> int:
        # 将数组排序之后,即可根据数组中每个下标处的元素是否和下标相等,得到丢失的数字。
        nums.sort()
        for i, num in enumerate(nums):
            if num != i:
                return i
        return len(nums)

2、哈希集合

class Solution:
    def missingNumber(self, nums: List[int]) -> int:
        # 哈希集合:首先遍历数组nums,将数组中的每个元素加入哈希集合,然后依次检查从0到n的每个整数是否在哈希集合中,不在哈希集合中的数字即为丢失的数字。
        s = set(nums)
        for i in range(len(nums) + 1):
            if i not in s:
                return i

78. 子集

1、使用库函数

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        # itertools模块combinations(iterable, r)方法可以创建一个迭代器,返回iterable中所有长度为r的子序列,返回的子序列中的项按输入iterable中的顺序排序。
        res = []
        for i in range(len(nums) + 1):
            for num in itertools.combinations(nums, i):
                # 子序列默认是元组,所以将其转化为list
                res.append(list(num))
        return res

Linked List链表

203. 移除链表元素

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeElements(self, head: ListNode, val: int) -> ListNode:
        # 初始化一个伪节点dummy,和双指针prev、cur
        dummy = ListNode(0)
        dummy.next = head
        prev, cur = dummy, head
        while cur: # 当head不为空时,进入循环
            if cur.val == val: # 当head指向的值等于val时,进行删除操作
                prev.next = cur.next
                cur = cur.next
            else: # 当head指向的值不等于val时,prev和head依次向后移
                prev = cur
                cur = cur.next
        return dummy.next

206. 反转链表

1、双指针

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

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        # 双指针
        pre, cur = None, head  # 初始化两个指针,一个指向头节点,一个指向None
        while cur: # 当cur指向None时,跳出循环
            tmp = cur.next  # 临时指针指向cur的后继节点
            cur.next = pre  # 断开原来cur的后继节点,使其指向pre
            pre = cur  # pre指向cur
            cur = tmp  # cur指向tmp,即一次循环内cur的后继节点
        return pre

2、头插法

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

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        # 头插法,往cur节点前插入节点
        cur = None
        while head:
            newNode = ListNode(head.val)  #将原始列表的节点的val形成一个节点
            head = head.next # head指向下一个节点
            newNode.next = cur # newNode.next指向cur
            cur = newNode # cur指向新的节点
        return cur

 Queue队列

933. 最近的请求次数

class RecentCounter:

    def __init__(self):
        # 使用列表初始化一个队列
        self.lst = []
        
    def ping(self, t: int) -> int:
        # 将t入队
        self.lst.append(t)
        # 如果t与队列头元素差值大于3000,需要将对头出队,因为题目解释需要返回过去 3000 毫秒内发生的所有请求数(包括新请求)
        while t - self.lst[0] > 3000:
            self.lst.pop(0)
        # 最后返回队列长度即可
        return len(self.lst)

# Your RecentCounter object will be instantiated and called as such:
# obj = RecentCounter()
# param_1 = obj.ping(t)

 Stack栈

20. 有效的括号

class Solution:
    def isValid(self, s: str) -> bool:
        dict = {')':'(',']':'[','}':'{'}
        stack = []
        for c in s:
            if stack and c in dict: # 如果栈不为空并且c为dict中的某一个键,为什么要加一个stack是否为空的判断:因为如果为空stack[-1]会报错
                if stack[-1] == dict[c]: # 判断栈顶元素和键c的值相等
                    stack.pop()
                else:
                    return False
            else: # 如果栈为空或c不在dict中,说明c为左括号,将其入栈
                stack.append(c)
        return not stack # 为什么返回not stack:因为如果最后stack中还存在元素,则说明s中元素为奇数个,并未完全匹配,则返回False;stack为空则说明都匹配完了,not stack = True

496. 下一个更大元素 I

1、暴力遍历

class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        len1, len2 = len(nums1), len(nums2)
        res = [0] * len1 #  构造一个和数组nums1等长的数组
        for i in range(len1): # 依次遍历nums1中的元素
            j = nums2.index(nums1[i]) # 找到本次目标元素在nums2中和nums1相等的元素的坐标
            k = j + 1 # k:往右边找
            while k < len2 and nums2[k] < nums2[j]: # k小于nums2的长度且小于该目标元素,则k继续加1,继续往右找
                k += 1
            if k < len2: # 加这个判断是因为如果遍历到nums2中的最后一个元素比目标元素小,也会进行k自加1,这时表明未找到下一个更大的元素
                res[i] = nums2[k]
            else:
                res[i] = -1
        return res

2、单调栈+哈希表

class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        # 单调栈+哈希表解法
        res = {} # 新建一个哈希字典
        stack = [] # 新建一个栈
        lst = [] # 存储最后结果
        for num in nums2: # 依次遍历nums2中的元素
            while stack and num > stack[-1]: # 判断stack不为空且num大于栈顶元素,则找到了num为目前栈顶元素的下一个更大元素,将栈顶元素作为key,num作为value添加到哈希字典中
                res[stack.pop()] = num
            # 否则将num入栈
            stack.append(num)
        # 然后依次遍历nums1中的元素
        for num in nums1:
            lst.append(res.get(num, -1))
        return lst
        '''
语法
get()方法语法:
dict.get(key, default=None)
参数
key -- 字典中要查找的键。
default -- 如果指定键的值不存在时,返回该默认值。
返回值
返回指定键的值,如果键不在字典中返回默认值 None 或者设置的默认值。
        '''

 Hash Table 哈希表

217. 存在重复元素

1、排序

class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        # 排序:在对数字从小到大排序之后,数组的重复元素一定出现在相邻位置中。因此,我们可以扫描已排序的数组,每次判断相邻的两个元素是否相等,如果相等则说明存在重复的元素。
        nums.sort()
        for i in range(1,len(nums)):
            if nums[i] == nums[i - 1]:
                return True
        return False

2、哈希表

class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        # 哈希表(集合):遍历数组,数字放到 set 中。如果数字已经存在于 set 中,直接返回 true。如果成功遍历完数组,则表示没有重复元素,返回 false。
        hashset = set()
        for num in nums:
            if num not in hashset:
                hashset.add(num)
            else:
                return True
        return False

389. 找不同

1、使用哈希表

class Solution:
    def findTheDifference(self, s: str, t: str) -> str:
        # 如果s为空,则直接返回t
        if len(s) == 0:
            return t
        # 初始化一个hashtable
        hashtable = [0] * 26
        # 遍历s,遍历到s中的每个元素,对应索引下hashtable中的值-1
        for i in s:
            tmp = ord(i) - 97
            hashtable[tmp] = hashtable[tmp] - 1
        # 遍历t,遍历到t中的每个元素,对应索引下hashtable中的值+1
        for j in t:
            tmp = ord(j) - 97
            hashtable[tmp] = hashtable[tmp] + 1
        # 最后遍历hashtable,值为1的元素就是我们要找的元素
        for k in range(len(hashtable)):
            if hashtable[k] == 1:
                ascii = k + 97
                return chr(ascii)

2、取巧,使用ascii

class Solution:
    def findTheDifference(self, s: str, t: str) -> str:
        # sum1为s中字母ascii的和
        sum1 = sum([ord(i) for i in s])
        # sum2为t中字母ascii的和
        sum2 = sum([ord(i) for i in t])
        # sum2-sum1就是添加的那个字母的ascii
        return chr(sum2 - sum1)

 Set 集合

217. 存在重复元素

class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        #  集合是无序可变元素不能重复。实际上集合底层是字典实现,集合的所有元素都是字典中的“键对象”,因此是不能重复的且唯一
        hashset = set(nums)
        if len(nums) != len(hashset):
            return True
        else:
            return False

705. 设计哈希集合

class MyHashSet:

    def __init__(self):
        # 因为0 <= key <= 10^6,所以可以设计一个超大数组,如果没有范围,则不能使用该方法
        self.set = [False] * 1000001

    def add(self, key: int) -> None:
        '''添加,将对应索引的值置为True'''
        self.set[key] = True

    def remove(self, key: int) -> None:
        '''删除,将对应索引的值置为False'''
        self.set[key] = False

    def contains(self, key: int) -> bool:
        '''是否存在,直接返回对应索引的值True or False'''
        return self.set[key]


# Your MyHashSet object will be instantiated and called as such:
# obj = MyHashSet()
# obj.add(key)
# obj.remove(key)
# param_3 = obj.contains(key)

Tree 树

102. 二叉树的层序遍历

class Solution(object):
    def levelOrder(self, root):
        """
        :type root: TreeNode
        :rtype: List[List[int]]
        """
        # 如果树为空,直接返回[]
        if not root:
            return []
        # 初始化一个res数组存储最后结果
        res = []
        # 初始化一个的队列用于存储每层的遍历结果,初始存储根节点
        queue = [root]
        while queue:
            # 获取当前队列的长度,这个长度相当于 当前这一层的节点个数
            size = len(queue)
            tmp = []
            # 将队列中的元素都拿出来(也就是获取这一层的节点),放到tmp中
            # 如果节点的左/右子树不为空,也放入队列中
            for _ in range(size):
                r = queue.pop(0)
                tmp.append(r.val)
                if r.left:
                    queue.append(r.left)
                if r.right:
                    queue.append(r.right)
            # 将临时list加入最终返回结果中
            res.append(tmp)
        return res

144. 二叉树的前序遍历

1、递归

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        # 递归解法,前序遍历:根-左-右
        res = []
        def preorder(root: TreeNode):
            if not root:
                return []
            res.append(root.val)
            preorder(root.left)
            preorder(root.right)
        preorder(root)
        return res

2、迭代

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        # 递归,使用栈(先进后出)
        if not root:
            return []
        cur, stack, res = root, [], []
        while cur or stack:
            while cur: # 该循环的目的是将根节点和左孩子全部入栈
                res.append(cur.val) # 将节点元素存储在res中
                stack.append(cur)
                cur = cur.left
            # 每弹出一个元素,cur重新执行该元素的右孩子
            tmp = stack.pop()
            cur = tmp.right
        return res

94. 二叉树的中序遍历

1、递归(左根右)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        def inorder(root: TreeNode):
            if not root:
                return []
            inorder(root.left)
            res.append(root.val)
            inorder(root.right)
        inorder(root)
        return res

2、迭代

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        # 递归,使用栈(先进后出)
        if not root:
            return []
        cur, stack, res = root, [], []
        while cur or stack:
            while cur: # cur入栈,并到达最左端的叶子节点    
                stack.append(cur)
                cur = cur.left
            tmp = stack.pop()
            res.append(tmp.val) # 出栈时加入res
            cur = tmp.right
        return res

145. 二叉树的后序遍历

1、递归(左右根)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        #前序遍历:根-左-右
        #中序遍历:左-根-右
        #后序遍历:左-右-根
        res = []
        def  postorder(root: TreeNode):
            if not root:
                return
            postorder(root.left)
            postorder(root.right)
            res.append(root.val)
        postorder(root)
        return res

2、迭代

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if not root:
            return []
        cur, stack, res = root, [], []
        while cur or stack:
            while cur: # 将右子节点存进stack
                res.append(cur.val)
                stack.append(cur)
                cur = cur.right
            tmp = stack.pop()
            cur = tmp.left
        return res[::-1]

 Heap 堆

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

import heapq
class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        # 初始化一个堆
        heap = []
        heapq.heapify(heap)
        # 遍历nums中的元素,加入到堆中,因为python没有最大堆,所以使每个元素*-1来构造绝对值大的元素在堆顶
        for num in nums:
            heapq.heappush(heap, num * -1)
        # 让数组中第 k 个最大的元素到堆顶
        while k > 1:
            heapq.heappop(heap)
            k = k - 1
        #返回数组中第 k 个最大的元素,返回时重新*-1
        return heapq.heappop(heap) * -1

692. 前K个高频单词

import heapq
class Solution:
    def topKFrequent(self, words: List[str], k: int) -> List[str]:
        # 将每个单词和其出现的频次加入到哈希表中
        dict = {}
        for word in words:
            if word not in dict:
                dict[word] = 1
            dict[word] = dict[word] + 1
        heap, ans = [], []
        # 因为求前 K 个高频元素,python 默认最小堆,则将频次取负再入堆
        # 堆的元素可以是元组类型
        for i in dict:
            heapq.heappush(heap, (-dict[i], i))
        # 取出前K个元素
        for _ in range(k):
            ans.append(heapq.heappop(heap)[1])
        return ans

 Graph 图

Trie 字典树/前缀树

208. 实现 Trie (前缀树)

class Trie:

    def __init__(self):
        self.child = [None] * 26 # 孩子节点
        self.isEnd = False # 结束标志

    def insert(self, word: str) -> None:
        rt= self # 从根开始
        for w in word:
            ID = ord(w) - ord('a') # 字母的ASCII值
            if rt.child[ID] == None:     #没有,就新建
                rt.child[ID] = Trie()
            rt = rt.child[ID]          #往下走
        rt.isEnd = True        #标记位

    def search(self, word: str) -> bool:
        rt= self  # 从根开始
        for w in word:
            ID = ord(w) - ord('a')
            if rt.child[ID] == None:     #有字母不在这条path上,断了
                return False
            rt = rt.child[ID]          #沿着path往下走
        return rt.isEnd == True    #看isEnd位


    def startsWith(self, prefix: str) -> bool:
        rt = self  # 从根开始
        for w in prefix:
            ID = ord(w) - ord('a')
            if rt.child[ID] == None:     #path断了
                return False
            rt = rt.child[ID]
        return True         #无论是否是单词,path没断就ok


# Your Trie object will be instantiated and called as such:
# obj = Trie()
# obj.insert(word)
# param_2 = obj.search(word)
# param_3 = obj.startsWith(prefix)

720. 词典中最长的单词

class Solution:
    def longestWord(self, words: List[str]) -> str:
        res = ''
        trie = Trie()
        for word in words: # 将每个单词依次插入到前缀树中
            trie.insert(word)
        for word in words:
            if trie.search(word):
                if len(word) > len(res):
                    res = word
                elif len(word) == len(res) and word < res:
                    res = word
        return res

# 前缀树
class Trie:

    def __init__(self):
        self.child = [None] * 26 # 孩子节点
        self.isEnd = False # 结束标志

    def insert(self, word: str) -> None:
        rt= self # 从根开始
        for w in word:
            ID = ord(w) - ord('a') # 字母的ASCII值
            if rt.child[ID] == None:     #没有,就新建
                rt.child[ID] = Trie()
            rt = rt.child[ID]          #往下走
        rt.isEnd = True        #标记位

    def search(self, word: str) -> bool:
        rt= self  # 从根开始
        for w in word: # 检索该单词的前n项子字符串是否都在树中,如果是则返回True
            ID = ord(w) - ord('a')
            rt = rt.child[ID] 
            if rt is None or not rt.isEnd: 
                return False
        return True

TWO Pointers 双指针

141. 环形链表

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

class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        # 快慢双指针
        slow = fast = head # 初始化两个快慢指针,指向头节点
        while fast and fast.next: # 防止head为空和出现空指针的next的情况
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                return True
        return False

881. 救生艇

class Solution:
    def numRescueBoats(self, people: List[int], limit: int) -> int:
        # 对撞双指针
        if people is None or len(people) == 0:
            return 0
        people.sort() # 使用对撞双指针必须是有序的
        i = 0
        j = len(people) - 1
        res = 0
        while i <= j: # i大于j时跳出循环
            if people[i] + people[j] <= limit: # 如果i和j指向的元素和大于limit,i不能走,所以不会进入循环
                i = i + 1
            j = j -  1 # 每次进入while,j指向的元素肯定能走,所以在if外
            res = res + 1
        return res

Binary Search 二分查找法

704. 二分查找

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        # 二分查找法,定义target在左闭右闭的区间里,[l, r]
        l = 0
        r = len(nums) - 1
        while l <= r:
            mid = l + ((r - l) // 2) # 防止溢出 等同于(l + r)//2
            if nums[mid] > target:
                r = mid - 1 # target在[l, mid-1]区间
            elif nums[mid] < target:
                l = mid + 1 # target在[mid+1, r]区间
            else:
                return mid
        return -1

35. 搜索插入位置

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        # 定义target在左闭右闭的区间里,[left, right]
        left = 0
        right = len(nums) - 1
        while left <= right:
            mid = left + ((right - left) // 2)  # 防止溢出 等同于(left + right)/2
            if nums[mid] > target:
                right = mid - 1 #target 在左区间,所以[left, mid - 1]
            elif nums[mid] < target:
                left = mid + 1 # target 在右区间,所以[mid + 1, right]
            else:
                return mid
        return right + 1
'''
分别处理如下四种情况:
目标值在数组所有元素之前:return right + 1;--若有此情况,此时left和right都指向第一个元素,索引为0,mid也为0,满足nums[mid] > target,此时right=mid-1=-1,返回right+1,则添加到数组的第一个位置
目标值等于数组中某一个元素  return middle;
目标值插入数组中的位置 [left, right],return  right + 1;
目标值在数组所有元素之后的情况 [left, right], return right + 1;

162. 寻找峰值

class Solution:
    def findPeakElement(self, nums: List[int]) -> int:
        # 二分查找最大值
        left, right = 0, len(nums) - 1
        while left < right: # 注意循环退出条件,若加上=号,会有死循环
            mid = (left + right) // 2
            if nums[mid] > nums[mid + 1]: # 若中点值比右方值大,说明极值点在中点左侧(包括中点)
                right = mid
            else:
                left = mid + 1 # 若中点值比右方小,说明极值点在中点右侧(不包括中点)
        return left

74. 搜索二维矩阵

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        # 将二位数组转化为一维数组进行整体二分
        M, N = len(matrix), len(matrix[0]) # M:行,N:列
        left, right = 0, M * N - 1 # 分别指向首尾
        while left <= right:
            mid = left + (right - left) // 2
            # 将一维又转化为二维中的元素cur表示:(x,y)的元素索引=x*N+y
            cur = matrix[mid // N][mid % N]
            # 以下和正常的二分法一样的流程
            if cur == target:
                return True
            elif cur < target:
                left = mid + 1
            else:
                right = mid - 1
        return False

 Sliding Windows 滑动窗口

209. 长度最小的子数组

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        # 滑动窗口
        left = right = 0 #初始化两个指针,分别为窗口的左边界和右边界,窗口大小right-left+1
        min_len = float("inf") # 初始化一个最小长度,默认值为无穷大
        sum = 0 # 子数组和
        while right < len(nums):
            sum += nums[right] # 加上右边一个元素
            while sum >= target: # sum大于等于目标值,进入循环
                min_len = min(min_len, right-left+1) # 取之前记录的窗口小大min_len和当前的窗口大小right-left+1中的最小值
                 # sum减去左边界的数再左边界右移一位
                sum -= nums[left]
                left = left + 1 
            # sum小于target
            right = right + 1
        # 如果不存在符合条件的子数组,返回 0
        if min_len == float('inf'):
            return 0
        else:
            return min_len

1456. 定长子串中元音的最大数目

class Solution:
    def maxVowels(self, s: str, k: int) -> int:
        # 滑动窗口+哈希
        if s is None or len(s) == 0 or len(s) < k:
            return 0
        hashset = {'a','e','i','o','u'}
        count = res = 0
        # 算出第一个窗口中的元音字母数
        for i in range(k):
            if s[i] in hashset:
                count += 1
        res = max(res, count)
        # 处理后面的窗口
        for i in range(k, len(s)):
            if s[i - k] in hashset: # 滑出窗口的字母是元音,count-1
                count = count - 1
            if s[i] in hashset: # 滑进窗口的字母是元音,count+1
                count = count + 1
            # 最后取res和count中最大值
            res = max(res, count)
        return res

 Recursion 递归

509. 斐波那契数

class Solution:
    def fib(self, n: int) -> int:
        # 递归,接受的参数:n
        if n == 0:
            return 0
        if n == 1:
            return 1 # 终止条件为n=0/1
        m = self.fib(n - 1) + self.fib(n - 2) # 拆解
        return m # 返回值


        

206. 反转链表

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        # 递归终止条件是当前为空,或者下一个节点为空
        if head==None or head.next==None:
            return head
        # 这里的cur就是最后一个节点
        cur = self.reverseList(head.next)
        # 如果链表是 1->2->3->4->5,那么此时的cur就是5,head是4,head的下一个是5,下下一个是空
        # head.next.next 就是5->4
        head.next.next = head
        # 防止链表循环,需要将head.next设置为空
        head.next = None
        # 每层递归函数都返回cur,也就是最后一个节点
        return cur

344. 反转字符串

class Solution:
    def reverseString(self, s: List[str]) -> None:
        """
        Do not return anything, modify s in-place instead.
        """
        self.recursion(s, 0, len(s)-1)
    
    def recursion(self, s, left, right):
        if left >= right:
            return 
        self.recursion(s,left+1,right-1)
        s[left],s[right] = s[right], s[left]

Dicide & Conquer 分治法

169. 多数元素

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        # 强行分治法
        return self.getMajority(nums, 0, len(nums)-1)
           
    def getMajority(self, nums, left, right):
        if left == right: # 只有一个元素时
            return nums[left]
        mid = left + (right-left) // 2
        leftMajority = self.getMajority(nums, left, mid)
        rightMajority = self.getMajority(nums, mid+1, right)
        if leftMajority == rightMajority: # 如果最后合并后的左右的多数元素一样,可以直接返回
            return leftMajority
        # 如果不一样
        leftCount = 0
        rightCount = 0
        for i in range(left, right+1):
            if nums[i] == leftMajority:
                leftCount += 1
            elif nums[i] == rightMajority:
                rightCount += 1
        if leftCount > rightCount:
            return leftMajority
        else:
            return rightMajority

53. 最大子数组和

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        # 分治法
        return self.getMax(nums, 0, len(nums)-1)
    '''
    连续子序列的最大和主要由这三部分子区间里元素的最大和得到:
    第 1 部分:子区间 [left, mid];
    第 2 部分:子区间 [mid + 1, right];
    第 3 部分:包含子区间 [mid , mid + 1] 的子区间,即 nums[mid] 与 nums[mid + 1] 一定会被选取。
    '''
    def getMax(self, nums, l, r):
        if l == r: # 分到最后只有一个元素
            return nums[l]
        mid = l + (r-l) // 2
        leftSum = self.getMax(nums, l, mid)
        rightSum = self.getMax(nums, mid+1, r)
        crossSum = self.crossSum(nums, l, r)
        return max(leftSum, rightSum, crossSum)
    
    def crossSum(self, nums, l, r):
        mid = l + (r-l) // 2
        # 左边到mid
        leftSum = nums[mid]
        leftMax = leftSum
        for i in range(mid-1, l-1, -1):
            leftSum += nums[i]
            leftMax = max(leftMax, leftSum)
        # mid到右边
        rightSum = nums[mid+1]
        rightMax = rightSum
        for i in range(mid+2, r+1):
            rightSum += nums[i]
            rightMax = max(rightMax, rightSum)       
        return leftMax + rightMax 

 Backtracking 回溯法

22. 括号生成

class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        if n <= 0: 
            return []
        res = [] # 初始化结果集

        # paths表示当前的括号组合,left表示当前左括号已使用个数,right表示当前右括号已使用个数
        def dfs(paths, left, right):
            if left > n or right > left:  # 如果左括号个数大于n,或者右括号大于左括号,当前的paths肯定不符合,直接返回
                return
            if len(paths) == n * 2:  # 因为括号都是成对出现的
                res.append(paths)
                return

            dfs(paths + '(', left+1, right)  # 左括号生成一个就加一个
            dfs(paths + ')', left, right+1) # 左括号生成一个就加一个

        dfs('', 0, 0)
        return res

78. 子集

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        def backtrack(index, tmp): # 枚举长度为0,1,2,3的子集
            res.append(tmp) 
            for i in range(index, n):
                backtrack(i + 1, tmp + [nums[i]])

        n = len(nums) # 数组nums的元素个数
        res = [] # 结果集
        backtrack(0, [])
        return res
'''
枚举过程:
          [1] --->  [1,2] --->  [1,2,3]
              [1,3]

[]  --->  [2] --->  [2,3]

          [3]
'''

77. 组合

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        res = []
        def backtrace(i,tmp):
            if len(tmp) == k: # 长度等于k就将tmp添加到结果集
                res.append(tmp)
                return
            for j in range(i,n+1): 
                backtrace(j+1,tmp+[j])
        backtrace(1,[])
        return res

46. 全排列

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        res = []
        def backtrack(nums, tmp):
            if not nums:
                res.append(tmp)
                return 
            for i in range(len(nums)):
                backtrack(nums[:i] + nums[i+1:], tmp + [nums[i]])
        backtrack(nums, [])
        return res

 DFS 深度优先搜索算法

938. 二叉搜索树的范围和

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def rangeSumBST(self, root: TreeNode, low: int, high: int) -> int:
        # 普通二叉树
        res = 0
        if not root:
            return res
        res += self.rangeSumBST(root.left, low, high) # 左子树满足条件的和
        if low <= root.val <= high: # 根节点
            res += root.val
        res += self.rangeSumBST(root.right, low, high) # 右子树满足条件的和
        return res
        # # 二叉搜索树,最重要性质:二叉搜索树的中序遍历是有序的
        # res = 0
        # if not root:
        #     return res
        # if root.val > low: # 二叉搜索树的左子树一定比 root 小,因此如果 root.val <= low,那么不用继续搜索左子树
        #     res += self.rangeSumBST(root.left, low, high)
        # if low <= root.val <= high:
        #     res += root.val
        # if root.val < high: # 二叉搜索树的右子树一定比 root 大,因此如果 root.val >= high,那么不用继续搜索右子树
        #     res += self.rangeSumBST(root.right, low, high)
        # return res    

200. 岛屿数量

class Solution:
    def numIslands(self, grid: [[str]]) -> int:
        def dfs(grid, i, j):
            if not 0 <= i < len(grid) or not 0 <= j < len(grid[0]) or grid[i][j] == '0': # 若遍历的网格i,j超过边界或为0,直接返回
                return
            grid[i][j] = '0'  # 执行 grid[i][j] = '0',即将岛屿所有节点删除,以免之后重复搜索相同岛
            # 搜索(i,j)点的上下左右
            dfs(grid, i + 1, j)
            dfs(grid, i, j + 1)
            dfs(grid, i - 1, j)
            dfs(grid, i, j - 1)
        
        count = 0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j] == '1':
                    dfs(grid, i, j)
                    count += 1
        return count

200. 岛屿数量

 

广度优先算法 BFS

102. 二叉树的层序遍历

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        # 如果树为空,直接返回[]
        if not root:
            return []
        # 初始化一个res数组存储最后结果
        res = []
        # 初始化一个的队列用于存储每层的遍历结果,初始存储根节点
        queue = [root]
        while queue:
            # 获取当前队列的长度,这个长度相当于 当前这一层的节点个数
            size = len(queue)
            tmp = []
            # 将队列中的元素都拿出来(也就是获取这一层的节点),放到tmp中
            # 如果节点的左/右子树不为空,也放入队列中
            for _ in range(size):
                cur = queue.pop(0) # 从队列头部删除一个节点
                tmp.append(cur.val)  # 需要添加的是cur的val值,而不是cur,切记!!!
                if cur.left:
                    queue.append(cur.left)
                if cur.right:
                    queue.append(cur.right)
            # 将临时list加入最终返回结果中
            res.append(tmp)
        return res

107. 二叉树的层序遍历 II

将102题的res[::-1]

 

200. 岛屿数量

class Solution:
    def numIslands(self, grid: [[str]]) -> int:
        def bfs(grid, i, j):
            queue = [[i, j]]
            while queue:
                [i, j] = queue.pop(0)
                if 0 <= i < len(grid) and 0 <= j < len(grid[0]) and grid[i][j] == '1':
                    grid[i][j] = '0'
                    queue += [[i + 1, j], [i - 1, j], [i, j - 1], [i, j + 1]]
        count = 0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j] == '0': 
                    continue
                bfs(grid, i, j)
                count += 1
        return count

 并查集 Union Find

200. 岛屿数量

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        # 并查集:使用横坐标*列数+纵坐标作为元素在并查集中的唯一标识
        # 岛屿数量 = (总元素个数 - 水的个数)中的连通块的个数
        class UnionFind:
            def __init__(self, n):
                # 总数
                self.size = n
                self.p = [i for i in range(n)]

            def find(self, x):
                # 查找根节点,即当前元素所属的集合
                if self.p[x] != x:
                    self.p[x] = self.find(self.p[x])
                return self.p[x]

            def union(self, a, b):
                
                ar, br = self.find(a), self.find(b)
                # 两个元素位于同一个集合,跳过
                if ar == br:
                    return
                # 不在同一个集合,合并
                else:
                    self.p[ar] = br 
                    self.size -= 1

        n, m = len(grid), len(grid[0])
        ocean = 0   # 统计水的个数

        uf = UnionFind(n*m)

        for i in range(n):
            for j in range(m):
                # 统计水的个数
                if grid[i][j] == "0":
                    ocean += 1
                else:
                    # 只需向右和向下查看
                    if i+1 < n and grid[i+1][j]=="1":
                        uf.union(i*m+j, (i+1)*m+j)
                    if j+1 < m and grid[i][j+1]=="1":
                        uf.union(i*m+j, i*m+(j+1))
                
        return uf.size - ocean

547. 省份数量

class Solution:
    def findCircleNum(self, isConnected: List[List[int]]) -> int:
        def find(index: int) -> int:
            if parent[index] != index:
                parent[index] = find(parent[index])
            return parent[index]
        
        def union(index1: int, index2: int):
            parent[find(index1)] = find(index2)
        
        provinces = len(isConnected)
        parent = list(range(provinces))
        
        for i in range(provinces):
            for j in range(i + 1, provinces):
                if isConnected[i][j] == 1:
                    union(i, j)
        
        circles = sum(parent[i] == i for i in range(provinces))
        return circles

 贪心算法 Greedy

1217. 玩筹码

class Solution:
    def minCostToMoveChips(self, position: List[int]) -> int:
        # 所有偶数位的筹码可以没有代价的转移到任意偶数位,奇数同理
        # 此时假设移动后,所有奇数都在位置1,所有偶数都在位置2,目前是无代价的
        # 奇数和偶数是相邻的,不管谁移动代价都为各自的个数,为了使代价最小,题目就转变为求奇数和偶数的个数,并返回它们两者中的最小值
        odd, even = 0, 0
        for i in position:
            if i % 2 == 0:
                even += 1
            else:
                odd += 1
        return min(odd, even)
class Solution:
    def canJump(self, nums: List[int]) -> bool:
        max_i = 0       #初始化当前能到达最远的位置
        for i, jump in enumerate(nums):   #i为当前位置,jump是当前位置的跳数
            if max_i >= i and i + jump > max_i:  #如果当前位置能到达,并且当前位置+跳数>最远位置  
                max_i = i + jump  #更新最远能到达位置
        return max_i >= i

记忆化搜索/备忘录 Memorzation

509. 斐波那契数

class Solution:
    def fib(self, n: int) -> int:
        # 记忆化搜索/备忘录
        self.memo = [-1 for _ in range(n+1)]
        return self.__fibo(n)

    def __fibo(self, n):
        if n==0:
            return 0
        if n==1:
            return 1
        if self.memo[n] == -1:
            self.memo[n] = self.__fibo(n-1)+self.__fibo(n-2)
        return self.memo[n]

动态规划 

62. 不同路径

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        dp = [[0 for _ in range(n)] for _ in range(m)] # 创建一个默认值全为0的m*n的二维数组
        # 第一行都赋予 1
        for j in range(n):
            dp[0][j] = 1
        # 第一列都赋予 1    
        for i in range(m):
            dp[i][0] = 1
        # 两个for循环推导,对于(i,j)来说,只能由上方或者左方转移过来 
        for i in range(1, m):
            for j in range(1, n):
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
        return dp[-1][-1]

 

 
posted @ 2022-02-09 22:24  钟胜一  阅读(219)  评论(0)    收藏  举报