LeetCode每日一题打卡开始于2021-05-14,预计打卡至大四毕业,总共两年多,每天都会打卡,但不一定会更新到这上面。我不想再当咸鱼了(逃。
12. 整数转罗马数字
2021-05-14,数电课延后一周再上,上完我就要开始学了。
方法一:模拟
思路
根据罗马数字的唯一表示法,为了表示一个给定的整数num,我们寻找不超过num的最大符号值,将num减去该符号值,然后继续寻找不超过num的最大符号值,将该符号拼接在上一个找到的符号之后,循环直至num为0。最后的得到的字符即为num的罗马数字表示。
编程时,可以建立一个数值-符号对的列表valueSymbols,按数值从大到小排列。遍历valueSymbols中的每个数值-符号对,若当前数值value不超过num则从num中不断减去value,直至num小于value,然后遍历下一个数值-符号对。若遍历中num为0则跳出循环。
代码
1 | const pair<int, string> valueSymbols[] = { |
复杂度分析
时间复杂度:O(1)。由于 valueSymbols 长度是固定的,且这 13 字符中的每个字符的出现次数均不会超过 3,因此循环次数有一个确定的上限。对于本题给出的数据范围,循环次数不会超过 15 次。
空间复杂度:O(1)。
13. 罗马数字转整数
今天是2021-05-15,周六,可是为什么还有实验课啊,滑动变阻器坏了,实验没做出来,躺平~
方法一:模拟
思路
通常情况下,罗马数字中小的数字在大的数字的右边。若输入的字符串满足该情况,那么可以将每个字符视作一个单独的值,累加每个字符对应的数值即可。
例如XXVII
可视作X
+ X
+ V
+ I
+ I
= 10 + 10 + 5 + 1 + 1 = 27。
若存在小的数字在大的数字的左边的情况,根据规则需要减去小的数字。对于这种情况,我们也可以将每个字符视作一个单独的值,若一个数字的右侧的数字比它大,则该数字的符号取反。
例如XIV
可视作 X
- I
+ V
= 10 - 1 + 5 = 14。
代码
1 | class Solution { |
复杂度分析
时间复杂度:O(n)。其中n是字符串s的长度。
空间复杂度:O(1)。
421. 数组中两个数的最大异或值
2021-05-16,周日,玩了一上午的OW(破坏球真好玩)。
方法一:暴力
题目要求在O(n)的时间复杂度下完成,但暴力解法时间复杂度会上升到O(n^2),用C++就会超时,而Java却AC了(不是很懂.jpg)。
代码
1 | class Solution { |
方法二:哈希表
这个方法来自官方题解,自己想不出别的了。
思路
设最大异或值为m。[从高到低依次求m的每一个bit]
设x(k)表示x的从高到低的前k位。
如何求m?已经求出m(k-1),判断第k是否为1:设m第k位可以为1的条件是:存在x和y于nums,满足m(k)=x(k)^y(k),否则m第k位是0。
代码
1 | class Solution { |
993. 二叉树的堂兄弟节点
2021-05-17,打卡第4天,上午有乒乓球课,玩得很过瘾啦~
前言
要想判断两个节点 x
和 y
是否为堂兄弟节点,我们就需要求出这两个节点分别的「深度」以及「父节点」。
因此,我们可以从根节点开始,对树进行一次遍历,在遍历的过程中维护「深度」以及「父节点」这两个信息。当我们遍历到 x 或 y 节点时,就将信息记录下来;当这两个节点都遍历完成了以后,我们就可以退出遍历的过程,判断它们是否为堂兄弟节点了。
常见的遍历方法有两种:深度优先搜索和广度优先搜索。
方法一:深度优先搜索
思路与算法
我们只需要在深度优先搜索的递归函数中增加表示「深度」以及「父节点」的两个参数即可。
代码
1 | /** |
复杂度分析
时间复杂度:O(n),其中 n 是树中的节点个数。在最坏情况下,我们需要遍历整棵树,时间复杂度为 O(n)。
空间复杂度:O(n),即为深度优先搜索的过程中需要使用的栈空间。在最坏情况下,树呈现链状结构,递归的深度为 O(n)。
方法二:广度优先搜索
思路来自Qian
因为在BFS中,我们使用的是层序遍历,如果每次遍历一层,那么这一层的元素的深度是相同的。
因此我们在每一层,看看是否有出现 x 和 y,其中分为以下三种情况:
x
和y
都没出现 → 那只能往下一层继续找了x
和y
只出现一个 → 两个元素的深度不同,不可能为兄弟,返回false
x
和y
都出现了,好耶,但是还不够好x
和y
父节点相同 → 不是堂兄弟,是亲兄弟,不行,返回false
x
和y
父节点不同 → 满足题目条件了,好耶,返回true
众所周知,BFS需要用到队列,那么我们应该如何设计队列的数据类型?
在这里,我采用了 pair<TreeNode*, TreeNode*>
(其实pair<TreeNode*, int>
也可以),其中pair
中,第一个元素记录指向当前结点的指针,第二个元素记录指向当前结点的父结点的指针,这样就可以完美应对上面所说的三种情况了。
代码
1 | class Solution{ |
复杂度分析
时间复杂度:O(n),其中 n 是树中的节点个数。在最坏情况下,我们需要遍历整棵树,时间复杂度为 O(n)。
空间复杂度:O(n),即为广度优先搜索的过程中需要使用的队列空间。
1442. 形成两个异或相等数组的三元组数目
2021-05-18,轻松的一天,只有一节德语课。
前言
记S(n) , n=0,1,2,3
$$
S(0) = 0, S(1) = arr[0], S(2) = arr[0] ⊕ arr[1], S(n) = arr[0] ⊕ arr[1] ⊕ … ⊕ arr[n-1]
$$
为该数组的异或前缀和,由异或的性质可知:
$$
a = S(i-1) ⊕ S(j-1) = arr[i] ⊕ arr[i+1] ⊕ arr[i+2] ⊕ … ⊕ arr[j-1]
$$
$$
b = S(j-1) ⊕ S(k) = arr[j-1] ⊕ arr[j] ⊕ arr[j+1] ⊕ … ⊕ arr[k]
$$
方法一:暴力
思路
依次枚举i
,j
,k
的值。
1 | class Solution { |
复杂度分析
时间复杂度:O(n^3),其中 n 是数组 arr 的长度。
空间复杂度:O(n)
方法二:二重循环
思路
由异或的性质可知,
$$
a ⊕ b = 0时,arr[i] ⊕ … ⊕ arr[j-1] ⊕ arr[j] ⊕ … ⊕ arr[k] = 0
$$
因此在 i 之前的前缀异或和到 k 时不会变,即pre_arr[i-1] = pre_arr[k]
。
其另一点重点在于在区间 [i, k]
内 j在哪并不重要, 因为无论 j
在哪,i
到k
的异或值都等于 0,不影响结果。
1 | class Solution { |
复杂度分析
时间复杂度:O(n^2)
空间复杂度:O(n)
1738. 找出第 K 大的异或坐标值
2021-05-19,一看就会,一写就错是我了。
思路(来自官方题解)
我们用 ⊕ 表示按位异或运算。
由于「按位异或运算」与「加法运算」有着十分相似的性质,它们都满足交换律:
$$
a⊕b=b⊕a
$$
以及结合律:
$$
(a⊕b)⊕c=a⊕(b⊕c)
$$
因此我们可以使用「前缀和」这一技巧对按位异或运算的结果进行维护。由于本题中给定的矩阵 matrix
是二维的,因此我们需要使用二维前缀和。
设二维前缀和 pre(i,j)
表示矩阵 matrix
中所有满足 0≤x<i
且 0≤y<j
的元素执行按位异或运算的结果 。与一维前缀和类似,要想快速得到 pre(i,j)
,我们需要已经知道 pre(i−1,j)
,pre(i,j−1)
以及pre(i−1,j−1)
的结果,即:
$$
pre[i][j] = pre[i-1][j]⊕pre[i][j-1]⊕pre[i-1][j-1]⊕matrix[i-1][j-1];
$$
当我们将 pre(i−1,j)
和 pre(i,j−1)
进行按位异或运算后,由于对一个数 x 异或两次 y
,结果仍然为 x
本身,即:
$$
x⊕y⊕y=x
$$
因此 pre(i−1,j−1)
对应区域的按位异或结果被抵消,我们需要将其补上,并对位置 (i,j)
的元素进行按位异或运算,这样就得到了 pre(i,j)
。
在得到了所有的二维前缀和之后,我们只需要找出其中第 k 大的元素即为答案。这一步我们可以直接将 mn
个二维前缀和进行排序后返第 k
大的元素。
细节
在二维前缀和的计算过程中,如果我们正在计算首行或者首列,即 i=0
或 j=0
,此时例如 pre(i−1,j−1)
是一个超出下标范围的结果。因此我们可以使用一个 (m+1)×(n+1)
的二维矩阵,将首行和首列空出来赋予默认值 0
,并使用接下来的 m
行和 n
列存储二维前缀和,这样就不必进行下标范围的判断了。
代码
1 | class Solution { |
复杂度分析
时间复杂度:O(mnlog(mn))。计算二维前缀和的时间复杂度为 O(mn),排序的时间复杂度为 O(mnlog(mn)),因此总时间复杂度为 O(mnlog(mn))。
空间复杂度:O(mn),即为存储二维前缀和需要的空间。
692. 前K个高频单词
2021-5-20,有人相爱,有人夜里开车看海,有人520刷LeetCode (:
方法一:哈希表 + 排序
思路及算法
我们可以预处理出每一个单词出现的频率,然后依据每个单词出现的频率降序排序,最后返回前 k
个字符串即可。
具体地,我们利用哈希表记录每一个字符串出现的频率,然后将哈希表中所有字符串进行排序,排序时,如果两个字符串出现频率相同,那么我们让两字符串中字典序较小的排在前面,否则我们让出现频率较高的排在前面。最后我们只需要保留序列中的前 k
个字符串即可。
代码
1 | class Solution { |
复杂度分析
时间复杂度:
O(l×n+l×mlogm)
,其中n
表示给定字符串序列的长度,l
表示字符串的平均长度,m
表示实际字符串种类数。我们需要l×n
的时间将字符串插入到哈希表中,以及l × mlogm
的时间完成字符串比较(最坏情况下所有字符串出现频率都相同,我们需要将它们两两比较)。空间复杂度:
O(l×m)
,其中l
表示字符串的平均长度,m
表示实际字符串种类数。哈希表和生成的排序数组空间占用均为O(l×m)
。
1035. 不相交的线
2021-5-21,对于今天来说,也许这题很奇怪(不相交
思路:动态规划
定义dp[i][j]
表示数组nums1
的前i
个元素和nums2
的前j个元素所能绘制的最大连接数,如果要求dp[i][j]
,我们首先判断nums1
的第i
个元素和nums2
的第j
个元素是否相等
如果相等,说明nums1
的第i
个元素可以和nums2
的第j个元素可以连成一条线,这个时候nums1
的前i
个元素和nums2
的前j个元素所能绘制的最大连接数就是nums1
的前i-1
个元素和nums2
的前j-1
个元素所能绘制的最大连接数加1
,也就是dp[i][j]=dp[i-1][j-1]+1
;
如果不相等,我们就把nums1
去掉一个元素,计算nums1
的前i-1
个元素和nums2
的前j个元素能绘制的最大连接数,也就是dp[i-1][j]
,或者把nums2
去掉一个元素,计算nums2
的前j-1
个元素和nums1
的前i
个元素能绘制的最大连接数,也就是dp[i][j-1]
,这两种情况我们取最大的即可,所以我们可以找出递推公式:
1 | if(nums[i] == nums[j]) |
代码
1 | class Solution { |
复杂度分析
- 时间复杂度:O(mn),其中 m 和 n 分别是数组 nums1和 nums2 的长度。二维数组 dp 有 m+1 行和 n+1 列,需要对 dp 中的每个元素进行计算。
- 空间复杂度:O(mn),其中 m 和 n 分别是数组 nums1和 nums2 的长度。创建了 m+1 行 n+1 列的二维数组 dp。
810. 黑板异或游戏
2021-5-22,一会儿是假新闻,一会儿又是证实,不是很懂央视的操作,袁老一路走好!
思路
要什么思路,我可不会!
代码
你问我代码哪来的?当然是CV啦~
1 | class Solution { |
复杂度分析
时间复杂度:O(n),其中 n 是数组 nums 的长度。最坏情况下,需要遍历数组一次,计算全部元素的异或结果。
空间复杂度:O(1)。
1707. 与数组中元素的最大异或值
2021-5-23,一看困难,又是异或,那我走?
代码
CV啦~
1 | class Trie { |
1190. 反转每对括号间的子串
2021-05-26,连着CV了好几天,我是废物,我是废物,我是废物。今天终于可以重拳出击了嘿嘿~
方法一:栈
思路及算法
对于括号序列相关的题目,通用的解法是使用递归或栈。本题中我们将使用栈解决。
我们从左到右遍历该字符串,使用字符串 str 记录当前层所遍历到的小写英文字母。对于当前遍历的字符:
如果是左括号,将 str 插入到栈中,并将 str 置为空,进入下一层;
如果是右括号,则说明遍历完了当前层,需要将 str 反转,返回给上一层。具体地,将栈顶字符串弹出,然后将反转后的 str 拼接到栈顶字符串末尾,将结果赋值给 str。
如果是小写英文字母,将其加到 str 末尾。
注意到我们仅在遇到右括号时才进行字符串处理,这样可以保证我们是按照从括号内到外的顺序处理字符串。
代码
1 | class Solution { |
复杂度分析
时间复杂度:O(n^2),其中 n 为字符串的长度。栈的最大深度为 O(n),每一层处理的时间复杂度主要为反转的时间复杂度,为 O(n),因此总时间复杂度为 O(n^2)。
空间复杂度:O(n),其中 n 为字符串的长度。对于任意时刻,字符串中的任意一个字符至多只被栈中的一个位置包含一次。
461. 汉明距离
2021-05-27,一觉醒来十点半,乍看是道简单题,我直接重拳出击。
方法一:位计数
C++(以及其他语言)中内置了计算二进制表达中 1 的数量的函数。在工程中,我们应该直接使用内置函数,但这里我们暂不使用。
__builtin_popcount(x ^ y);
代码
1 | class Solution { |
复杂度分析
时间复杂度:O(logC),其中 C是元素的数据范围,在本题中 logC=log2^31=31。
空间复杂度:O(1)。
方法二:Brian Kernighan 算法
代码
跳过0直接计算1的个数。
1 | class Solution { |
复杂度分析
时间复杂度:O(logC),其中 CC 是元素的数据范围,在本题中logC=log2^31 =31。
空间复杂度:O(1)。
477. 汉明距离总和
2021-05-28,打卡第15天,每天都要坚持呀!
首先想到的是暴力,提交就发现超时了,只能用其他方法了。
方法一:按位统计
代码
1 | class Solution { |
复杂度分析:
- 时间复杂度:O(n⋅L)。其中 n 是数组 nums 的长度,L=30。
- 空间复杂度:O(1)。
231. 2 的幂
2021-5-30,美好的一天从早起开始。
思路
一个数 n 是 2 的幂,当且仅当 n 是正整数,并且 n 的二进制表示中仅包含 1 个 1。
代码一
1 | class Solution { |
代码二
1 |
|
复杂度分析:
时间复杂度:O(1)
空间复杂度:O(1)
342. 4的幂
2021-05-31,乒乓球考试终于考完啦~
方法一:二进制表示中 1 的位置
思路
先判断是不是2的倍数,再判断是不是4的倍数。
代码
1 | class Solution { |
复杂度分析
时间复杂度:O(1)
空间复杂度:O(1)
方法二:取模性质
思路
4的幂有两条性质:
- 二进制中只有一个1
- 除以3余1
代码
1 | class Solution { |
复杂度分析
时间复杂度:O(1)
空间复杂度:O(1)
523. 连续的子数组和
2021-06-02,昨天遇见一位说话很可爱(欠)的女生(聊天中发现,可爱这个词她觉得并不准确,可我觉得没问题呀,但我还是换成了她对自己的描述:欠 ○( ^皿^)っHiahiahia…),幸好要到了联系方式,否则我真的会挂念很久的。一开始也是怂了,其实只要敢迈出这一步真的不难…
代码
1 | # 前缀 |
复杂度分析
时间复杂度:O(m),其中 m 是数组 nums 的长度。需要遍历数组一次。
空间复杂度:O(min(m,k)),其中 m 是数组 nums 的长度。空间复杂度主要取决于哈希表,哈希表中存储每个余数第一次出现的下标,最多有 min(m,k) 个余数。
160. 相交链表
2021-06-04,走到尽头见不到你,于是走过你来时的路,等到相遇时才发现,你也走过我来时的路。
思路
利用集合,先遍历一遍链表A,将A中的所节点存入set,再遍历链表B,如果遇到当B中的某一节点出现在set中,说明该节点是相交节点,返回该节点即可,没找到返回null
。
代码
1 | /** |
复杂度分析
- 时间复杂度:O(m+n),其中 m 和 n 是分别是链表 headA 和 headB 的长度。两个指针同时遍历两个链表,每个指针遍历两个链表各一次。
- 空间复杂度:O(1)。
203. 移除链表元素
思路
递归yyds
代码
1 | /** |
复杂度分析
时间复杂度:O(n),其中 n 是链表的长度。递归过程中需要遍历链表一次。
空间复杂度:O(n),其中 n 是链表的长度。空间复杂度主要取决于递归调用栈,最多不会超过 n 层。
726. 原子的数量
2021-07-05,一个月没更新了,今天终于想起来了,一看题,哦困难啊,告辞告辞。
代码
1 | class Solution { |
- 本文标题:LeetCode每日一题打卡
- 本文作者:recovxy
- 创建时间:2021-05-14 19:34:43
- 本文链接:https://naiv.xyz/2021/05/14/2021-05-14-LeetCode One question per day/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!