本仓库包含用Golang解决的Leetcode算法题题解以及笔记,学习路线主要跟随代码随想录
题目以数据结构或算法类型来分类,目录如下:
- 26.删除有序数组中的重复项
- 27.移除元素
- 34.在排序数组中查找元素的第一个和最后一个位置
- 35.搜索插入位置
- 51.螺旋矩阵
- 59.螺旋矩阵II
- 69.x的平方根
- 76.最小覆盖子串
- 209.长度最小的子数组
- 283.移动零
- 367.有效的完全平方数
- 704.二分查找
- 844.比较含退格的字符串
- 904.水果成篮
- 977.有序数组的平方
- 剑指29.顺时针打印矩阵
- 927.三等分
- 611.有效三角形的个数
- 31.下一个排列
- HJ11.数字颠倒
- 1.两数之和
- 15.三数之和
- 18.四数之和
- 202.快乐数
- 242.有效的字母异位词
- 349.两个数组的交集
- 383.赎金信
- 454.四数相加II
- 347.前k个高频元素
- 692.前k个高频单词
- 剑指II60.出现频率最高的k个数字
- HJ8.合并表记录
- HJ9.提取不重复的整数
- HJ10.字符个数统计
- 28.实现strStr()
- 151.反转字符串里的单词
- 344.反转字符串
- HJ12.字符串反转
- 459.重复的子字符串
- 541.反转字符串II
- 剑指05.替换空格
- 剑指58II.左旋转字符串
- HJ4.字符串分隔
- HJ14.字符串排序
- HJ17.坐标移动
- 165.比较版本号
- DFS-深度优先遍历
- 迭代法:通过栈来模拟递归
- 144.二叉树的前序遍历
- 94.二叉树的中序遍历
- 145.二叉树的后序遍历
- 二叉树的统一迭代法
- BFS-广度优先遍历
- 101.二叉树:是否对称,100.二叉树:是否相同
- 递归:后序,比较的是根节点的左子树和右子树是不是相互反转
- 迭代:使用栈/队列将两个节点顺序放入容器中进行比较
- 572.二叉树:是否为另一棵树的子树
- 递归:查看是否是相同的树,递归
- 104.二叉树:求最大深度
- 递归:后序,求根节点最大高度就是最大深度,通过递归函数的返回值计算树的高度(左树和右树的最大高度+1)
- 迭代:层序,层数就是最大深度
- 111.二叉树:求最小深度
- 递归:后序,求根节点的最小高度就是最小深度,注意最小深度的定义
- 迭代:层序
- 222.二叉树:求有多少个节点
- 递归:后序,通过递归函数的返回值计算节点数量
- 递归2:如果是完全二叉树,左右指针分别遍历至最后一层,利用特性来计算
- 迭代:层序
- 110.二叉树:是否平衡
- 递归:后序,先求左右子树的高度,差值大于1时直接返回-1
- 257.二叉树:找所有路径
- 递归:前序,path和res数组分别记录目前path和最终结果。注意回溯的思想
- 404.二叉树:求左叶子之和
- 递归:后序,注意判断左叶子的条件
- 迭代:直接用栈模拟后序
- 513.二叉树:找左下角的值
- 递归:需要记录最大深度,来判断是否为最后一层
- 迭代:层序遍历找到最后一行的第一个值
- 112.二叉树:求路径总和,路径总和II
- 递归:顺序无所谓,递归函数返回值为bool是为了搜索一边,没有返回值是搜索整棵树
- 226.反转二叉树
- 递归:前序,交换左右孩子
- 迭代:前序或层序都行
- 106.中序与后序构造二叉树,105.前序与中序构造二叉树
- 递归:前序,以后序遍历序列的最后一个节点为root,寻找前/中序和后序中的切割点分别构造左右区间
- 654.构造最大的二叉树
- 递归:前序,找到数组的最大值作为切割点,构造左右区间
- 617.合并两个二叉树
- 递归:前序,同时操作两个节点,可以直接用其中一个节点而不用新建root
- 迭代:层序,每次往队列中放入两个节点
- BST的遍历不需要额外存储空间,因为无需回溯
- 700.二叉搜索树中的搜索
- 递归:按大小判断递归
- 迭代:按大小判断遍历
- 98.是不是二叉搜索树
- 递归1:中序遍历,判断生成的数组是否为一个递增有序数组
- 递归2:无需数组,递归时更新min和max值来判断
- 530.求二叉搜索树的最小绝对差
- 递归:中序,用pre来记录前一个结点方便计算差值,双指针
- 迭代:中序,相同逻辑
- 501.求二叉搜索树中的众数
- 递归:中序,count和maxCount,更新maxCount时清空已存在的res
- 迭代:中序,相同逻辑
- 538.把二叉搜索树转换为累加树
- 递归:中序(右中左),双指针pre来操作累加
- 迭代:栈模拟中序,相同逻辑
- 236.二叉树的最近公共祖先
- 递归:后序,然后判断几种情况
- 235.二叉搜索树的最近公共祖先
- 递归:根据root的数值比较决定递归方向
- 迭代:直接指针遍历,与递归相同逻辑
- 701.二叉搜索树中的插入操作
- 递归:找到null值时插入
- 迭代:遍历找到位置插入
- 450.二叉搜索树中的删除操作
- 递归:前序,重平衡时用右树的最小值作为新的root值
- 669.修剪二叉搜索树
- 递归:前序
- 108.构造二叉搜索树
- 递归:前序,用数组中间节点作为root进行区间分隔
- 组合问题其实也是一种子集问题
- 77.组合
- 回溯模板,res,path,start递归
- 216.组合总和III
- 回溯模板,res,path,start递归
- 还需要sum记录总和
- 17.电话号码的字母组合
- 回溯模板,res,path,index递归
- 数组来储存数字与字母的映射
- 39.组合总和
- 回溯模板,res,path,index递归
- 由于可重复,回溯时用i而不是i+1
- 若先排序数组,可以剪枝
- 40.组合总和II
- 回溯模板,res,path,index递归
- 不可重复,回溯时用i+1
- 需要去重操作,首先要排序数组
- 然后递归逻辑中每次要去重(candidates[i] == candidates[i-1])
- 可以剪枝
- 分隔与组合相似,组合选择变成了切割点
- 131.分割回文串
- 回溯模板,res,path,start递归
- 判断回文,不符合时continue
- 终止条件为到达末尾
- 93.复原IP地址
- 回溯模板,res,path,start递归
- 判断每段地址是否合法
- 终止条件为1.到达末尾;2.path有四段地址
- 求取子集问题,不需要任何剪枝!因为子集就是要遍历整棵树
- 78.子集
- 回溯模板,res,path,start递归
- 每次递归先保存当前path至res
- 终止条件可以省略,因为for循环遍历至最后一个数字时自然结束
- 90.子集II
- 与78.子集一个套路
- 需要去重操作,首先要排序数组
- 然后递归逻辑中每次要去重
- 491.递增子序列
- 回溯模板,res,path,start递归
- 根据题目条件,当path有两个元素时即可保存,保存后继续往后走
- 需用used来记录本层用过的数字,以免重复搜索同样的数字开头的子序列
- 排列问题是有序的,所以每次循环都要从0开始,不需要start参数
- 去重时需要用used来记录
- 46.全排列
- 回溯模板,res,path
- used记录出现过的元素,同样要回溯
- 47.全排列II
- 回溯模板,res,path
- used记录出现过的元素,同样要回溯
- 需要去重:
- 已经搜索过以第i个数字开头的排列,跳过
- 重复的数字,规定按从左到右的顺序搜索,还没有搜索过第i-1个数时跳过
- 332.重新安排行程
- 回溯模板,res,不需要path了,因为此时res相当于path,找到一条符合要求的path时便结束了
- 需要一个map来记录起点和终点的映射
- map需要根据终点排序,这样最终的结果才是根据字典排序的
- 终止条件:当res的路径数等于tickets数加一时结束
- 51.N皇后
- 回溯模板,board来记录棋盘状态,row记录当前的行
- 递归遍历行
- 递归里的for遍历列
- 终止条件:当row = n时,完成了所有行
- 检查是否合法时,查看列、45度和135度上斜线即可
- 37.解数独
- 回溯模板,board记录棋盘状态
- 需要遍历所有行所有列,以及1-9的值,找到当前cell合适的值直接返回true
- 没有合适的值返回false
- 检查是否合法时,查看行、列、以及九宫格
- 455.分发饼干
- 局部最优:大饼干喂给胃口大的孩子,不造成饼干尺寸的浪费
- 全局最优:喂饱尽可能多的孩子
- 排序饼干和孩子胃口
- 双指针
- 1005.K次取反后最大化的数组和
- 局部最优:将绝对值最大的负数变为正,从而最大化当前数值
- 整体最优:将尽可能多的负数取反为正,或将最小的正数取反,最大化总和
- 第一步 按绝对值从大到小排序
- 第二步 从头开始遍历,遇到负数便取反操作,k--
- 第三步 如k还大于0,则继续操作取反绝对值最小的那个数
- 第四部 求和
- 860.柠檬水找零
- 局部最优:碰到20的账单,优先消耗10元面值,再消耗5元,因为5元能用来找零的场景更多
- 全局最优:完成全部找零
- 376.摆动序列
- 无需删除操作,只用记录符合条件的节点数,也就是数组的峰值数量
- 记录currDiff和preDiff用来比较
- 738.单调递增的数字
- 需从后往前遍历,然后一个flag记录从哪里开始赋值为9,例:987 -> flag为1 -> 899
- 局部最优:当遇到num[i-1] > num[i]的情况,将num[i-1]减1,num[i]赋值为9,可以保证这两位变成最大的单调递增整数
- 全局最优:得到小于等于N的最大单增整数
- 908.最小差值I
- 找出nums中的最小值和最大值
- 它们最多可以分别增加和减少k
- 最小差值则是(max-k)-(min+k),若得负数则返回0
- 910.最小差值II
- 排序数组
- 在排序后的数组中i下标处切一刀,左边小的这部分集体+k,右边大的部分集体-k
- 初始化也覆盖了一种情况,就是不切,所有数增加或减少k,diff不变
- 遍历寻找i点,使得新的max-min最小
- 122.买卖股票的最佳时机II
- 把利润分解为每天为单位的维度,而不是从0天到第n天整体去考虑
- 局部最优:收集每天的正利润,也就是只收集当利润为正时
- 全局最优:求得最大利润
- 714.买卖股票的最佳时机含手续费
- 局部最优:最低值买,最高值卖(算上手续费后依然盈利)
- 全局最优:利润最大
- 股票问题常规解法是用动态规划
在出现两个维度相互影响时,不要一起考虑,先确定一个维度,再确定另一个维度
- 135.分发糖果
- 两次贪心
- 第一次:从左到右遍历,确保评分高的右孩子比左孩子的糖多
- 第二次:从右到左遍历,确保评分高的左孩子比右孩子的糖多(贪心:既比左边多也比右边多)
- 全局最优:中间的孩子比两边的孩子糖多
- 两次贪心
- 406.根据身高重建队列
- 先身高h从高到矮排序
- 局部最优:优先按身高高的people的k来插入,插入后的people满足队列属性
- 全局最优:全部插入,并符合属性
各种覆盖各种去重
- 55.跳跃游戏
- 判断最大的跳跃范围是否能大于等于数组最后一位
- 局部最优:每次取最大的跳跃范围
- 全局最优:最后得到整体能跳到的最大范围
- 45.跳跃游戏II
- 需要统计两个覆盖范围:当前这一步的最大覆盖 和 下一步的最大覆盖
- 局部最优:当前可移动距离尽量多走,如果还没到终点,步数再加一
- 整体最优:每一步都尽可能多走,达到最小步数
- 452.用最少数量的箭引爆气球
- 按左边界(起点)排序,从左向右遍历
- 局部最优:当气球重叠时,一起射,所用弓箭最少
- 全局最优:把所有气球射爆所用的箭最少
- 435.无重叠区间
- 按右边界(终点)排序,从左向右遍历
- 局部最优:优先选右边界小的区间,所以从左向右遍历,留给下一个区间的空间更大,从而尽量避免交叉
- 全局最优:选取最多的非交叉区间
- 763.划分字母区间
- 统计每个字母的最远边界
- 从头遍历,更新当前遍历到的字母的最远边界,如果最远边界与当前坐标重合,则为切割点
- 不算是严格的贪心,因为不存在局部最优
- 56.合并区间
- 按左边界排序,从左向右遍历
- 局部最优:每次合并都取最大的右边界,可以合并更多的区间
- 整体最优:合并所有重叠的区间
- 53.最大子数组和
- 遍历每次将nums[i]更新为当前子数组的最大和,并判断是否大于max
- 局部最优:当前连续和为负数的时候立刻放弃(不跟新nums[i]),从下一个元素重新计算连续和,因为负数加上下一个元素只会越来越小
- 全局最优:选取最大连续和
- 134.加油站
- curSum记录当前起始点start的累加,totalSum记录全部的累加
- 遍历,curSum小于0时起始点start加1
- 若totalSum小于0,则不可能跑完一圈
- 局部最优:curSum小于0时探索下一个起始点
- 全局最优:找到可以跑一圈的起始点
- 968.监控二叉树
- 交叉类难题
- DFS,从叶节点向根节点递归遍历
- 每次递归返回状态码:0:没有覆盖,1:有摄像头,2:已覆盖
- 局部最优:让叶节点的父节点安装摄像头,每隔两个节点放一个摄像头
- 全局最优:摄像头最少
-
- dp[i]:第i个斐波那契数
- 递推公式:dp[i] = dp[i-1] + dp[i-2]
- 初始化:dp[0] = 1, dp[1] = 1
- 遍历顺序:从前往后
-
- dp[i]:爬到第i阶有几种方法
- 递推公式:dp[i] = dp[i-1] + dp[i-2]
- 初始化:dp[0]不用考虑,dp[1] = 1, dp[2] = 2
- 遍历顺序:从前往后
-
- dp[i]:爬到第i阶的最小花费
- dp[0] = cost[0], dp[1] = cost[1]
-
- dp[i][j]:到达ixj网格有多少条不同的路径
- 初始化:所有行的第一列 和 所有列的第一行 都为1
- 两层遍历
-
- dp[i][j]:到达ixj网格有多少条不同的路径
- 初始化:所有行的第一列 和 所有列的第一行 (没有障碍的) 都为1
- 两层遍历
-
- dp[i]:整数i的最大拆分乘积
- dp[2]=1
- 两层遍历,每次用j * (i-j) 和 j * dp[i-j]的较大值与已经dp[i]比较取最大值
-
- dp[i]:1到i为节点组成的二叉搜索树的个数为dp[i]
- dp[0] = 1
- 两层遍历,每次要累加j从1到i作为根节点的树数量
- 0-1背包理论基础
- 416.分割等和子集
- 一维数组
- dp[j]:容量为j的背包能放的最大数值和
- 初始化:全部为0
- 两层遍历(j要倒序)
- dp[j] = max(dp[j], dp[j-nums[i]]+nums[i])
- 二维数组
- dp[i][j]:从0-i下标的数值里任选放入容量为j的书包的最大数值和
- 初始化:dp[i][0]=0; 当j>=nums[0]时dp[0][j]=nums[0]
- 两层遍历
- dp[i][j] = max(dp[i-1][j], dp[i-1][j-nums[i]]+nums[i])
- 1049.最后一块石头的重量II
- 一维数组
- dp[j]:容量为j的背包能放的最大数值和
- dp[j] = max(dp[j], dp[j-stones[i]]+stones[i])
- 二维数组
- dp[i][j]:0-i下标的石头范围内,能装入容量为j的最大重量
- dp[i][j] = max(dp[i-1][j], dp[i-1][j-stones[i]]+stones[i])
- 一维数组
- 一维数组