力扣算法题总结

lc253

题目:求最多重叠(x,y)的数量
思路:按y排序,把y放入优先队列,逐个比较x,x大于优先队列的堆顶元素就弹出堆顶。

lc148

题目:对链表排序
思路:归并排序。快慢指针找到链表中点,递归找下去,然后比较左右结点的值,创建一个链表头节点串起来。

lc41

题目:找出未排序数组中没有出现的最小的正整数
思路:原地哈希,将数值为i的数映射到下标为i-1的位置

lc1

题目:两数之和
思路:哈希表存,找与目标值的差值是否在哈希表中

lc49

题目:将字母构成相同的单词分成一类
思路1:给每个单词排序,把排序后相同的单词存入map,key为单词,value为存单词的列表。
思路2:遍历单词,用数组存单词的字母出现次数,遍历数组把大于0的字母拼接成字符串,作为哈希表的Key。

lc128

题目:找出数组中数值连续的最长序列,要求时间复杂度为O(n)
思路:用set存入每个数组元素,遍历set,当前元素为t,如果不存在t-1的元素在set中,就从t开始寻找连续的数值序列,寻找t+1是否在set中。

lc283

题目:原地将数组所有0移动到数组末尾
思路:双指针,一个i指向0,一个j指向非0,当j遇到非0时就与i交换数字。

lc11

题目:求数组中盛水最多的值。即两个下标的数值中的最小值与下标之差相乘,要得到这个积的最大值。
思路:双指针,left指开头,right指末尾,记录积的最大值后,判断下标left代表的数组元素数值大,还是right代表的数组元素数值大,哪个小就移动哪一个。

lc15

题目:三数之和。找出数组中三个加起来等于0的数,不能重复。
思路:排序,先定位一个数,再对另外两个数用双指针的方法找,加起来大于0就right–,加起来小于0就left++。为了保证不重复遍历的时候需要跳过相邻相等的数。

lc42

题目:接雨水
思路:按列求。先分别求左边的最大值数组和右边的最大值数组,再遍历数组,比较当前元素的左边最大值和右边最大值,取更小的那个值min,如果这个值min比当前元素cur的值大,则当前列的雨水为min-cur,加入总雨水值中。

lc3

题目:无重复字符的最长子串
思路:滑动窗口。用map存字母的下标位置,用right去遍历到,一个字母如果已经存在于map中,则更新left的位置,更新为map中已存的这个字母的下标+1和left旧位置中的最大值。每次遍历都要更新无重复子串的最大值,并且更新当前字母在map中的值。

lc662 (快手一面手撕)

题目:二叉树的最大宽度
思路:手撕没撕出来,很烦。正确做法应该是层次遍历,用队列去存每一层的结点,同时要保存结点的下标,左子结点为2i,右子节点为2i+1。队列首先弹出第一个元素,代表二叉树这一层的最开头的结点,记录下它的下标,然后一直执行弹出元素并添加元素子结点的操作,一直把这一层结点弹完(在弹第一个元素之前记录队列的大小,即为这一层的结点数量),记录最后一个结点的下标,与最开头结点下标相减,将结果与最大值比较并更新。

lc139

题目:单词拆分。给一个字符串s和一组字符串list,求一组字符串中是否能选出几个字符串组成s。
思路:动态规划。dp[i]表示s的前i个字符的子串s[0,i-1]是否能被list中的字符串组合。i从前往后遍历,对于每个i都需要枚举包含i-1的最后一个单词,看这个单词是否在list中。转移方程为dp[i] = dp[j] && check(s[j,i-1])。
也可以看作是完全背包问题,字符串s是背包,字典中的单词是物品。外层循环遍历背包,内层循环遍历物品。

lc438

题目:找到字符串中所有的字母异位词。
思路:滑动窗口。这种子串问题一遇到就要想到用滑动窗口。用一个map(need)存异位词的所有字符,用另一个map(window)存滑动窗口中的所有字符。当两个map存的某一个字符的数量相等时,将count++。当窗口的大小为异位词的长度时(right-left==p.length()),就判断count是否等于need的大小,如果等于说明此时窗口中已经包含异位词。此时就记录下left的位置,并将窗口右移,并将window中left位置的字符数量减一。

lc76

题目:最小覆盖子串。在s中找到一个最小的子串包含t中的所有字符。
思路:滑动窗口。与lc438类似,同样用两个map来存t的字符后窗口中的字符,差别是窗口收缩的条件变成count==map.size()。

lc239

题目:滑动窗口最大值。
思路:用双向队列实现单调队列,保证队列首部是窗口中的最大值,尾部是最小值。每次都要将元素和队列尾部元素比较,如果比队列尾部元素大,则弹出尾部元素,直到遇到比它大的尾部元素,再将当前元素添加到队列尾部,以此实现队列首部到尾部单调递减的单调队列。要注意的是,每次向右滑动窗口,要判断队列首部元素是不是要被删除的元素,是的话则弹出队列首部元素。

lc53

题目:最大子数组和
思路:用sum累加数组里面的元素,每次累加后更新最大值,sum小于0时把sum置为0。

lc56

题目:合并区间。合并有重叠的区间。
思路:用一个left来存已经遍历过的元素的x的最小值,用一个right来存已经遍历过的元素的y的最大值,如果right比当前元素的x大,则说明有重叠,更新right为当前元素的y。如果小,则将当前的(left,right)添加进结果集中,并将(left,right)更新为当前元素的(x,y)。

lc189

题目:轮转数组。将数组中的元素向右轮转k个元素。
思路:前k个元素翻转一遍,后k个元素翻转一遍,整体再翻转一遍。
(A-1 B-1)-1 = (B-1)-1(A-1)-1 = BA

lc 238

题目:除自身以外数组的乘积
思路:求出前缀积和后缀积,遍历一个元素时将该元素的前缀积和后缀积相乘即可。

lc54

题目:螺旋矩阵
思路:模拟。设定上下左右四个方向,按照右下左上的顺序走,从左到右走到尽头,将所遍历的行top加1,并判断是否大于行bottom。依次类推。

lc48

题目:旋转图像。顺时针旋转矩阵。
思路:先把矩阵变为它本身的转置,即(i,j)变成(j,i)。然后对每行元素进行逆序即可。

lc240

题目:搜索二维矩阵2。
思路:从右上角开始找,二分的思想,如果大于目标值就y–,如果小于就x++。

lc234

题目:回文链表
思路:快慢指针找到链表中点,然后翻转后半段,再与前半段比较。

lc142

题目:环形链表2。要求找到入环点。
思路:当慢指针遇到快指针时,慢指针再走一段距离能到入环点,这段距离与从起点到入环点的距离相等,所以当快慢指针相遇时,在定义两个指针,一个指向起点,一个指向快慢指针相遇的点,一起移动,当相遇时就说明到了入环点。

lc2

题目:两数相加。在链表上模拟加法。
思路:同时遍历两个链表,判断当前结点是否为空,为空则值为0,不为空则值为val,将两个链表当前结点的值加起来,为sum,并且要加上上一个值的进位,这个进位用一个变量carry保存下来。sum/10为这个结点的进位,将加给下一个结点的和,sum%10为新链表结点的val。遍历两个链表完后,要注意此时carry的值,如果为1要再创建一个结点保存。

lc24

题目:两两交换链表中的节点
思路:主要要控制好交换节点的前后链接。首先要创建一个新节点res连在头节点前面,方便最终返回结果。再创建一个指针pre指向res,一个指针cur指向head。遍历链表,首先保存hou = cur.next.next,因为交换节点后还要将后面那个节点链接到cur.next.next上。然后将pre指向cur.next,再将两个节点的后一个节点指向前一个节点,即cur.next.next = cur。再将两个节点的前一个节点指向hou,即cur.next = hou。再移动pre指针为cur(此时cur因为交换节点已经变成了两个节点的后一个节点),移动cur指针为hou。

lc25

题目:K个一组翻转链表
思路:与lc24类似,主要要保存好翻转链表后的前后节点,以便翻转后还能接回原链表。

lc138

题目:随机链表的复制。创建一个新链表保存旧链表的所有信息。
思路:用哈希表存旧链表的结点,key为旧链表的结点,value为新建的一个结点,用旧结点的val初始化。然后再遍历一遍旧链表,构建新链表结点的next和random两个指针的指向,即map.get(cur).next = map.get(cur.next)和map.get(cur).random = map.get(cur.random)。返回map.get(head)。

lc146

题目:LRU缓存
思路:LRU策略是要选择一个最近最少使用的元素删除,填上新来的元素。要实现这个策略的话,可以用一个链表来构建缓存区,按元素被使用的顺序存储键值对,靠近链表头部的键值对是最近使用的,靠近尾部的键值对是最久未使用的。
每次向缓存区增加新的元素,如果这个元素已经在缓存区,则把这个元素所在的结点移到链表最前面,如果不在缓存区,则新建一个结点放到链表最前面。增加一个结点后,还要判断链表长度是否大于缓存区的大小,如果大了则要删除链表最后一个结点。
此时问题就来了,怎么判断这个元素在不在缓存区呢? 这时就可以用一个hashmap来存储元素所在的结点位置,key为元素值,value为元素所在的结点,通过map映射得到元素所在的结点后,如果要删除这个结点,就要得到这个结点的前面的结点和后面的结点,为了方便得到前后结点,链表可以使用双向链表,这样就方便进行删除结点的操作。如果要移动结点到链表最前头,直接删除后再在链表首部插入一个新建的结点就可以。
同时,为了方便在链表头部插入结点和在链表尾部删除结点,可以创建额外的头结点和尾结点,使得更好操作。

lc101

题目:对称二叉树
思路:同时遍历树的左节点和右节点,比较是否相等。

lc543

题目:二叉树的直径
思路:对于每一个结点,计算它的左右子结点的深度,取最大值+1(要加上子结点到本结点的路径长度1)返回给父结点,并用左右子结点的深度之和再+2(要加上左右子结点到本结点的路径长度)更新直径的最大值。

lc124

题目:二叉树的最大路径和
思路:与lc543类似,区别是这题求的路径中各节点值的总和得最大值,而lc543求得是路径的最大长度。

lc102

题目:二叉树的层序遍历
思路:用队列实现。重点是要知道哪些结点是同一层的,所以每次都要把队列上次存入的结点全部弹出去,这样这次存入的结点就是同一层的了,同一层结点的数量就是队列的大小,所以每次都要先记录下队列的大小size,每弹出一个结点,size就减1,直到为0,就说明这一层的结点统计完了。

lc98

题目:验证二叉搜索树
思路:看到二叉搜索树的题目要想到中序遍历,因为中序遍历二叉搜索树就是一个升序数列,利用这个特性来解题。要验证是不是二叉搜索树,只要判断中序遍历是不是一个升序数列,判断此时结点的数值是否大于前一个结点的数值就可以了,可以把前一个结点的数值用全局变量存下来。

lc230

题目:二叉搜索树中第K小的元素
思路:一样用中序遍历,遍历到第k个结点就行了。

lc199

题目:二叉树的右视图
思路:与层序遍历类似,用队列来存储每一层的结点,每一层最后那个结点就是右视图能看到的结点。

lc114

题目:二叉树展开为链表
思路:把结点的左子树放到右子树的位置,再把右子树放到左子树的最右边结点下。

lc105

题目:根据前序遍历和中序遍历构建二叉树
思路:对于二叉树而言,前序遍历的结果是:

[ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]

中序遍历的结果是:

[ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]

在这里插入图片描述
用map存中序遍历元素的下标,用前序遍历元素去取map中的下标,每取一个,为当前的结点,然后对左右子树进行递归处理。左子树的前序遍历下标区间是[preLeft+1,preLeft+pIndex-inLeft],中序遍历下标区间是[inLeft,pIndex-1],右子树的前序遍历下标区间是[preLeft+pIndex-inLeft+1,preRight],中序遍历下标区间是[pIndex+1,inRight]。

lc106

题目:根据后序遍历和中序遍历构建二叉树
思路:对于二叉树而言,后序遍历的结果是:

[  [左子树的前序遍历结果], [右子树的前序遍历结果], 根节点]

中序遍历的结果是:

[ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]

可以看到后序遍历与前序遍历的区别就是根节点放到了尾部,所以从map中取元素时,从后序遍历的尾部开始取即可。除此之外,后序遍历的下标区间也与前序有一点不同,左右子树的区间下标都比前序遍历少1,因为根结点放到了最后。其它与lc105一致。

lc5

题目:最长回文子串
思路:动态规划。dp[i][j]代表字符串s的子串(j,i)是不是回文子串。状态由(j+1,i-1)的子串转移而来,如果子串(j+1,i-1)是回文子串同时s[i]==s[j],那么子串(j,i)就是回文子串。

lc134

题目:加油站
思路:累加每经过一个加油站的剩余油量,如果当前的累加油量比之前还少,说明到这个加油站仍然是消耗量大于油量,如果当前的累加油量比之前多了,
说明到这个加油站的油量大于到这来的消耗量。如果最终的累加油量为负数,说明没办法跑完,如果为正数,起点就是累加油量第一次有上升趋势的那个结点。

lc739

题目:每日温度。找到这个元素之后第一个比它大的元素。
思路:单调栈。遍历数组元素,如果当前元素比栈顶元素小,则入栈,如果大,则弹出栈顶元素,接着比较下一个栈顶元素,直到当前元素比栈顶元素小后就将当前元素入栈。在比较过程中,弹出来的栈内元素都是比当前元素小的元素,说明当前元素就是第一个比弹出来的元素大的元素。

lc84

题目:柱状图中最大的矩形
思路:分别找当前列左边第一个高度小于它的列left和右边第一个高度小于它的列right,当前列的最大矩形的底就是(left+1, right-1),高就是当前列的高度。遍历每个列,找到最大面积的矩形。
这种找左右边第一个小于或者大于当前元素的方法可以用单调栈。

lc994

题目:腐烂的橘子
思路:BFS。题目可以转化为求BFS的层数,跟之前层序遍历的套路一样,先计算队列的大小,把这个大小的队列清空即BFS的这一层清空了,层数加1。需要注意的是,图中可能存在多个腐烂的橘子,即多个BFS的起点,需要在开始时都加入队列中,再去BFS。同时需要计算新鲜橘子的数量,每把一个新鲜橘子放入队列中,新鲜橘子的数量就减1,当数量小于0时就可以退出BFS了。如果BFS完了最后新鲜橘子的数量还大于0,就按照题意返回-1。

lc34

题目:在排序数组中查找元素的第一个和最后一个位置
思路:二分查找。如果要找第一个位置,就用二分查找找到元素后继续向左查找,即让right=mid-1。如果要找最后一个位置,就用二分查找找到元素后继续向右查找,即让left=mid+1。

lc33

题目:搜索旋转排序数组
思路:先用二分查找找到最小值,即把二分查找比较的target设为数组第一个元素a,二分查找找到第一个小于元素a的元素b。然后以元素b为左端点,元素b下标加数组长度为右端点,进行二分查找,注意查找中访问数组元素时要取模。

lc153

题目:寻找旋转排序数组中的最小值
思路:二分查找找到第一个小于数组第一个元素的元素,当前元素大于第一个元素就向当前元素的右边找,当前元素小于第一个元素就向当前元素的左边找。

lc20

题目:有效的括号
思路:遍历,遇到左括号就入栈,遇到右括号先判断栈是否为空,为空就直接入栈,不为空就判断当前右括号和栈顶括号是否匹配,匹配就弹出栈顶元素,不匹配就将当前右括号入栈。最后栈为空即是有效的括号组。

lc155

题目:最小栈
思路:用一个栈a存元素,再用一个栈b存最小的元素。添加元素时,栈a直接入栈,栈b判断一下栈顶元素是否大于要添加的元素,如果大于就添加,小于就不添加。弹出元素时,栈a直接弹出,栈b判断一下栈顶元素是否等于栈a弹出的元素,如果相等才弹出。

lc394

题目:字符串解码
思路:用一个栈a存括号前面的数字,用另一个栈b存字符串。遇到左括号时,就把数字存入栈a,把字符串存入栈b,然后把数字和字符串变量都清空,准备装这个括号右边的字符串。遇到字符时,就把它装入字符串变量。遇到右括号时,就从栈a里取出栈顶元素,乘以字符串变量得到这个括号内的字符串,再和栈b的栈顶元素字符串相加。

lc215

题目:数组中的第K个最大元素
思路:堆排序。先建堆。从首个非叶子结点开始,即数组长度的一半减1这个结点,判断该结点是否大于左右子结点。把这三个结点中最大的一个结点换到父结点的位置。递归调整子堆。

lc347

题目:前K个高频元素
思路:优先队列。先用map统计每个数的出现次数,再往优先队列里面放。优先队列是小顶堆,按照出现次数排序。当优先队列大小没达到k时,直接往里面放数和对应的次数。当优先队列大小等于k时,需要比较当前数的出现次数和堆顶的数的出现次数,如果堆顶的数的出现次数更少,则弹出堆顶,将当前的数和出现次数放入优先队列。否则就不放入。

lc55

题目:跳跃游戏
思路:贪心。遍历每个元素,遍历终止条件为当前跳跃的覆盖范围。每次遍历都更新一下当前的覆盖范围,如果当前下标加上当前元素值大于当前覆盖范围,就要更新为新的覆盖范围。如果当前覆盖范围已经大于数组长度,则能到到达最后一个下标。

lc45

题目:跳跃游戏2
思路:跟上题类似,只是要判断一下当前访问的下标是否为覆盖范围的右边界,如果达到了,则更新覆盖范围并且跳跃次数加1。

lc763

题目:划分字母区间
思路:由于同一个字母只能出现在同一个片段,显然同一个字母的第一次出现的下标位置和最后一次出现的下标位置必须出现在同一个片段。因此需要遍历字符串,得到每个字母最后一次出现的下标位置。初始区间是start下标0到end下标0字母的最后一次出现的位置,如果这个end位置比接下来字母的最后一次出现位置更小,就需要更新end位置,直到遍历的下标等于end位置,就把start到end区间存入结果列表,同时更新start位置为下一个下标。

lc198

题目:打家劫舍
思路:dp[i]表示前i间房屋能偷到的最高总金额,对于第i间房间,如果选择偷,就不能偷i-1的房间的,偷窃总金额为前i-2间房屋加上第i间房屋的金额。如果选择不偷,偷窃总金额为前i-1间房屋的最高总金额。在这两个选择中选择金额最大的选项,转移方程为dp[i] = max(dp[i-2] + nums[i], dp[i-1])。

lc213

题目:打家劫舍2
思路:跟上题类似,只是房屋成环了,成环了所以房屋首尾中只能选择一个,所以有两种情况,第一种选择首栋房子不选择最后一栋,等于dp的范围变成(0,nums.length-2)。第二章选择最后一栋不选择首栋,dp范围变成了(1, nums.length-1)。创建两个dp数组对这两种情况遍历一下,最后对比两个dp数组的nums.length-2元素,选择更大的那一个即可。

lc279

题目:完全平方数
思路:背包问题。dp[j]表示和为j的完全平方数的最少数量,dp[j]可以由dp[j-ii]推出,递推公式为dp[j] = min(dp[j-ii]+1, dp[j])。

lc322

题目:零钱兑换
思路:完全背包问题,不限制同一种硬币的使用次数。外层循环遍历物品(不同的硬币),内层循环遍历背包(金额)。dp[j]表示金额为j所需的最少硬币数量,凑足金额为j-coin[i]的最少个数为dp[j-coin[i]],dp[j-coin[i]]加上一个钱币coin[i]即dp[j-coin[i]]+1就是dp[j]。递推公式为dp[j] = min(dp[j-coin[i]]+1, dp[j])。

lc300

题目:最长递增子序列
思路:动态规划。dp[i]表示以nums[i]为结尾的序列的最长递增子序列长度。为什么一定表示 “以nums[i]结尾的最长递增子序” ,因为我们在做递增比较的时候,如果比较 nums[j] 和 nums[i] 的大小,那么两个递增子序列一定分别以nums[j]为结尾 和 nums[i]为结尾, 要不然这个比较就没有意义了,不是尾部元素的比较那么 如何算递增呢。如果nums[i]大于nums[j],状态转移方程为dp[i] = max(dp[j]+1, dp[i])。j是i之前的下标。

lc152

题目:乘积最大子数组
思路:用一个数组a存最大值,一个数组b存最小值。每遍历一个数,比较数组a乘这个数,数组b乘这个数,和这个数本身。这三个数中的最大值赋给数组a,最小值赋给数组b。最后返回数组a中的最大值。

lc416

题目:分割等和子集
思路:01背包问题。背包是数组总和的一半,因为要分割数组为相等的两半,所以只要看是否能选几个元素加起来等于总和的一半就行。外层循环是物品(数组元素),内层循环是遍历背包(元素和),注意内层循环要倒序(就记住01背包问题,内层循环倒序)。dp[j]表示背包总容量为j时背包能放的最大重量。转移方程是dp[j] = max(do[j], dp[j-nums[i]]+nums[i])。dp[sum/2]=sum/2时就说明能等半分割。

首先是背包分类的模板:
1、0/1背包:外循环nums,内循环target,target倒序且target>=nums[i];
2、完全背包:外循环nums,内循环target,target正序且target>=nums[i];
3、组合背包:外循环target,内循环nums,target正序且target>=nums[i];
4、分组背包:这个比较特殊,需要三重循环:外循环背包bags,内部两层循环根据题目的要求转化为1,2,3三种背包类型的模板

然后是问题分类的模板:
1、最值问题: dp[i] = max/min(dp[i], dp[i-nums]+1)或dp[i] = max/min(dp[i], dp[i-num]+nums);
2、存在问题(bool):dp[i]=dp[i]||dp[i-num];
3、组合问题:dp[i]+=dp[i-num];

lc62

题目:不同路径
思路:dp[i][j] = dp[i-1][j] + dp[i][j-1]

lc63

题目:不同路径2
思路:跟上题类似,遇到障碍就把dp[i][j]置为0。

lc64

题目:最小路径和
思路:dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j];

lc1143

题目:最长公共子序列
思路:dp[i][j]:长度为[0, i - 1]的字符串text1(不一定以text1[i-1]结尾)与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]。如果text1[i-1]等于text2[j-1],dp[i][j] = dp[i-1][j-1]+1。如果不相等,就从dp[i-1][j]和dp[i][j-1]中选一个最大的赋给dp[i][j]。

lc72

题目:编辑距离
思路:将单词1变成单词2的最后操作次数。dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]。如果word1[i-1]等于word2[j-1],表示不用进行操作,dp[i][j] = dp[i-1][j-1]。如果不相等,可能需要word1或者word2删除或增加一个元素,即dp[i][j] = dp[i-1][j] + 1或者dp[i][j] = dp[i][j-1] + 1,也可能需要替换元素,即dp[i][j] = dp[i-1][j-1] + 1。所以不相等的时候需要在这三种情况中去最小。初始化dp需要注意,dp[i][0]表示以下标i-1为结尾的字符串word1,和空字符串word2,最近编辑距离为dp[i][0],所以dp[i][0]要初始化为i,dp[0][j]同理。

lc31

题目:下一个排列
思路:从后往前找第一个相邻升序的元素对(i,j),满足a[i]<a[j]。此时[j,end]必然是降序。在[j,end)从后往前找第一个满足a[i]<a[k]的,将这两个数交换,然后逆置[j,end),使其升序,然后就返回。如果找不到满足情况的,就把整个数组逆序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值