Python数据结构和算法笔记总结

文章目录

  • Python数据结构和算法笔记
    • 解决题目的思路
    • 时间复杂度
    • 重要工具...
    • Python抽象类型
      • 如何使用面向对象的方式实现数据结构
    • Python数据结构
      • 数组array
      • 列表list
        • list练习
          • 两数之和
          • 按奇偶排序数组
          • [搜索二维矩阵 II](https://leetcode-cn.com/problems/search-a-2d-matrix-ii/)
          • 移除元素
          • 合并两个有序数组
      • 链表
        • 链表的概念概念
        • 不同的链表类型
          • 单链表(linked list)
          • 双链表(double linked list)
        • 链表练习
          • 如何将一个list构造一个链表并编写一个打印单链表的函数
          • 反转链表
          • 合并两个排序的链表
          • 相交链表
          • 环形链表
          • 两数相加
      • 队列和栈
        • 队列
        • 栈练习
        • 用栈实现队列
        • 最小栈
        • 有效的括号
      • 哈希表
        • 概念
        • 疑问
        • 哈希表的应用
        • 哈希表的练习
          • 两个数组的交集
          • [两个数组的交集 II](https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/)
          • LRU缓存机制
      • 递归
        • 递归的概念
        • 递归的三个特点
        • 解决递归问题的顺序(重点*******)
        • 掌握递归函数的编写
        • 递归练习
          • 斐波那契数
          • 反转链表
          • 全排列
        • 树的概念
        • 树的分类
        • 代码表示二叉树
        • 二叉树的三种遍历顺序
        • 二叉树练习
          • 二叉树的后序遍历
          • 二叉树的层序遍历
          • 翻转二叉树
          • 二叉树的最大深度
          • 从前序与中序遍历序列构造二叉树
        • 二叉树总结

Python数据结构和算法笔记

解决题目的思路

  • 1、暴力破解:使用笨方法,不考虑时间复杂度和空间复杂度,使用for循环外加创建新的数据结构
  • 2、迭代方法:使用指针,一个指针搞定不了就是用两个,两个不行就三个…,多使用中间变量存储数据,多创建新的数据结构
  • 3、递归方法

时间复杂度

优劣排序:O(1) > O(logn) > O(n) > O(nlogn) > O(n^2) > O(2^n) > O(n!)

Python数据结构和算法笔记总结_第1张图片

重要工具…

  • python代码数据结构可视化

    • https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
    • http://pythontutor.com/live.html#mode=edit
  • leetcode(力扣)刷题网站:https://leetcode-cn.com/

  • 视频教学:https://edu.csdn.net/course/play/29389/416882

  • 算法题多使用内置函数和考虑时间复杂度

  • 内置函数:https://docs.python.org/3/library/functions.html

  • Python数据结构和算法笔记总结_第2张图片

  • pytest:测试工具

  • when-changed:自动监控文件并运行单元测试工具(看门狗)

    • https://github.com/joh/when-changed
  • 命令行执行:
    when-changed -r -v -1 'filepath' pytest -s 'filepath'
    -r(Watch recursively):监听
    -v(Verbose output):输入详细的信息
    -1:等待代码更新后才继续执行“filepath”文件
    -s:是pytest的命令,输入print打印的信息
    
    

Python抽象类型

  • 面向对象编程
  • 在Python中所有东西都可以看成是一个对象
  • 封装继承多态
    • 封装:公开对外接口,隐藏实现细节
    • 继承:复用现有类的数据和行为
    • 多态:同一操作作用在不同的对象,可以有不同的解释和产生不同的执行结果

如何使用面向对象的方式实现数据结构

  • 掌握抽象数据类型的概念(ADT:Abstract Data Type)

    • 通过“组合”或者“继承”已有数据结构实现新的结构
    • 经常使用“组合”的方式实现新类型
  • 学会用Python类实现自己的抽象数据类型

  • 了解魔术方法的概念和使用

  • 练习:用Python类实现一个新的抽象数据类型背包类Bag,Bag方法:构造函数、放入(add)、移除(remove)、长度(len)、遍历(iter)

    • class Bag():
      
          def __init__(self, maxsize=10):
              self.maxsize = maxsize
              self._item = list() # []
      
          def add(self, item):
              if len(self) > self.maxsize:
                  raise Exception('Bag is full')
              self._item.append(item)
      
          def remove(self, item):
              self._item.remove(item)
          
          def __len__(self):
              return len(self._item)
      
          def __iter__(self):
              for i in self._item:
                  yield i
          # def size(self):
          #     return len(self._item)
      
          # def iter(self):
          #     for i in self._item:
          #         yield i
      
      def test_Bag():
          b = Bag()
          b.add(1)
          b.add(2)
          b.add(3)
          # assert b.size() == 3
          assert len(b) == 3
          b.remove(3)
          # assert b.size() == 2
          assert len(b) == 2
          print()
          for i in iter(b):
              print(i)
      

Python数据结构

数组array

  • 1、内存连续,类型相同的线性结构,通过下标O(1)访问
  • 2、Python提供了array模块,但是其实用的并不多
  • from array import array
  • 日常一般用list

列表list

  • 时间复杂度:

  • 查找元素,通过下标查找:O(1)

  • 1、支持动态扩容的线性结构,下标访问

  • 2、超出容量之后,会开辟新内存,并复制旧数据

  • 3、Python list可以包含不同的数据类型(元素的类型可以是list、string、tuple…)

  • 4、业务代码和题目当中最常用的一种数据结构

  • 5、L = [] 或者 L= list() 来创建(这两种方式等价)

  • 练习:list数据类型的注意点(坑)

    • # 1、如何初始化一个元素都相同的一维list
      
      # 第一种方式:直接先创建一个list,然后for循环遍历数值并添加进list
      l = []
      for _ in range(10):
      	l.append(5)
      print(l) # [5,5,5,5,5,5,5,5,5,5]
      
      # 第二种方式:使用Python list提供的语法糖
      l = [5]*10
      
    • #2、如何初始化一个元素都相同的二维list(有一个坑在这里)
      def init_matrix(val,rows, cols):
      	return [[val]*cols]*rows # 这种写法会有一个坑,引用了同一个对象
      	# return [[val]*cols for _ in range(rows)] # 应该这样写
      	
      ll = init_matrix(0, 3, 4)
      print(ll) # [[0,0,0],[0,0,0],[0,0,0]]
      ll[0][0] = 100
      print(ll) # [[100,0,0],[100,0,0],[100,0,0]]
      
      # 解释:因为第一种return写法中的list中每一个元素都是指向同一个子list对象(语法糖*的问题),而第二种return写法中的list中每个元素指向的子list都是不同的对象
      # 可以使用可视化网站查看http://pythontutor.com/live.html#mode=edit
      
    • Python数据结构和算法笔记总结_第3张图片

list练习

两数之和
  • 解法:

    • 1、暴力解法:使用两次for循环遍历,时间复杂度:O(n^2)

    • 2、空间换时间:使用dict存储,只使用一次for循环,O(n)

    • 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
      
      你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
      示例:
      
      给定 nums = [2, 7, 11, 15], target = 9
      
      因为 nums[0] + nums[1] = 2 + 7 = 9
      所以返回 [0, 1]
      
    • class Solution(object):
          def twoSum(self, nums, target):
              """
              :type nums: List[int]
              :type target: int
              :rtype: List[int]
              """
              d = {}
              for i, num in enumerate(nums):
                  n = target - num
                  if n in d:
                      return [d[n],i]
                  d[num] = i
              return []
      
按奇偶排序数组
  • 解法:

    • 1、在内存空间中开辟新的list空间用来存储奇数以及偶数,空间复杂度会高

    • 2、使用指针方式,不开辟新的内存空间

    • 给定一个非负整数数组 A,返回一个数组,在该数组中, A 的所有偶数元素之后跟着所有奇数元素。
      
      你可以返回满足此条件的任何数组作为答案。
      
      示例:
      输入:[3,1,2,4]
      输出:[2,4,3,1]
      输出 [4,2,3,1],[2,4,1,3] 和 [4,2,1,3] 也会被接受。
      
      提示:
      1 <= A.length <= 5000
      0 <= A[i] <= 5000
      
    • # 按奇偶排序数组
      # class Solution(object):
      #     def odd(self, n):
      #         return n % 2 == 1
          
      #     def even(self, n):
      #         return n % 2 == 0
      
      #     def sortArrayByParity(self, A):
      #         """
      #         :type A: List[int]
      #         :rtype: List[int]
      #         """
      #         L = []
      #         even = list(filter(self.even, A))
      #         odd  = list(filter(self.odd, A))
      #         L = even + odd
      #         return L
      class Solution(object):
          
          def sortArrayByParity(self, A):
              """
              :type A: List[int]
              :rtype: List[int]
              """
              beg, end = 0, len(A)-1
              while True:
                  while A[beg] % 2 == 0 and beg < end:
                      beg += 1
                  while A[end] % 2 == 1 and beg < end:
                      end -= 1 
                  if beg >= end:
                      break
                  A[beg], A[end] = A[end], A[beg]
              return A
      
搜索二维矩阵 II
  • 解法:

    • 1、for循环两次,O(m*n)

    • 2、也是两次for循环,第二次for循环是使用二分查找,O(m*logn)

    • 3、使用指针方式,只使用一次for循环,O(m+n)

    • 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:
      
      每行的元素从左到右升序排列。
      每列的元素从上到下升序排列。
      示例:
      
      现有矩阵 matrix 如下:
      [
        [1,   4,  7, 11, 15],
        [2,   5,  8, 12, 19],
        [3,   6,  9, 16, 22],
        [10, 13, 14, 17, 24],
        [18, 21, 23, 26, 30]
      ]
      
      给定 target = 5,返回 true。
      给定 target = 20,返回 false。
      
    • class Solution(object):
          def searchMatrix(self, matrix, target):
              """
              :type matrix: List[List[int]]
              :type target: int
              :rtype: bool
              """
              if not matrix:
                  return False
              rows = len(matrix)
              cols = len(matrix[0])
              i, j = 0, cols-1
              while i < rows and j >= 0:
                  val = matrix[i][j]
                  if target == val:
                      return True
                  elif target < val:
                      j -= 1
                  else:
                      i += 1
              return False
              
      
      def test_searchMatrix(): # 测试
          M = [
              [1,   4,  7, 11, 15],
              [2,   5,  8, 12, 19],
              [3,   6,  9, 16, 22],
              [10, 13, 14, 17, 24],
              [18, 21, 23, 26, 30]
              ]
      
          assert Solution().searchMatrix(M, 5) == True
          assert Solution().searchMatrix(M, 20) == False
      # 测试命令:when-changed -r -v -1 'filepath' pytest -s 'filepath' 
      
移除元素
  • 解法:

    • 1、反向遍历。如果使用正向遍历的过程,删除list的多个元素的同时会导致list内部数据会变动从而index值会变,会比较麻烦

    • 2、使用指针

    • 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
      不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
      元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
      
      示例 1:
      给定 nums = [3,2,2,3], val = 3,
      函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
      你不需要考虑数组中超出新长度后面的元素。
      
      示例 2:
      给定 nums = [0,1,2,2,3,0,4,2], val = 2,
      函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
      注意这五个元素可为任意顺序。
      你不需要考虑数组中超出新长度后面的元素。
      
      
    • class Solution(object):
          def removeElement(self, nums, val):
              """
              :type nums: List[int]
              :type val: int
              :rtype: int
              """
              # 反向遍历
              # for i in range(len(nums)-1, -1, -1):
              #     if nums[i] == val:
              #         nums.pop(i)
              # return len(nums)
      
              # 使用指针
              i = 0
              for j in range(len(nums)):
                  if nums[j] != val:
                      # nums[i] = nums[j]
                      i += 1
              return i
      
      def test_removeElement():
          print()
          nums = [3,2,2,3]
          val = 3
          nums2 = [0,1,2,2,3,0,4,2] 
          val2 = 2
                  
          assert Solution().removeElement(nums, val) == 2
          assert Solution().removeElement(nums2, val2) == 5
      
合并两个有序数组
  • 解法:

    • 分析输入与输出的数据,多利用指针解决

    • 给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
      
      说明:
      初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
      你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
      
      示例:
      输入:
      nums1 = [1,2,3,0,0,0], m = 3
      nums2 = [2,5,6],       n = 3
      
      输出: [1,2,2,3,5,6]
      
    • class Solution(object):
          def merge(self, nums1, m, nums2, n):
              """
              :type nums1: List[int]
              :type m: int
              :type nums2: List[int]
              :type n: int
              :rtype: None Do not return anything, modify nums1 in-place instead.
              """
              i = m-1
              j = n-1 
              tmp = i+j+1
              while j >= 0:
                  if i == -1:
                      nums1[tmp] = nums2[j]
                      j -= 1
                      tmp -= 1
                      continue
                  if nums1[i] < nums2[j]:
                      nums1[tmp] = nums2[j]
                      j -= 1
                      tmp -= 1
                  else:
                      nums1[tmp] = nums1[i]
                      i -= 1
                      tmp -= 1
                      
      # 测试             
      def test_merge():
          nums1 = [1,2,3,0,0,0]
          m = 3
          nums2 = [2,5,6]     
          n = 3
          Solution().merge(nums1, m, nums2, n)
          assert nums1 == [1,2,2,3,5,6]
      
      

链表

链表的概念概念

  • 链表是一种常见的链式结构(linked list)
  • 链表由节点链接而成
  • 每个链表的节点包含数据成员和指向下一个节点的指针

不同的链表类型

单链表(linked list)
  • 特点:

    • 1、可以方便地追加元素到链表尾部,O(1)
    • 2、不支持随机下标访问,查找元素地时间复杂度是O(n),需要从头开始一个个查找
  • class Node:
    	def __init__(self, value, next=None):
    		self.value = value
    		self.next = next
    
双链表(double linked list)
  • 特点:

    • 1、可以给单链表再加一个指针(节点前后都有一个指针,左指针和右指针),指向前一个节点
    • 2、双链表可以支持反向遍历
    • 3、知道了双链表的一个节点后,可以将它的左指针或右指针指向另一个节点,即将两个节点串起来,从而实现**O(1)**插入,同理删除也是O(1)
    • 4、双链表还可以首位指针相连形成一个循环双端链表(至少两个节点)
    • 5、双链表可以高效地往两头增加或删除元素
  • class Node:
    	def __init__(self, value, prev=None, next=None):
    		self.value, self.prev, self,next = value, prev, next
    

链表练习

如何将一个list构造一个链表并编写一个打印单链表的函数
  • class LinkedListNode():
        def __init__(self, value, next=None):
            self.value = value
            self.next = next
    
    
    def gen_linked_list(nums):
    
        if not nums:
            return None
        head = LinkedListNode(nums[0])
        cur = head
        for i in range(1, len(nums)):
            node = LinkedListNode(nums[i])
            cur.next = node
            cur = node
        return head
    
    
    def print_linked_list(head):
        cur = head
        while cur:
            print('{}->'.format(cur.value), end='')
            cur = cur.next
        print('nil')
    
    if __name__ == "__main__":
        nums = [1,2,3,4]
        head = gen_linked_list(nums)
        print_linked_list(head)
    
反转链表
  • 解法:

    • 第一种方法利用指针迭代,比较好理解

    • 第二种使用递归,找出最后一个节点,然后递归实现最后一个节点需要做的事情

    • class Solution(object):
          def reverseList(self, head):
              """
              :type head: ListNode
              :rtype: ListNode
              """
              # 迭代 时间复杂度:O(n),空间复杂度:O(1)
              # pre = None
              # cur = head
              # while cur:
              #     nextnode = cur.next
              #     cur.next = pre
              #     pre = cur
              #     cur = nextnode
              # return pre
      
              # 递归 时间复杂度:O(n),空间复杂度:O(1)
              if not (head and head.next):
                  return head
              newhead = self.reverseList(head.next)
              head.next.next = head
              head.next = None
              return newhead
      
      
      # when-changed -r -v -1 'filepath' pytest -s 'filepath'
      def test_reverselist():
          L = [1,2,3,4,5]
          head = gen_linked_list(L)
          print()
          print_linked_list(head)
          pre = Solution().reverseList(head)
          print_linked_list(pre)
      
      
合并两个排序的链表
  • 解法:

    • 纸上做草稿,使用指针完成

    • 输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
      
      示例1:
      
      输入:1->2->4, 1->3->4
      输出:1->1->2->3->4->4
      
    • # Definition for singly-linked list.
      class ListNode(object):
          def __init__(self, x):
              self.val = x
              self.next = None
      
      class Solution(object):
          def mergeTwoLists(self, l1, l2):
              """
              :type l1: ListNode
              :type l2: ListNode
              :rtype: ListNode
              """
              pre = ListNode(0)
              cur = pre
              while l1 and l2:
                  if l1.val <= l2.val:
                      cur.next = l1
                      l1 = l1.next
                      cur = cur.next
                  else:
                      cur.next = l2
                      l2 = l2.next
                      cur = cur.next
      
              cur.next = l1 or l2
              return pre.next
      
      def test_mergeTwoLists():
          l1 = gen_linked_list([1,2,4])
          l2 = gen_linked_list([1,3,4])
          s = Solution().mergeTwoLists(l1, l2)
          print_linked_list(s)
      
      
      def gen_linked_list(nums):
      
          if not nums:
              return None
          head = ListNode(nums[0])
          cur = head
          for i in range(1, len(nums)):
              node = ListNode(nums[i])
              cur.next = node
              cur = node
          return head
      
      
      def print_linked_list(head):
          cur = head
          while cur:
              print('{}->'.format(cur.val), end='')
              cur = cur.next
          print('nil')
      
      
相交链表
  • 解法:

    • 第一种方式,将长链表砍掉一部分使得它的长度与短链表一样长,然后判断

    • 第二种方式,将两个链表相交即长度都相等了,再判断

    • 编写一个程序,找到两个单链表相交的起始节点。
      
    • Python数据结构和算法笔记总结_第4张图片

    • class ListNode(object):
          def __init__(self, x):
              self.val = x
              self.next = None
      
      class Solution(object):
          def getIntersectionNode(self, headA, headB):
              """
              :type head1, head1: ListNode
              :rtype: ListNode
              """
              # 第一种方式,将长链表砍掉一部分使得它的长度与短链表一样长
              # if headA is None or headB is None:
              #     return None 
      
              # curA = headA
              # lenA = 0
              # while curA:
              #     lenA += 1
              #     curA = curA.next
      
              # curB = headB
              # lenB = 0
              # while curB:
              #     lenB += 1
              #     curB = curB.next
              
              # ldiff = abs(lenA-lenB) 
              # if lenA > lenB:
              #     for _ in range(ldiff):
              #         headA = headA.next
              # else:
              #     for _ in range(ldiff):
              #         headB = headB.next
              
              # while headA and headB:
              #     if headA == headB:
              #         # print(headA,headB)
              #         return headA
              #     headA = headA.next
              #     headB = headB.next
              
              # return None
      
              # 第二种方式,将两个链表相交即长度都相等了,再判断
              ha, hb = headA, headB
              while ha != hb:
                  ha = ha.next if ha else headB
                  hb = hb.next if hb else headA
              return ha
      
环形链表
  • 思路:

    • 1、开辟一个新的内存空间即创建一个set(集合),利用集合元素不重复的特性

    • 2、快慢指针,若闭环,快慢指针一定会相遇

    • 给定一个链表,判断链表中是否有环。
      
      为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
      
    • Python数据结构和算法笔记总结_第5张图片

    • # Definition for singly-linked list.
      class ListNode(object):
          def __init__(self, x):
              self.val = x
              self.next = None
      
      class Solution(object):
          def hasCycle(self, head):
              """
              :type head: ListNode
              :rtype: bool
              """
              # 第一种使用hash表存储(集合,根据元素不重复的特性)
              # 时间复杂度O(n),空间复杂度O(n)
              # hash = {}
              # cur = head
              # while cur:
              #     if hash.get(cur.next) is not None:
              #         return True
              #     hash[cur] = 1
              #     cur = cur.next
              # return False
              
              # 利用快慢指针
              # 时间复杂度O(n),空间复杂度O(1)
              slow, fast = head, head
              while fast and fast.next:
                  slow = slow.next
                  fast = fast.next.next
                  if slow == fast:
                      return True
              return False
      
两数相加
  • 思路:

    • 1、竖式计算,定义多几个变量(指针)进行操作,会方便很多,重点在于获取值的方式,%10为获取个位,//10获取进位

    • 2、使用递归的方式

    • 给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
      如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
      您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
      
      示例:
      输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
      输出:7 -> 0 -> 8
      原因:342 + 465 = 807
      
    • class ListNode(object):
          def __init__(self, x):
              self.val = x
              self.next = None
      
      class Solution:
      	# 迭代的方式
          def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
              # 迭代的方式,创建了新的链表,空间复杂度O(n),时间复杂度O(n)
              pre = cur = ListNode(None) # pre变量用于返回,cur用于指向新创建的节点
              s = 0                       # 中间暂存变量
              while l1 or l2 or s:
                  # if l1 == None:
                  #     l1 = ListNode(0)
                  # if l2 == None:
                  #     l2 = ListNode(0)
                  s += (l1.val if l1 else 0) + (l2.val if l2 else 0)  # 注意是+=,存储l1和l2节点的值
                  cur.next = ListNode(s%10) # 新节点赋值  %10取个位
                  s = s//10   # 取进位的值
                  cur = cur.next # 使指向新节点的指针向前指
                  l1 = l1.next if l1 else None # l1向前走
                  l2 = l2.next if l2 else None # l2向前走
              return pre.next # 返回新节点的首节点
      
      # 递归的方式,添加参数carry
      # class Solution:
          # def addTwoNumbers(self, l1: ListNode, l2: ListNode, carry=0) -> ListNode:
          #     if l1==None and l2==None and carry==0:  
          #         return None
      
          #     if l1==None and l2==None and carry==1 :
          #         return ListNode(1)
              
          #     if l1==None :
          #         l1 = ListNode(0)
          #     if l2==None :
          #         l2 = ListNode(0)
      
          #     l1.val, carry = (l1.val+l2.val+carry)%10, (l1.val+l2.val+carry)//10
          #     l1.next = self.addTwoNumbers(l1.next, l2.next, carry)
      
          #     return l1
      
      
      def test_addTwoNumbers():
          l1 = gen_linked_list([2,4,3,2])
          l2 = gen_linked_list([5,6,4])
          s = Solution().addTwoNumbers(l1,l2)
          print_linked_list(s)
      
      def gen_linked_list(nums):
      
          if not nums:
              return None
          head = ListNode(nums[0])
          cur = head
          for i in range(1, len(nums)):
              node = ListNode(nums[i])
              cur.next = node
              cur = node
          return head
      
      
      def print_linked_list(head):
          cur = head
          while cur:
              print('{}->'.format(cur.val), end='')
              cur = cur.next
          print('nil')
      
      

队列和栈

  • 队列可视化网站
  • deque
    • Python collections模块自带了deque实现
    • 底层基于双端链表实现
    • 可以高效地在两头增删元素,O(1)
    • 可以利用deque实现队列和栈

队列

  • FIFO-first in first out,先进先出结构,类似于排队

  • 最先插入的元素最先出来,可以使用数组、链表等结构可以用来实现队列

  • 队列:queue,双端队列:double-ended queue

  • 下面使用deque(python内置模块)来实现队列或双端队列(利用双端队列deque的特性)

  • from collections import deque
    class Queue():
        def __init__(self):
            self.items = deque()
        
        def push(self, item):
            self.items.append(item)
    
        def pop(self):
            return self.items.popleft()
        
        def head(self):
            return self.items[0]
    
        def isempty(self):
            return len(self.items) == 0
        
    if __name__ == "__main__":
        q = Queue()
        q.push(1)
        q.push(2)
        q.push(3)
        q.pop()
        qh = q.head()
        print(qh)
    

  • LIFO-last in first out,后进先出结构,类似于往桶里面添加东西

  • 可以用数组、双端队列等方式实现栈

  • 下面同样使用deque实现

  • class Stack():
        def __init__(self):
            self.items = deque()
    
        def push(self, item):
            self.items.append(item)
        
        def pop(self):
            return self.items.pop()
        
        def head(self):
            return self.items[0]
        
        def isempty(self):
            return len(self.items) == 0
    if __name__ == "__main__":
        s = Stack()
        s.push(4)
        s.push(5)
        s.push(6)
        s.pop()
        sh = s.head()
        print(sh)
    

栈练习

用栈实现队列

  • 思路:

    • 使用两个栈,push数据时push进第一个栈,然后将数据以栈的方式移进第二个栈,这样pop数据时使用第二个栈pop,取数据相当于先进先出

    • 使用栈实现队列的下列操作:
      push(x) -- 将一个元素放入队列的尾部。
      pop() -- 从队列首部移除元素。
      peek() -- 返回队列首部的元素。
      empty() -- 返回队列是否为空。
      
      示例:
      MyQueue queue = new MyQueue();
      queue.push(1);
      queue.push(2);  
      queue.peek();  // 返回 1
      queue.pop();   // 返回 1
      queue.empty(); // 返回 false
      
    • from collections import deque
      
      
      class Stack():
          def __init__(self):
              self.items = deque()
          
      
          def push(self, val):
              return self.items.append(val)
          
      
          def pop(self):
              return self.items.pop()
          
      
          def __len__(self):
              return len(self.items)
      
      
          def top(self):
              return self.items[-1]
      
      
      class MyQueue(object):
      
          def __init__(self):
              """
              Initialize your data structure here.
              """
              self.s1 = Stack()
              self.s2 = Stack()
      
      
          def push(self, x):
              """
              Push element x onto stack.
              :type x: int
              :rtype: None
              """
              self.s1.push(x)
      
      
          def pop(self):
              """
              Removes the element on top of the stack and returns that element.
              :rtype: int
              """
              print(self.s2)
              if self.s2:
                  return self.s2.pop()
              
              while self.s1:
                  val = self.s1.pop()
                  self.s2.push(val)
      
              return self.s2.pop()
      
      
          def peek(self):
              """
              Get the top element.
              :rtype: int
              """
              if self.s2:
                  return self.s2.top()
      
              while self.s1:
                  val = self.s1.pop()
                  self.s2.push(val)
              
              return self.s2.top()
      
          def empty(self):
              """
              Returns whether the stack is empty.
              :rtype: bool
              """
              return len(self.s1) == 0 and len(self.s2) == 0
      
      
      def test():
          q = MyQueue()
          q.push(1)
          q.push(2)
          # val = q.pop()
          # print(val)
          assert q.pop() == 1
          assert q.pop() == 2
          assert q.empty() is True
      

最小栈

  • 思路:

    • 使用两个栈来完成

    • 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
      push(x) —— 将元素 x 推入栈中。
      pop() —— 删除栈顶的元素。
      top() —— 获取栈顶元素。
      getMin() —— 检索栈中的最小元素。
       
      
      示例:
      
      输入:
      ["MinStack","push","push","push","getMin","pop","top","getMin"]
      [[],[-2],[0],[-3],[],[],[],[]]
      
      输出:
      [null,null,null,null,-3,null,0,-2]
      
      解释:
      MinStack minStack = new MinStack();
      minStack.push(-2);
      minStack.push(0);
      minStack.push(-3);
      minStack.getMin();   --> 返回 -3.
      minStack.pop();
      minStack.top();      --> 返回 0.
      minStack.getMin();   --> 返回 -2.
      
    • from collections import deque
      
      
      class Stack():
          def __init__(self):
              self.items = deque()
      
          def push(self, val):
              self.items.append(val)
      
          def pop(self):
              return self.items.pop()
      
          def top(self):
              return self.items[-1]
      
          def __len__(self):
              return len(self.items)
      
          def empty(self):
              return len(self.items) == 0
      
      
      class MinStack(object):
      
          def __init__(self):
              """
              initialize your data structure here.
              """
              self.s = Stack()
              self.mins = Stack()
      
          def push(self, x):
              """
              :type x: int
              :rtype: None
              """
              if self.s.empty():
                  self.s.push(x)
                  self.mins.push(x)
              else:
                  top = self.mins.top()
                  if x < top:
                      self.mins.push(x)
                  else:
                      self.mins.push(top)
                  self.s.push(x)
      
          def pop(self):
              """
              :rtype: None
              """
              self.mins.pop()
              return self.s.pop()
      
          def top(self):
              """
              :rtype: int
              """
              return self.s.top()
      
          def getMin(self):
              """
              :rtype: int
              """
              return self.mins.top()
      
      
      def test():
          s = MinStack()
          s.push(-2)
          s.push(0)
          s.push(-3)
          assert s.getMin() == -3
          s.pop()
          print(s.top())
          assert s.getMin() == -2
      
      

有效的括号

  • 思路:

    • 1、使用栈

    • 2、使用字典外加列表

    • 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
      
      有效字符串需满足:
      
      左括号必须用相同类型的右括号闭合。
      左括号必须以正确的顺序闭合。
      注意空字符串可被认为是有效字符串。
      
      示例 1:
      
      输入: "()"
      输出: true
      示例 2:
      
      输入: "()[]{}"
      输出: true
      示例 3:
      
      输入: "(]"
      输出: false
      示例 4:
      
      输入: "([)]"
      输出: false
      示例 5:
      
      输入: "{[]}"
      输出: true
      
    • 方法一:使用栈

    • from collections import deque
      
      
      class Stack():
          def __init__(self):
              self.items = deque()
      
          def push(self, val):
              self.items.append(val)
      
          def pop(self):
              return self.items.pop()
      
          def top(self):
              return self.items[-1]
      
          def __len__(self):
              return len(self.items)
      
          def empty(self):
              return len(self.items) == 0
      
      
      class Solution(object):
          def isValid(self, s):
              """
              :type s: str
              :rtype: bool
              """
              if not s:
                  return True
              stack = Stack()
              for char in s:
                  if stack.empty():
                      stack.push(char)
                  else:
                      top = stack.top()
                      if get_pair(char) == top:
                          stack.pop()
                      else:
                          stack.push(char)
              return stack.empty()
      
      
      def get_pair(char):
          if char == ')':
              return '('
          if char == ']':
              return '['
          if char == '}':
              return '{'
          return None
      
      
      def test_valid_parentheses():
          s = Solution()
          assert s.isValid('[]') is True
          assert s.isValid('{
              {[]}}') is True
          assert s.isValid('[{]}') is False
      
    • 方法二:使用字典加列表

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

哈希表

概念

  • 1、哈希表是一种快速查找结构
  • 2、经常用来存储“键值对”,key/value值
  • 3、哈希表的查找时间近似为O(1),几乎可以瞬间查找到一个值
  • 4、Java HashMap,Python的dict/set底层就是使用哈希表实现的,我们可以快速查找一个key的值

疑问

  • 1、如果传入的值通过散列函数计算的下标一样怎么办?如何解决哈希冲突
    • 1、链接法:
      • 可以在当前位置挂一个链表,将冲突的数据都链接起来。
      • 缺点:极端情况下一个链表可能会比较长
    • 2、开放寻址法(Open Addressing):
      • 线性探查(Linear Probing:f(i) = i)
      • 二次方探查(Quadratic Probing:f(i) = i*i)
      • 双重散列(Double Hashing:f(i) = i*hash2(elem))
    • 3、重哈希:
      • 上述两种方法不适合元素一直插入的情况,元素一直插入会导致底层数组不够用,使用重哈希方式,当数组使用到了一定比例(负载因子),会触发重哈希算法,重哈希会重新建立一个更大的数组,比如扩大两倍,然后把之前的数组元素重新放入到新的数组里
  • 2、哈希表底层一般使用一个数组来实现。思考如何存一个k/v结构
  • 3、通过对散列函数计算key的下标插入到对应位置

哈希表的应用

  • 1、Python dict/set 底层就是使用哈希表实现
  • 2、dict可以快速保存键值对
  • 3、set常用来判断元素是否存在,set也可以使用dict来实现

哈希表的练习

  • 掌握leetcode 哈希表和集合经典例题
  • 掌握LRU cache (最近最少使用缓存算法)
两个数组的交集
  • 思路:利用set的特性,熟悉set里面的方法使用

    • 给定两个数组,编写一个函数来计算它们的交集。
      
      示例 1:
      输入:nums1 = [1,2,2,1], nums2 = [2,2]
      输出:[2]
      
      示例 2:
      输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
      输出:[9,4]
      
    • class Solution(object):
          def intersection(self, nums1, nums2):
              """
              :type nums1: List[int]
              :type nums2: List[int]
              :rtype: List[int]
              """
              return set(nums1) & set(nums2)
      
两个数组的交集 II
  • 思路:

    • 1、哈希表

    • 2、指针排序(下面未实现)

    • 给定两个数组,编写一个函数来计算它们的交集。
      
      示例 1:
      输入:nums1 = [1,2,2,1], nums2 = [2,2]
      输出:[2,2]
      
      示例 2:
      输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
      输出:[4,9]
      
    • import collections
      
      # 哈希表,时间复杂度O(max(n,m)),空间复杂度O(max(n,m))
      class Solution:
          def intersect(self, nums1, nums2):
              if len(nums1) > len(nums2):
                  return self.intersect(nums2, nums1)
      
              m = collections.Counter()
              for num in nums1:
                  m[num] += 1
      
              intersection = list()
              for num in nums2:
                  if (m.get(num, 0)) > 0:
                      intersection.append(num)
                      m[num] -= 1
                      if m[num] == 0:
                          m.pop(num)
      
              return intersection
      
LRU缓存机制
  • 解决缓存空间不够用的一种常见的高效淘汰策略

  • 思路:

    • 1、题目说O(1)时间复杂度,说明空间复杂度不限,限定了容量。

    • 2、使用字典存储key/value值,查询只需要根据关键字查找所以是O(1)

    • 3、记录key对应的顺序,且要对key进行移位操作,使用**(循环)双向链表**,双向链表移位插入的时间复杂度为O(1)

    • 4、Python内置的collections模块中的OrderedDict类底层是字典+循环双向链表,正好适合此题

    • 运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
      
      获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
      写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
      
       
      你是否可以在 O(1) 时间复杂度内完成这两种操作?示例:
      
      LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
      
      cache.put(1, 1);
      cache.put(2, 2);
      cache.get(1);       // 返回  1
      cache.put(3, 3);    // 该操作会使得关键字 2 作废
      cache.get(2);       // 返回 -1 (未找到)
      cache.put(4, 4);    // 该操作会使得关键字 1 作废
      cache.get(1);       // 返回 -1 (未找到)
      cache.get(3);       // 返回  3
      cache.get(4);       // 返回  4
      
    • from collections import OrderedDict
      
      
      class LRUCache(object):
      
          def __init__(self, capacity:int):
              """
              :type capacity: int
              """
              self.od = OrderedDict()
              self.capacity = capacity
      
          def get(self, key):
              """
              :type key: int
              :rtype: int
              """
              res = self.od.get(key)
              if res:
                  self.od.move_to_end(key)
                  return res
              else:
                  return -1
      
          def put(self, key, value):
              """
              :type key: int
              :type value: int
              :rtype: None
              """
              self.od[key] = value
              self.od.move_to_end(key)
              if len(self.od) > self.capacity:
                  self.od.popitem(last=False)
      

递归

详细讲解:https://www.zhihu.com/question/31412436/answer/683820765

递归的概念

  • 1、递归:自己调用自己就是递归。(递归小套娃)
  • 2、每进入一个函数调用,都会压栈,所以递归深度太深,则会出现栈溢出
  • 3、同一个变量名在不同的函数栈,值是不同的

递归的三个特点

  • base case:递归必须包含一个基础的出口,问题小到一定规模的时候,需要一个递归的“出口”返回
  • recursive case:递归必须要包含一个可以解决的子问题,每一次递归我们都要分解它的参数,解决一个更小一点的问题
  • toward the base case:递归必须要向着递归出口靠近

解决递归问题的顺序(重点*******)

  • 1、首先定义递归函数功能:将函数框架先定义出来,函数名,函数参数
  • 2、递归结束条件:需找出参数为啥时,递归结束,之后直接把结果返回(注意,这个时候我们必须能根据这个参数的值直接知道函数的结果是什么)
  • 3、寻找函数的等价关系(递归表达式)
    • 从上往下递归思想:例如求P(n),我们假设P(n-1)已经知道(可以看作是子问题的结果),则思考从P(n-1)的结果加上一些什么变量或者操作才能变成P(n)的结果
    • 从下往上递推思想:例如求P(n),我们可以从n=1,2,3…依次类推去找到规律,找到类似于P(3)=P(1)+P(2)这样的规律,就可以推出P(n)=P(n-2)+P(n-1)
  • 4、还有一种简单的思考方式:
    • 1、递归出口
    • 2、子问题
    • 3、返回值
  • 动态规划思想:最后需要考虑是否有重复计算的问题,将一些重复计算过的值使用中间变量/数组/哈希表保存起来,这样递归遇到之前算过的值就可以直接拿来用而不需要再去重复计算

掌握递归函数的编写

  • 1、理解以下代码

    • # 这一段代码输出什么?为什么?
      def print_num_recursive(n):
      	if n > 0:
      		print_num_recursive(n-1)
      		print(n)
      # 1 
      # 2 
      # 3 
      # ...
      # n-1
      # n
      
      
      # 如果把print(n) 放在上面则输出什么?
      def print_num_recursive(n):
      	if n > 0:
      		print(n)
      		print_num_recursive(n-1)
      # n
      # n-1
      # ... 
      # 3
      # 2
      # 1
      
  • 2、正整数的阶乘

    • n! = n*(n-1)*(n-2)*…*1
    • f(n) = n*f(n-1)
  • 3、汉诺塔问题

递归练习

斐波那契数
  • 思路:

    • 1、纯递归方式,时间复杂度O(2^n)

    • 2、动态规划方式,时间复杂度O(n)

    • 斐波那契数,通常用 F(n) 表示,形成的序列称为斐波那契数列。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
      F(0) = 0,   F(1) = 1
      F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
      给定 N,计算 F(N)。
      
    • import functools
      class Solution:
      
          def fib(self, N: int) -> int:
              # 使用递归,时间复杂度O(2**n)
              # if N <= 1:
              #     return N
              # return self.fib(N-1) + self.fib(N-2)
      
              # 迭代(动态规划),时间复杂度O(n)
              # a, b = 0, 1
              # if N <= 1:
              #     return N
              # for _ in range(N-1):
              #     a, b = b, a+b
              # return b
      
      
          # 递归+动态规划,时间复杂度O(n)
          @functools.lru_cache(None)
          def fib(self, N: int) -> int:
              if N <= 1:
                  return N
              return self.fib(N-1) + self.fib(N-2)
      
反转链表
  • class LinkedListNode():
        def __init__(self, value, next=None):
            self.value = value
            self.next = next
    
    
    def gen_linked_list(nums):
    
        if not nums:
            return None
        head = LinkedListNode(nums[0])
        prev = head
        for i in range(1, len(nums)):
            node = LinkedListNode(nums[i])
            prev.next = node
            prev = node
        return head
    
    
    def print_linked_list(head):
        cur = head
        while cur:
            print('{}->'.format(cur.value), end='')
            cur = cur.next
        print('nil')
    
    
    class Solution(object):
        def reverseList(self, head):
            """
            :type head: ListNode
            :rtype: ListNode
            """
            # 迭代 时间复杂度:O(n),空间复杂度:O(1)
            # pre = None
            # cur = head
            # while cur:
            #     nextnode = cur.next
            #     cur.next = pre
            #     pre = cur
            #     cur = nextnode
            # return pre
    
            # 递归,时间复杂度:O(n),空间复杂度:O(1)
            if not (head and head.next):
                return head
            newhead = self.reverseList(head.next)
            nextnode = head.next
            nextnode.next = head
            head.next = None
            return newhead
    
    # pytest -s 'filepath'
    def test_reverselist():
        L = [1,2,3,4,5]
        head = gen_linked_list(L)
        print()
        print_linked_list(head)
        pre = Solution().reverseList(head)
        print_linked_list(pre)
    
全排列
  • 思路:

    • 1、寻找子问题:当数列有三个元素,可以将第一个数固定,则后面的数继续递归操作,同理数列变成两个元素的话,也是固定第一个数,直到数列只有一个数的时候返回数列

    • 给定一个 没有重复 数字的序列,返回其所有可能的全排列。
      
      示例:
      
      输入: [1,2,3]
      输出:
      [
        [1,2,3],
        [1,3,2],
        [2,1,3],
        [2,3,1],
        [3,1,2],
        [3,2,1]
      ]
      
    • class Solution:
          def permute(self, nums):
              res = []
      
              def per(nums, beg, end):
                  if beg == end-1:
                      res.append(nums[:])
                      return
                  for i in range(beg, end):
                      nums[i], nums[beg] = nums[beg], nums[i]
                      per(nums, beg+1, end)
                      nums[i], nums[beg] = nums[beg], nums[i]
              per(nums, 0, len(nums))
              return res
      

  • 一种包括节点(nodes)和边(edges)的拥有层级关系的结构
  • 树的形式和家谱非常类似

树的概念

  • 1、根节点(root):树的最上层的节点,任何非空的树都有一个节点
  • 2、路径(path):从起始节点到终止节点经历过的边
  • 3、父亲(parent):除了根节点,每个节点的上一层连接的节点就是它的父亲
  • 4、孩子(children):每个节点由边指向的下一层节点
  • 5、兄弟(siblings):同一个父亲并且处于同一层的节点
  • 6、子树(subtree):每个节点包含它所有的后代组成的子树
  • 7、叶子节点(leaf node):没有孩子的节点称为叶子节点

树的分类

  • 二叉树:每个节点最多只有两个孩子,每个节点拥有的孩子节点范围:0~2
    • 满二叉树:每个内部节点(非叶子节点)都包含两个还是,即每个节点要么没有孩子节点,要么有两个
    • 完美二叉树:所有的叶子节点都在同一层
    • 完全二叉树:在完美二叉树的基础上,添加多一层或者不添加,使最下层的节点是无间隙的从左到右填充

代码表示二叉树

  • data表示节点数据,left表示左孩子指针,right表示右孩子指针

  • class TreeNode:
    	def __init__(self, data, left=None, right=None):
    		self.data = data
    		self.left = left
    		self.right = right
    

二叉树的三种遍历顺序

  • 先(根)序遍历

  • 中(根)序遍历

  • 后(根)序遍历

  • 深度遍历:递归与非递归方式,使用栈模拟递归过程,实现非递归遍历,因为递归操作本身是因为计算机帮我们完成了入栈和出栈过程

  • 广度遍历(层序遍历):使用list或者队列实现

  • 递归,非递归(使用栈,因为递归操作是),层序遍历(list或队列)三种方式

  • 递归方式

    • class TreeNode():
          def __init__(self, data, left=None, right=None):
              self.data = data
              self.left = left
              self.right = right
      
      
      def preorder(root):
          if root is not None:
              print(root.data,end=' ')
              preorder(root.left)
              preorder(root.right)
      def inorder(root):
          if root is not None:
              preorder(root.left)
              print(root.data)
              preorder(root.right)
      
      def postorder(root):
          if root is not None:
              preorder(root.left)
              preorder(root.right)
              print(root.data)
      
  • 非递归方式(使用栈)(标记法)

    • from collections import deque
      def preorder_use_stack(root):
          if not root:
              return
          s = deque()
          s.append((root, False))
          while s:
              node, visted = s.pop()
              if node:
                  if visted:
                       print(node.data)
                  else: # stack是 后进先出。所以先序:根左右出栈 -> 右左根入栈,同理中序和后续
                      s.append((node.right, False))
                      s.append((node.left, False))
                      s.append((node, True))
      
  • 层序遍历(使用list/队列)

    • # 使用list完成层序遍历
      def layer_order_uselist(root):
        # if not root:
          #     return
          # cur_nodes = [root]
          # next_nodes = []
          # while cur_nodes or next_nodes:
          #     print(cur_nodes)
          #     for node in cur_nodes:
          #         if node.left:
          #             next_nodes.append(node.left)
          #         if node.right:
          #             next_nodes.append(node.right)
          #         cur_nodes = next_nodes
          #         next_nodes = []
      
          li = [root]
          while li:
              n = len(li)
              for _ in range(n):
                  l = li.pop(0)
                  if l:
                      print(l.data)
                      li.append(l.left if l.left else None)
                      li.append(l.right if l.right else None)
      
      # 使用队列完成层序遍历
      def layer_order_usequque(root):
          d = deque()
          d.append(root)
          while d:
              node = d.popleft()
              if node:
                  print(node.data)
                  d.append(node.left if node.left else None)
                  d.append(node.right if node.right else None)
      

二叉树练习

二叉树的后序遍历
  • 思路:

    • 1、使用迭代遍历方法实现(栈+标记法)

    • 2、使用递归实现

    • 给定一个二叉树,返回它的 后序 遍历。
      
      示例:
      
      输入: [1,null,2,3]  
         1
          \
           2
          /
         3 
      
      输出: [3,2,1]
      
      class TreeNode:
          def __init__(self, x, left=None, right=None):
              self.val = x
              self.left = left
              self.right = right
      
      # # 递归,后序遍历:左右根
      # class Solution:
      #     def postorder(self, root, res):
      #         if not root:
      #             return
      #         self.postorder(root.left,res)
      #         self.postorder(root.right,res)
      #         print(root.val)
      #         res.append(root.val)
      
      #     # 后序遍历:左右根
      #     def postorderTraversal(self, root: TreeNode):
      #         res = []
      #         self.postorder(root, res)
      #         return res
      
      
      # 迭代后序遍历
      class Solution:
      
          def postorderTraversal(self, root: TreeNode):
              stack = deque()
              res = []
              stack.append((root, False))
              while stack:
                  node, visited = stack.pop()
                  if node:
                      if visited:
                          res.append(node.val)
                      else:  # 由于是用栈存储,所以后序遍历原本时左右根->变成入栈顺序根右左
                          stack.append((node, True))
                          stack.append((node.right, False))
                          stack.append((node.left, False))
              return res
      
二叉树的层序遍历
  • 思路:

    • 使用list/队列

    • 给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
      
      示例:
      二叉树:[3,9,20,null,null,15,7],
      
          3
         / \
        9  20
          /  \
         15   7
      
      返回其层次遍历结果:
      [
        [3],
        [9,20],
        [15,7]
      ]
      
    • # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, x):
      #         self.val = x
      #         self.left = None
      #         self.right = None
      
      class Solution:
          def levelOrder(self, root: TreeNode) -> List[List[int]]:
              if not root:
                  return []
              cur_nodes = [root]
              next_nodes = []
              res = []
              vals = [i.val for i in cur_nodes]
              res.append(vals)
              while cur_nodes or next_nodes:
                  for node in cur_nodes:
                      if node.left:
                          next_nodes.append(node.left)
                      if node.right:
                          next_nodes.append(node.right)
                  if next_nodes:
                      res.append([i.val for i in next_nodes])
                  cur_nodes = next_nodes
                  next_nodes = []
              return res
      
翻转二叉树
  • 思路

    • 递归:递归出口、子问题、返回值

    • 翻转一棵二叉树。
      示例:
      
      输入:
           4
         /   \
        2     7
       / \   / \
      1   3 6   9
      
      输出:
           4
         /   \
        7     2
       / \   / \
      9   6 3   1
      
    • # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, x):
      #         self.val = x
      #         self.left = None
      #         self.right = None
      
      class Solution:
          def invertTree(self, root: TreeNode) -> TreeNode:
              if not root:
                  return
              root.left, root.right = root.right, root.left 
              self.invertTree(root.left)
              self.invertTree(root.right)
              return root
      
二叉树的最大深度
  • 思路:

    • 递归:递归出口、子问题、返回值

    • 给定一个二叉树,找出其最大深度。
      
      二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
      
      说明: 叶子节点是指没有子节点的节点。
      
      示例:
      给定二叉树 [3,9,20,null,null,15,7],
          3
         / \
        9  20
          /  \
         15   7
       
      返回它的最大深度 3 。
      
    • # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, x):
      #         self.val = x
      #         self.left = None
      #         self.right = None
      
      class Solution:
          def maxDepth(self, root: TreeNode) -> int:
              if not root:
                  return 0
              left = self.maxDepth(root.left) + 1
              right = self.maxDepth(root.right) + 1
              return max(left, right) 
      
从前序与中序遍历序列构造二叉树
  • 思路:

    • 1、递归出口:序列为空的时候

    • 2、子问题:递归地去构造左右子树

      • 从先序中找到根节点
      • 从中序中判断根节点位置,从而判断左右子树的长度
      • 根据中序中得到的左右子树的长度可以回到先序中可以找出对应的左右子树
      • 递归地去构造左右子树
    • 3、返回值:根节点

    • 根据一棵树的前序遍历与中序遍历构造二叉树。
      
      注意:
      你可以假设树中没有重复的元素。
      
      例如,给出
      前序遍历 preorder = [3,9,20,15,7]
      中序遍历 inorder = [9,3,15,20,7]
      
      返回如下的二叉树:
      
          3
         / \
        9  20
          /  \
         15   7
      
    • # Definition for a binary tree node.
      # class TreeNode:
      #     def __init__(self, x):
      #         self.val = x
      #         self.left = None
      #         self.right = None
      
      class Solution:
          def buildTree(self, preorder, inorder):
              if not preorder:
                  return None
              root_val = preorder[0]
              root = TreeNode(root_val)  # build root node
              root_idx = inorder.index(root_val)
              # 计算左右子树的长度
              left_len = root_idx
              right_len = len(inorder)-root_idx-1
              # 递归构造
              if left_len:
                  root.left = self.buildTree(preorder[1:left_len+1], inorder[0:root_idx])
              if right_len:
                  root.right = self.buildTree(preorder[left_len+1:], inorder[root_idx+1:])
      
              return root
      

二叉树总结

  • 二叉树是递归定义的,所以很多问题可以用递归解决
  • 掌握二叉树的遍历方式可以解决很多基础问题
  • 基于二叉树还可以衍生出二叉搜索树和链表的一些问题,可以去leetcode找些题目练手

你可能感兴趣的:(Python数据结构与算法,python,数据结构,算法)