LeetCode刷题笔记

文章目录

  • 1.数据结构
    • 1.1字符串、数组、链表
    • 1.2队列、栈
    • 1.3堆
    • 1.4树
      • 1.4.1二叉树
      • 1.4.2二叉搜索树
      • 1.4.3字典树
      • 1.4.4树状数组
      • 1.4.5线段树
    • 1.5图
    • 1.6哈希表
    • 1.7Ordered Map
  • 2.简单算法
    • 2.1位运算
    • 2.2双指针
    • 2.3排序算法
    • 2.4二分查找
  • 3.复杂算法
    • 3.1递归、回溯算法、深度优先搜索DFS
    • 3.2广度优先搜索BFS
    • 3.3拓扑排序
    • 3.4贪心算法
    • 3.5分治算法
    • 3.6滑动窗口算法
    • 3.7极小化极大
    • 3.8并查集
    • 3.9动态规划
  • 4.其他
    • 4.1Random
    • 4.2拒绝采样Rejection Sampling
    • 4.3天际线Line Sweep
    • 4.4脑筋急转弯
    • 4.5蓄水池抽样
    • 4.6记忆化
    • 4.7数学
    • 4.8几何
    • 4.9设计
  • 5.细节

前些日子在LeetCode上刷了几十道题,主要包括二叉树、回溯算法、双指针,感觉没什么系统,还有二十天就要投简历了,得抓紧时间掌握知识点了,刷完题还要把机器学习、深度学习的知识点和项目再熟悉一遍。这知识点太多了,感觉二十天搞不定,加快进度。之后计划(强度特别大,但 有志者,事竟成!!):

  • 1.27搞定数据结构1.1 1.2(今天刷了344 13 11 42 206 19 21 225 232 739未完)
  • 1.28搞定数据结构1.3 1.4(今天刷了724 J40 215 23 101 94 98 538 124 42)
  • 1.29搞定数据机构1.6 1.7 3.3(今天刷了997 J3 136 347 409 207 210)
  • 1.30搞定简单算法2.1 2.2(今天刷了169 136 78 389 268 461 191 28 M10.01 75)
  • 2.1搞定简单算法2.4(今天刷了888 35 69 34 167 33 242 349 56 75 164)
  • 2.2做了脑筋急转弯(DFS的递归实在是太绕,还是脑筋急转弯有意思)(今天刷了110 521 319 1033 1227)
  • 2.3搞定滑动窗口算法(今天刷了3 239 J59 76 480 295 567)
  • 2.4啥也没搞定(今天刷了643 M16.11 1288)
  • 2.5搞定并查集(今天刷了1288 200 547,还得多做几个并查集的题)
  • [?] 2.7搞定动态规划、贪心算法、极小化极大、分治、记忆化、图(这几种算法题目一致,都可用DP解决)(今天刷了665 130 5)
  • [?] 2.8动态规划有点思路了(今天刷了978 121 70 53 198,还得多做几个DP的题)
  • [?] 2.10搞定DFS、BFS、回溯、递归
  • 2.19-2.21 图 排序算法 Morris遍历 KMP 并查集

1.数据结构

数据结构这部分题我觉得除了树那部分,其余部分主要是记住python的各种方法使用。因为没有学过python的基础,只学过java的,python是直接上手的,所以很多方法的使用都不清楚,都是用到现学。

1.1字符串、数组、链表

KMP实现:

def calNext(s2):
    j, k = 0, -1
    next = [0] * len(s2)
    next[0] = -1
    while j < len(s2) - 1:
        if k == -1 or s2[j] == s2[k]: 
            j += 1
            k += 1
            next[j] = k
        else:
            k = next[k]    # 重新进入上面循环
    return next
print(calNext('abcabx'))    # [-1, 0, 0, 0, 1, 2]

def KMP(s1, s2, pos = 0):   # pos为开始比较的位置,一般为0
    next = calNext(s2)
    i, j = pos, 0
    while i < len(s1) and j < len(s2):
        if j == -1 or s1[i] == s2[j]:
            i += 1
            j += 1
        else:
            j = next[j]
    if j == len(s2):
        return i - j
    else:
        return -1
s1 = "acabaabaabcacaabc"
s2 = "abaabcac"
print(KMP(s1, s2))	# 5

参考博客:
KMP详解
油管最火KMP算法讲解,阿三哥的源代码!

1.2队列、栈

队列和栈的基本操作可以使用数组来完成。队列是先进先出,栈是先进后出。

参考博客:数据结构:单调栈

1.3堆

堆是一种常用的树形结构,是一种特殊的完全二叉树,当且仅当满足所有节点的值总是不大于或不小于其父节点的值的完全二叉树被称之为堆。

如果数组是多维数组,数组堆化时按照数组的第0个元素排序。hq = [(points[i][0] ** 2 + points[i][1] ** 2, i) for i in range(len(points))]

参考博客:栈与堆的区别

1.4树

树一般有两种解题方式:

  • 递归:确定递归条件和终止条件
  • 迭代:利用队列或栈这两种数据结构实现,一般层次遍历用队列实现,前序遍历、中序遍历、后序遍历用栈实现

参考博客:二叉树Python遍历和迭代方法实现

1.4.1二叉树

二叉树有一种特殊的遍历方式:Morris遍历(空间复杂度为 O ( 1 ) O(1) O(1))。

参考博客:Morris遍历的图示理解以及代码实现

1.4.2二叉搜索树

二叉搜索树BST中序遍历时,结果依此递增。

1.4.3字典树

参考博客:字典树(前缀树)

1.4.4树状数组

1.4.5线段树

1.5图

图的题多用到并查集、拓扑排序、动态规划

参考博客:
数据结构和算法:图
图的表示法与常用的转化算法

1.6哈希表

  • 哈希表去重:哈希表的key不能重复,value可以重复
  • 哈希冲突:key不同,value相同

参考博客:Python之哈希表

1.7Ordered Map

2.简单算法

2.1位运算

知识点:

  • 正数的反码和补码都与原码相同。负数的反码为对该数的原码除符号位外各位取反。 负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加1 。
  • 任何数和 0 做异或运算,结果仍然是原来的数,即 a ⊕ 0 = a
  • 任何数和其自身做异或运算,结果是 0,即 a ⊕ a = 0
  • 异或运算满足交换律和结合律,即a ⊕ b ⊕ a = b ⊕ a ⊕ a = b ⊕ (a ⊕ a) = b ⊕ 0 = b

常用方法:

  • bin()获取数字的二进制值
  • ord()输入单个字符返回其ASCII值(0-255)或unicode值
  • chr()输入一个整数[0,255]返回其ASCII值
  • int("s",x)将指定进制x的字符s转换成十进制值

参考博客:
位运算介绍与经典例题总结
位掩码(BitMask)
Unicode详解

2.2双指针

当需要枚举数组中的两个元素时,如果发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,将枚举的时间复杂度从 O ( N 2 ) O(N^2) O(N2) 减少至 O ( N ) O(N) O(N)

双指针包括:

  • 普通双指针
  • 对撞双指针l、r
  • 快慢双指针s、f(环形链表)

2.3排序算法

知识点:

  • 二维数组.sort(key=lambda x: x[0]) 根据指定二维数组的第一个元素升序排序
  • 二维数组.sort(key=lambda x: (x[0], -x[1]))根据指定二维数组的第一个元素升序排序,若第一个元素相等,按第二个元素降序排序

参考文章:十大经典排序算法

2.4二分查找

时间复杂度 O ( l o g N ) O(logN) O(logN),前提是有序。注意细节:

  • mid=(left+right)//2改成mid=left+(right-left)//2或者mid=left+((right-left)>>1)(位运算速度快)可以防止left+right过大造成的整数溢出
  • while left < right 还是while left <= right
  • 注意边界条件

参考文章:二分查找及其多个变种

3.复杂算法

3.1递归、回溯算法、深度优先搜索DFS

递归需要注意:

  • 参数、返回值
  • 递归条件
  • 终止条件

DFS:不撞南墙不回头,一般以作为基本数据结构,题型多为二叉树搜索和图搜索。

回溯 = DFS + 剪枝

3.2广度优先搜索BFS

层层递进,一般以队列作为基本数据结构,题型多为二叉树搜索和图搜索。

一道题如果BFS和DFS都可以做,两种解法要都会。

3.3拓扑排序

课程表 I 和 II 的 BFS 和 DFS 代码掌握并盲码了一遍,实际上再遇到拓扑排序的题还是做不出来(吐血…)

参考博客:拓扑排序(Topological Sorting)

3.4贪心算法

每次均取最优解。贪心算法可以做的题动态规划也可以做。

参考博客:五大常用算法入门(一)——贪心算法

3.5分治算法

分而治之,归并排序原理。

参考博客:【五大常用算法】一文搞懂分治算法

3.6滑动窗口算法

一般用于解决数组中的定长问题。

类似滑动窗口协议,滑动窗口算法是指在给定特定窗口大小的数组或字符串上执行要求的操作。该技术可以将一部分问题中的嵌套循环转变为一个单循环,因此它可以减少时间复杂度。其时间复杂度为 O ( n ) O(n) O(n)

参考博客:滑动窗口算法基本原理与实践

3.7极小化极大

常采用递归实现,两个玩家要么是极大化玩家,要么是极小化玩家。极大化玩家目标是找到能获得最大收益的走法,但是必须考虑极小化玩家的走法,即需要求得对手的回手:让极大化玩家的收益最小化的走法。这个过程一直进行(求最大值、求最小值),直到达到递归函数的基线条件。对于搜索空间太深而无法抵达终局的游戏,极小化极大函数minimax()会在达到一定深度后停止。

α-β剪枝算法(alpha-beta pruning)优化极小化极大算法:

在搜索时将不会生成更优结果的棋局排除,由此来增加搜索的深度。跟踪记录递归调用minimax()间的两个值 α \alpha α β \beta β α \alpha α表示搜索树当前找到的最优极大化走法的评分, β \beta β表示当前找到的对手的最优极小化走法的评分,如果 β ≤ α \beta \leq\alpha βα,则不值得对该搜索分支进一步搜索。这种启发式算法能显著缩小搜索空间。

参考博客:极小化极大算法

3.8并查集

模板性极强(以岛屿数量为例):

class UnionFind:
    def __init__(self, grid):
        m, n = len(grid), len(grid[0]) 	# 获取数组的行、列数
        self.count = 0					# 记录连通分量的数量
        self.parent = [-1] * (m * n)    # 记录根节点
        self.rank = [0] * (m * n)       # 记录树高
        for i in range(m):
            for j in range(n):
                if grid[i][j] == "1":
                    self.parent[i * n + j] = i * n + j
                    self.count += 1

    # 寻找元素i的根节点
    def find(self,i):
        if self.parent[i] != i:
            self.parent[i] = self.find(self.parent[i])  # 递归实现Quick Find(路径压缩)
        return self.parent[i]

    # 在x和y之间添加一条连接,合并两个元素为根节点
    def union(self,x,y):
        rootx = self.find(x)
        rooty = self.find(y)
        if rootx != rooty:  
            if self.rank[rootx] < self.rank[rooty]:	# Quick Union 权重
                rootx,rooty = rooty,rootx
            self.parent[rooty] = rootx
            if self.rank[rootx] == self.rank[rooty]:
                self.rank[rootx] += 1
            self.count -= 1

    # 连通分量的数量
    def getCount(self):
        return self.count
        
    # 判断两节点x,y是否相连    
	def is_connected(self,x,y):
        return self.find(x) == self.find(y)
    
    # 添加新节点x
    def add(self,x):
        if x not in self.parent:
            self.parent[x] = None

时间复杂度 O ( M N × α ( M N ) ) O(MN \times \alpha(MN)) O(MN×α(MN)),M 和 N 分别为行数和列数。注意当使用路径压缩(见 find 函数)和按秩合并(见数组 rank)实现并查集时,单次操作的时间复杂度为 α ( M N ) \alpha(MN) α(MN),其中 α ( x ) \alpha(x) α(x) 为反阿克曼函数,当自变量 x 的值在人类可观测的范围内(宇宙中粒子的数量)时,函数 α ( x ) \alpha(x) α(x) 的值不会超过 5,因此也可以看成是常数时间复杂度。

空间复杂度 O ( M N ) O(MN) O(MN),这是并查集需要使用的空间。

参考博客:
【算法与数据结构——并查集】
并查集(Union-Find)
并查集算法详解、
反阿克曼函数

3.9动态规划

解题步骤:

  • 定义状态:明确 d p ( i ) dp(i) dp(i)应该表示什么(二维情况: d p ( i ) ( j ) dp(i)(j) dp(i)(j)
  • 写出状态转移方程:根据 d p ( i ) dp(i) dp(i) d p ( i − 1 ) dp(i-1) dp(i1) 的关系
  • 初始化:如 d p ( 0 ) dp(0) dp(0)
  • 确定输出
  • 是否可以空间优化:前阶段状态若只和上一个阶段状态有关,则可以对状态空间进行重复利用

参考博客:
六大算法之三——动态规划
告别动态规划,连刷40道动规算法题,我总结了动规的套路
动态规划问题——经典模型的状态转移方程
动态规划之状态压缩dp入门
总结——01背包问题 (动态规划算法)

4.其他

4.1Random

4.2拒绝采样Rejection Sampling

4.3天际线Line Sweep

4.4脑筋急转弯

4.5蓄水池抽样

4.6记忆化

本质上是DFS搜索的思路,但其对搜索过的状态进行记录,从而完成对未知状态的推导,实际上也是一种DP的思想。

参考博客:
算法导论学习——动态规划之记忆化搜索
DFS–记忆化DFS–DP之间的联系

4.7数学

4.8几何

4.9设计

5.细节

  • sort()排序的时间复杂度为 O ( N l o g N ) O(NlogN) O(NlogN):使用的是归并排序
  • numsnums[:]区别:用nums只是将nums指向那个副本,而nums在内存中原来地址的值没有变化,用nums[:]就会将内存中地址对应的值改变。gelthin-解释为何要用 nums1[:]
  • python切片[start:end]切不到end
  • 袖珍计算器算法:一种用指数函数 exp 和对数函数 ln 代替平方根函数的方法。
  • 子串和子序列:子串必须连续,子序列可以不连续
  • 双指针和滑动窗口区别
  • 叶子节点:左右孩子都为空的节点
  • 差分数组:原始数组相邻元素之间的差

结束了四个月的实习,回归力扣

你可能感兴趣的:(Data,Structures,and,Algorithms,算法,python,leetcode)