
LeetCode
文章平均质量分 85
分享 LeetCode 题解。
恋喵大鲤鱼
一条不止于编码的鱼。
展开
-
全排列(LeetCode 46)
例如,对{1,2,2},第一个数1与第二个数2交换得到212,然后考虑第一个数1与第三个数2交换,此时由于第三个数等于第二个数,所以第一个数不再与第三个数交换。使用字典序输出集合的全排列需要注意,因为字典序涉及两个排列之间的比较,对于元素集合不方便比较的情况,可以将它们在数组中的索引作为元素,按照字典序生成索引的全排列,然后按照索引输出对应集合元素的排列。还是以数组 {1,2,3} 为例,如果数组中有重复的元素,变成了{1,2,2},那么它的全排列就不能完全按照上面的方法求解,需要稍作改动。原创 2016-04-15 00:46:04 · 31830 阅读 · 9 评论 -
字符串的排列(LeetCode 567)
注意,因为字符仅包含 26 个小写字母,所以统计字符个数可以使用一个长度为 26 的数组,数组下标与 26 个小写字母一一对应。由于排列不会改变字符串中每个字符的个数,所以只有当两个字符串每个字符的个数均相等时,一个字符串才是另一个字符串的排列。根据这一性质,统计 s1 的字符个数,然后使用滑动串口遍历 s2,统计串口内字符个数是否需 s1 的相等。O(Σ),其中 Σ 是字符集字符数,这道题中的字符集是小写字母,所以 Σ 为 26。如果相等,那么 s2 包含 s1 的排列之一,返回 true。原创 2024-03-15 19:41:09 · 376 阅读 · 0 评论 -
有效的正方形(LeetCode 593)
注意:判断过程中,不用计算出两点实际距离,只需要算出距离的平方即可。给定 2D 空间中四个点的坐标 p1, p2, p3 和 p4,如果这四个点构成一个正方形,则返回 true。如果三角形两个边相等,则为直角边。如果直角边的平方和等于另一条边的平方,那么可断定为等腰直角三角形。正方形可以将其拆分成四个等腰直角三角形,所以枚举由三个点构成的三角形是否时等腰直角三角形即可。即有一组邻边相等,并且有一个角是直角的平行四边形称为正方形。根据上面的特点,我们可以计算出任意两点之间的距离来判断是否是正方形。原创 2024-03-13 12:17:59 · 558 阅读 · 0 评论 -
数组中第 K 大的数(LeetCode 215)
第一次划分时间复杂度是 O(n),第二次划分是 O(n/2)。最差的情况,最后一次即 logn 次找到第 K 大的数,那么时间复杂度为 n + n/2 + n/4 + …从快排的核心操作中可以看到,如果分界值的位置刚好是 K(升序为从后往前数),那么该分界值为数组中第 K 大的数。其核心操作是从数组中选择任意一个元素(通常选取第一个)作为分界值,通过双指针遍历数组,采用交换的方式使得左边的元素都小于等于它,右边的元素都大于等于它。先回顾一下快速排序,其属交换类排序,采用分治的思想,完成对无序数列的排序。原创 2022-09-19 18:39:29 · 1066 阅读 · 0 评论 -
移掉 K 位数字(LeetCode 402)
给你一个以字符串表示的非负整数 num 和一个整数 k,移除这个数中的 k 位数字,使得剩下的整数最小。这里需要注意,剩下的数不能有前导零。其实,最小的数有一个特点,就是小的数字在高位(左边),大的数字在低位(右边),比如 123 小于 321。对于两个相同长度的数字序列,最左边不同的数字决定了这两个数字的大小,因为高位的数字位权比低位的大。所以最小的数的数字应该是单调不降的,删除的 k 位数字都尽可能的在高位(左边)寻找。基于此,我们可以知道,若要使得剩下的数字最小,需要保证靠前的数字尽可能小。原创 2024-03-08 17:34:57 · 1359 阅读 · 0 评论 -
二叉树的直径(LeetCode 543)
具体而言,在计算当前二叉树的最大深度时,可以先递归计算出其左子树和右子树的最大深度,然后在 O(1) 时间内计算出当前二叉树的最大深度。如图我们可以知道路径 [9, 4, 2, 5, 7, 8] 可以被看作以 2 为根,从其左子树向下遍历的路径 [2, 4, 9] 和从其右子树向下遍历的路径 [2, 5, 7, 8] 拼接得到。知道了如何求解二叉树的高度之后,那么在递归搜索过程中记录当前结点作为根结点的最长路径。遍历每个结点作为根结点的最长路径上的结点数,其最长路径结点数等于其左子树与右子树高度和加 1。原创 2024-01-19 12:51:08 · 496 阅读 · 0 评论 -
合并K个升序链表(LeetCode 23)
这个方法和前两种方法的思路有所不同,我们需要维护当前每个链表没有被合并的元素的最前面一个,kkk 个链表就最多有 kkk 个满足这样条件的元素,每次在这些元素里面选取 val\textit{val}val 属性最小的元素合并到答案中。第二次合并后,结果链表的长度为 2n,第 i 次合并后,结果链表的长度为 in。考虑优先队列中的元素不超过 k 个,那么插入和删除的时间代价为 O(logk),这里最多有 kn 个结点,对于每个结点都被插入删除各一次,故总的时间代价为 O(kn*logk)。原创 2024-01-18 20:36:48 · 1618 阅读 · 0 评论 -
排序链表(LeetCode 148)
寻找链表的中点可以使用快慢指针的做法,快指针每次移动 222 步,慢指针每次移动 111 步,当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表。递归栈空间复杂度为 O(logn),不考虑的话为 O(1)。将两个排序后的子链表合并,得到完整的排序后的链表。可参考归并排序中的归并排序思想,主要有三个步骤。」的做法,将两个有序的子链表进行合并。下面以 Golang 为例给出实现。递归对左半部分和右半部分排序。原创 2024-01-18 16:23:49 · 547 阅读 · 0 评论 -
搜索二维矩阵 II(LeetCode 240)
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。原创 2024-01-04 09:28:59 · 613 阅读 · 0 评论 -
旋转图像(LeetCode 48)
其次这是一个特殊的二维矩阵,列数和行数是相等的,通常称之为方阵。你必须在「原地」旋转图像,这意味着你需要直接修改输入的二维矩阵。上一步操作的是最外层的一层 环,我们只需要一层层往里执行相同的操作,最终即可完成整个矩阵的旋转。给定一个 n × n 的二维矩阵 matrix 表示一个图像。我们需要遍历矩阵的所有元素,除了中间的那个元素。假设矩阵是 n*n 的,那么我们对 n/2 个环执行旋转即可完成。所谓的旋转,实际上是将每一位移动到下一个位置。观察上图,我们可以由外到内,一层一层地旋转。原创 2024-01-03 11:30:57 · 499 阅读 · 0 评论 -
缺失的第一个正数(LeetCode 41)
以题目中的示例二 [3, 4, -1, 1] 为例,恢复后的数组应当为 [1, -1, 3, 4],我们就可以知道缺失的数为 2。我们可以对数组进行一次遍历,对于遍历到的数 x=nums[i],如果 x∈[1,n],我们需要将 x 放在数组中的 x−1 的位置,因此交换 nums[i] 和 nums[x−1],这样 x 就出现在了正确的位置。由于我们只在意 [1,n] 中的数,因此我们可以先对数组进行遍历,把不在 [1,n] 范围内的数修改成任意一个大于 n 的数(例如 n+1)。原创 2023-12-28 12:53:22 · 1235 阅读 · 0 评论 -
除自身以外数组的乘积(LeetCode 238)
可以先计算给定数组所有元素的乘积,然后对数组中的每个元素 x,将乘积除以 x 求得除自身值以外的数组乘积。给你一个整数数组 nums,返回数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。这增加了这个问题的难度。我们不必将所有数字的乘积除以给定索引处的数字得到相应的答案,而是可以利用索引处左侧的所有数字乘积和右侧所有数字的乘积相乘得到答案。O(n^2),需要两层遍历,第一层为遍历数组中的每一个元素,第二层是遍历数组中除当前元素的其他所有元素。原创 2023-12-27 19:35:33 · 1078 阅读 · 0 评论 -
合并区间(LeetCode 56)
请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。如果我们按照区间的左端点排序,那么在排完序的列表中,可以合并的区间一定是连续的。如果当前区间的左端点在数组 merged 中最后一个区间的右端点之后,那么它们不会重合,我们可以直接将这个区间加入数组 merged 的末尾;首先,我们将列表中的区间按照左端点升序排序。否则,它们重合,我们需要用当前区间的右端点更新数组 merged 中最后一个区间的右端点,将其置为二者的较大值。我们用数组 merged 存储最终的答案。原创 2023-12-27 15:34:58 · 1100 阅读 · 0 评论 -
最大子数组和(LeetCode 53)
如何找到跨越中点的最大子数组,很简单,我们只要以 mid 为起点,向左遍历找出左边最大子数组。此递归式与归并排序的递归式一样,求解递归式的时间复杂度可以采用《算法导论中》中文第三版的4.5节中提出的方法,可快速求解上面递归式的时间复杂度 T(n)=O(nlogn)。对于一个包含负数的数组,从数组左边开始,依次累加元素和,当小于 0 时,从下一个元素重新开始累加并与前面最大子数组的和比较,记录最大者。在这里,对问题进行简化,假设原问题是规模的2的幂,这样所有子问题的规模均为整数。原创 2016-03-13 15:42:46 · 4138 阅读 · 2 评论 -
最小覆盖子串(LeetCode 76)
在滑动窗口类型的问题中都会有两个指针,一个用于「延伸」现有窗口的 r 指针,和一个用于「收缩」窗口的 l 指针。当窗口包含 t 全部所需的字符后,如果能收缩,我们就收缩窗口直到得到最小窗口。我们可以用一个哈希表表示 t 中所有的字符以及它们的个数,用一个哈希表动态维护窗口中所有的字符以及它们的个数,如果这个动态表中包含 t 的哈希表中的所有字符,并且对应的个数都不小于 t 的哈希表中各个字符的个数,那么当前的窗口是「可行」的。本问题要求我们返回字符串 s 中包含字符串 t 的全部字符的最小窗口。原创 2023-12-26 14:39:39 · 1234 阅读 · 0 评论 -
翻转二叉树(LeetCode 226)
二叉树翻转是一道经典的面试编程题,经常出现在各大公司的招聘笔试面试环节。这里还有个趣事,Homebrew的作者Max Howell某天去 Google 面试,面试官出了一道翻转二叉树的题目,然而 Max Howell 没答上来,结果被拒。面试官的评语是:“我们 90% 的工程师使用您编写的软件,但是您却无法在面试时在白板上写出翻转二叉树这道题,所以滚蛋吧”。可见,在求职面试过程中,即使你是一位优秀的程序员,如果答不上算法题,那么在算法方面的能力将被面试官认为是不及格的,甚至无法被聘用。原创 2020-05-29 15:04:16 · 2626 阅读 · 1 评论 -
二叉树的中序遍历(LeetCode 94)
根据中序遍历的顺序,对于根结点,先访问其左孩子,而左孩子又可以看做一根结点,然后继续访问其左孩子,直到遇到左孩子为停止访问,然后按相同的规则访问其右子树。而在访问左子树或右子树的时候我们按照同样的方式遍历,直到遍历完整棵树。空间复杂度取决于递归的栈深度,而栈深度在二叉树为一条链的情况下会达到 O(n) 的级别。(1)若其左孩子不为空,循环将 R 及其左结点入栈,直至左结点为空;给定一个二叉树的根节点 root ,返回 它的 中序 遍历。(3)重复(1)和(2)的操作,直到 cur 为空且栈为空。原创 2022-09-18 23:24:30 · 906 阅读 · 0 评论 -
反转链表(LeetCode 206)
从倒数第二个节点开始反转,依次向前,将后一个节点的 next 指向当前节点。注意每次反转后要将当前节点的 next 置空,表示断开当前节点与后一个节点的关联。由于每次递归都需要为实参分配空间,所以相较于非递归实现,较耗费栈空间,且不易理解。给定单向链表 1->2->3->4->5,反转后为 5->4->3->2->1。给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。出题公司:腾讯、阿里、百度、字节、虾皮等,极其热门,务必掌握。给定单向链表 1->2->3,反转后为 3->2->1。原创 2019-06-22 23:02:05 · 7666 阅读 · 2 评论 -
滑动窗口最大值(LeetCode 239)
因为堆不支持删除指定的元素,删除元素只能将堆顶的元素弹出,所以在移动窗口时,左边离开窗口的元素不着急从堆中删除,而是当堆顶元素不在窗口中时,不断地移除堆顶的元素,直到堆顶的元素出现在滑动窗口中。与方法一不同的是,在方法二中我们使用的数据结构是双向的,因此「不断从队首弹出元素」保证了队列中最多不会有超过 k+1 个元素,因此队列使用的空间为 O(k)。为了方便判断堆顶元素与滑动窗口的位置关系,我们在堆中存储二元组 (num, index),堆的元素是下标 index,权重是下标对应的值 num。原创 2023-12-15 15:59:51 · 1128 阅读 · 0 评论 -
和为K的子数组(LeetCode 560)
但是如果我们知道 [j,i] 子数组的和,就能 O(1) 推出 [j−1,i] 的和,因此这部分的遍历求和是不需要的,我们在枚举下标 j 的时候已经能 O(1) 求出 [j,i] 的子数组之和。O(n),其中 n 为数组的长度。我们遍历数组的时间复杂度为 O(n),中间利用哈希表查询删除的复杂度均为 O(1),因此总时间复杂度为 O(n)。所以,当我们考虑以 i 结尾和为 k 的连续子数组个数时,只需要统计有多少个前缀和为 pre[i] - k (即 pre[j - 1])的个数即可。原创 2023-12-12 15:37:39 · 2301 阅读 · 0 评论 -
找到字符串中所有字母异位词(LeetCode 438)
O(m+(n−m)×Σ),其中 n 为字符串 s 的长度,m 为字符串 p 的长度,Σ 为所有可能的字符数。因为字符串 p 的异位词的长度一定与字符串 p 的长度相同,所以我们可以在字符串 s 中构造一个长度为与字符串 p 的长度相同的滑动窗口,并在滑动中维护窗口中每种字母的数量;当窗口中每种字母的数量与字符串 p 中每种字母的数量相同时,则说明当前窗口为字符串 p 的异位词。当字符串 s 的长度小于字符串 p 的长度时,字符串 s 中一定不存在字符串 p 的异位词。不考虑答案输出的顺序。原创 2023-12-11 17:39:13 · 1430 阅读 · 0 评论 -
无重复字符的最长子串(LeetCode 3)
给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。s 由英文字母、数字、符号和空格组成。原创 2023-12-08 17:33:42 · 490 阅读 · 0 评论 -
最小栈(LeetCode 155)
因此我们可以使用一个辅助栈,与元素栈同步插入与删除,用于存储与每个元素对应的最小值。设计一个支持 push,pop,top 和 min 操作的栈,min 为获取栈中的最小元素,要求 push、pop、top 和 min 都是 O(1) 时间复杂度。当一个元素要入栈时,我们取当前辅助栈的栈顶存储的最小值,与当前元素比较得出最小值,将这个最小值插入辅助栈中;如果栈中的元素为 n,那么辅助栈空间也为 n,所以空间复杂度为 O(n)。在任意一个时刻,栈内元素的最小值就存储在辅助栈的栈顶。出题公司:腾讯、虾皮。原创 2022-10-07 21:41:14 · 434 阅读 · 0 评论 -
合并两个有序链表(LeetCode 21)
不能开辟新空间来存储合并后的链表。如果第一次做该题,很容易会想到使用新链表来存储合并后的有序链表。虽然可以如此实现,但是不符合要求。从上面合并两个有序链表的步骤中可以看出,每次合并的步骤(2)都是一样的,由此我们想到了递归。将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。输入:两个有序的单链表 head1 与 head2。输出:合并后的有序单链表 mergeHead。原创 2018-03-20 01:49:40 · 4438 阅读 · 0 评论 -
K 个一组翻转链表(Leetcode 25)
对于一个子链表,除了翻转其本身之外,还需要将子链表的头部与上一个子链表连接,以及子链表的尾部与下一个子链表连接。反复移动指针 head 与 pre,对 head 所指向的子链表进行翻转,直到结尾,我们就得到了答案。给定一个链表分组,即给定首位结点。给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。当 k = 1 时,应当返回: 1->2->3->4->5。当 k = 2 时,应当返回: 2->1->4->3->5。当 k = 3 时,应当返回: 3->2->1->4->5。原创 2023-06-21 21:26:58 · 832 阅读 · 0 评论 -
相交链表(LeetCode 160)
当链表 headA 和 headB 都不为空时,创建两个指针 pA 和 pB,初始时分别指向两个链表的头节点 headA 和 headB,然后将两个指针依次遍历两个链表的每个节点。然后开始从当前位置同时遍历两个链表,当遍历到的链表的节点相同时,则这个节点就是第一个相交的节点。从头开始遍历第一个链表,遍历第一个链表的每个节点时,同时从头到尾遍历第二个链表,看是否有相同的节点,第一次找到相同的节点即第一个交点。若两链表相交,则循环出栈,直到遇到两个出栈的节点不相同,则这个节点的后一个节点就是第一个相交的节点。原创 2023-12-06 14:17:43 · 1206 阅读 · 0 评论 -
三数之和(LeetCode 15)
给你一个整数数组 nums,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i!= k ,同时还满足 nums[i] + nums[j] + nums[k] == 0。时间复杂度:双指针移动的复杂度是 O(n),再加上外能循环的复杂度 O(n),所以总的时间复杂度是。在这之后,我们还需要使用哈希表进行去重操作,得到不包含重复三元组的最终答案,还会消耗了大量的空间。我们可以将三元组元素拼成一个字符串写入哈希表,然后遍历所有三元组,去掉重复的三元组。原创 2023-12-06 09:24:40 · 1033 阅读 · 0 评论 -
按权重随机选择(LeetCode 528)
设数组 w 的权重之和为 total。例如,对于 w = [1, 3],挑选下标 0 的概率为 1 / (1 + 3) = 0.25 (即 25%),而选取下标 1 的概率为 3 / (1 + 3) = 0.75(即 75%)。注意:需要先确定待查找的元素在数组中的位置 , 根据题意, 前缀和数组中第一个 >= x 的元素就是待查找的元素,确定了这一点才能写出正确的二分查找。例如对于数组 w[1,3,5,6],计算其累计的前缀和数组为 [1,4,9,15],然后随机产生一个 [1,15] 之间的随机数。原创 2022-10-08 13:05:19 · 778 阅读 · 0 评论 -
最大正方形(LeetCode 221)
输入:matrix = [[“1”,“0”,“1”,“0”,“0”],[“1”,“0”,“1”,“1”,“1”],[“1”,“1”,“1”,“1”,“1”],[“1”,“0”,“0”,“1”,“0”]]如果 i 和 j 中至少有一个为 0,则以位置 (i,j) 为右下角的最大正方形的边长只能是 1,因此 dp(i, j) = 1。暴力求解过程中,我们可以发现,在遍历每一个可能的最大正方形时,存在着很多重复的计算,因此我们可以使用动态规划来记录每一步骤中的中间结果,来减少重复的计算。以下用一个例子具体说明。原创 2022-10-12 21:50:16 · 469 阅读 · 0 评论 -
最长连续序列(LeetCode 128)
但仔细分析这个过程,我们会发现其中执行了很多不必要的枚举,如果已知有一个 x,x+1,x+2,⋯ ,x+y 的连续序列,而我们却重新从 x+1,x+2 或者是 x+y 处开始尝试匹配,那么得到的结果肯定不会优于枚举 x 为起点的答案,因此我们在外层循环的时候碰到这种情况跳过即可。增加了判断跳过的逻辑之后,时间复杂度是多少呢?我们考虑枚举数组中的每个数 x,考虑以其为起点,不断判断 x+1,x+2,⋯x+1, x+2,⋯ 是否存在,假设最长匹配到了 x+y,其长度为 y+1,我们不断枚举并更新答案即可。原创 2023-12-04 14:47:44 · 1259 阅读 · 0 评论