Day1
1091. Shortest Path in Binary Matrix
Given an n x n binary matrix grid, return the length of the shortest clear path in the matrix. If there is no clear path, return -1.
A clear path in a binary matrix is a path from the top-left cell (i.e., (0, 0)) to the bottom-right cell (i.e., (n - 1, n - 1)) such that:
- All the visited cells of the path are
0. - All the adjacent cells of the path are 8-directionally connected (i.e., they are different and they share an edge or a corner).
The length of a clear path is the number of visited cells of this path.
这题是到BFS的搜索题,处理好边界及入栈条件。
Day 2
今天开始在力扣国内版做,开了会员,现在刷的是华为春招22题。
最小跳跃次数
给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。
每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:
0 <= j <= nums[i] i + j < n 返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]。
来源:力扣(LeetCode) 链接:leetcode.cn/problems/ju…
这题算是典型的贪心算法的应用,我还算有印象,但其实本来也很直观,就是每一步跳出去后看下一步所能到达的最远距离,距离越远当然步数就越少,关键就是怎么优雅的写出来,利用两个变量存储当前所能达到的最远距离和下一步所能达到的最远距离,这样在到达当前最远距离后当然步数就要加一了。
对称二叉树
给你一个二叉树的根节点 root , 检查它是否轴对称。
这题很简单,就是同时遍历两棵树,按照递归的顺序,不过遍历的方向要反一下,就是左右子节点的对比。
字符串映射
给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
这题就是典型的哈希表判断,就是比较两个元素有没有同时出现,其实也可以用set或者map了。
给定两个字符串 s 和 t ,判断它们是否是同构的。
如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。
每个出现的字符都应当映射到另一个字符,同时不改变字符的顺序。不同字符不能映射到同一个字符上,相同字符只能映射到同一个字符上,字符可以映射到自己本身。
来源:力扣(LeetCode) 链接:leetcode.cn/problems/is…
一道小小的简单题,竟然卡了半天,首先是没理解题意,以为是一一对应,但其实只是s到t的映射,不必满足逆映射。其次是所有的ASCII码而不仅仅是字母,而且分情况讨论也是问题,逻辑不清晰,写的磕磕绊绊,一道简单题耗费这么久,确实需要警醒。
奇数在偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的前半部分,所有偶数在数组的后半部分
双指针法的典型应用
二叉搜索树中两个节点之和
给定一个二叉搜索树的 根节点 root 和一个整数 k , 请判断该二叉搜索树中是否存在两个节点它们的值之和等于 k 。假设二叉搜索树中节点的值均唯一。
这题是利用二叉搜索树中序遍历的数组为递增数组,先遍历得到数组,然后求解,利用递增顺序进行剪枝,如果当前值大于0且大于目标值则终止。如果当前和大于0则跳过。
再看了参考后, 发现有很多可以改进的地方,首先如果中序遍历后,其实可以用双指针的方法进行求解,时间复杂度降低,说明还是没有深刻理解双指针的含义啊,或者说有序数组的性质理解不足。
除此之外,还有用hash表进行求解的方法,先遍历一遍,在遍历过程中将数据用hash表保存下来,如果k-val的值已经存在说明有解,直接返回true,如果遍历到最后都没有说明不存在返回false。
一个简单题都有很多细节,所以万万不可大意,理清思路,认真思考,合理运用各种数据结构简化难度。
螺旋矩阵
这题很重要!!!
给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
这题写的把人恶心死了,一直没处理好边界问题,又想代码简洁又想效率高,结果就是啥都不行,操。模拟的思路很清楚,但就是一直没处理好,下次要再做一遍。
剑指 Offer II 074. 合并区间
这题很重要!!!
有趣,真是有趣,我自己写的sort的比较函数进行排序给我报错,不使用这个排序就可以通过,有意思。答题思路就是按开头排序,然后不断比较当前间隔首位值是否在范围内,如果在的话就并入并且更新尾部,否则就压入开始新一轮比较。
让我不解的就是为什么自己写的比较函数会给我报错。
1669. 合并两个链表
这题就是简单的模拟,但是需要确定边界,开头位置是a-1,结尾位置是b+1;
剑指 Offer II 024. 反转链表
这题看似是个简单题,但是依旧耗费了我不少时间,主要是因为边界没处理好,只关注结尾而不关注开头,我平时做事也是这样,顾头不顾尾,思考缺乏全面性,这很要命的,而且特殊条件也是,应该要注意了。做题前没有想清楚,都是边做边补丁,属实不合适。
705. 设计哈希集合
不使用任何内建的哈希表库设计一个哈希集合(HashSet)。
实现 MyHashSet 类:
void add(key) 向哈希集合中插入值 key 。 bool contains(key) 返回哈希集合中是否存在这个值 key 。 void remove(key) 将给定值 key 从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。
这题很重要!!! 之前一直用hash表结果忘记hash表的原理,其实是用hash函数进行映射,存储的数据结构可以是list,映射冲突时就额外压入。用hash函数求下标可以快速获得位置。
首先是C++的list。 list和vector得最主要的区别在于vector使用连续内存存储的,他支持[]运算符,而list是以链表形式实现的,不支持[] 。 Vector对于随机访问的速度很快,但是对于插入尤其是在头部插入元素速度很慢,在尾部插入速度很快。List对于随机访问速度慢得多,因为可能要遍历整个链表才能做到,但是对于插入就快的多了
剑指 Offer II 062. 实现前缀树
这题很重要!!! 老实说,这题完全不了解在做什么,也没有理解,就是单纯的抄了下代码,需要再看一下。
213. 打家劫舍 II
328. 奇偶链表
146. LRU 缓存
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。 void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。 函数 get 和 put 必须以 O(1) 的平均时间复杂度运行
3. 无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
198. 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
这题就是动态规划的简单应用,没什么复杂的。
20. 有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都有一个对应的相同类型的左括号。
这题是栈的一个应用,在入栈时可以压入闭括号而非开括号这样就可以直接比较。
912. 排序数组
给你一个整数数组 nums,请你将该数组升序排列。
冒泡排序:不断比较当前值和后一个值得大小使大的排在后面,一轮遍历完最值就到了末尾,需要一直
选择排序:每一轮遍历选择一个最值然后放在末尾,比较的多,交换的少。
插入排序:像切牌一样,在遍历到的范围内要保证有序,是当前遍历的值插在有序的位置上。
快速排序:选择一个点排序后左边全是小于的,右边全是大于的,然后递归求解,复杂度O(nlogn)
计数排序:先求出当前数组得最大最小值,然后生成max-min+1大小的数组用来统计每个值出现的次数。 接着遍历该数组,如果当前出现次数不为0,那么就把下标放进result里并使index+1,次数减一直到为0,这样一直遍历最后就排序了。这题的时间复杂度为,空间复杂度为O(n+k)取决于数组的个数以及数组内数字的取值范围。
通过这一题算是把经典的排序算法简单过了一遍,很有收获。
5. 最长回文子串
给你一个字符串 s,找到 s 中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
这题也是蛮有趣的,我一开始写的时候确实因为之前做过还有印象所以瞬间想到用动态规划的方法,但老实说没有一开始就考虑好遍历顺序的问题,提交发现答案不对后才开始查漏补缺,不得不说其实是不对的。虽然一直强调谋而后动很可能就一直不动了,但人总是要提升自己,如果已经有思路了,那么最好还是完善一下。
另外要提一下参考里面的另一种解法,就是扩散法,这个方法很明显没有说用到什么技巧就是直观的思考,非要说的话可以认为是贪心算法,对每一个可能的回文子串的核,可能是一个也可能是两个元素,一直向两边扩散,只要不满足条件,那么后面的也一定不会是了。还是很有启发意义的。
6. N 字形变换
将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下
这题还是比较有启发,我虽然利用模拟的方法做出来了,但是边界的判断或者说方向的判断也是纯靠模拟而实际上这是可以通过一点小小的数学技巧给解决的而不需要额外变量,所以说想考的越多,利用一点小小的数学技巧就很有可能简化代码,具体判断就是如果 则向上,否则向下。
7. 整数反转
给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。
笑死,参考里面几行搞定的东西,我写了一大堆,还是功力不足啊。人家就是每次加之前判断一下是不是超过条件了(res<INT_MIN/10||res>INT_MAX/10),如果超过了说明不能再加了,再加就超了,反之就可以继续,也不用转化字符串就不断的取余就可以了。多学习啊!
8. 字符串转换整数 (atoi)
请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。
函数 myAtoi(string s) 的算法如下:
- 读入字符串并丢弃无用的前导空格
- 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
- 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
- 将前面步骤读入的这些数字转换为整数(即,"123" -> 123, "0032" -> 32)。如果没有读入数字,则整数为
0。必要时更改符号(从步骤 2 开始)。 - 如果整数数超过 32 位有符号整数范围
[−231, 231 − 1],需要截断这个整数,使其保持在这个范围内。具体来说,小于−231的整数应该被固定为−231,大于231 − 1的整数应该被固定为231 − 1。 - 返回整数作为最终结果。
注意:
- 本题中的空白字符只包括空格字符
' '。 - 除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。
这题很重要!!! 这题其实是一道有限状态机的引用,但是用简单的判断条件考虑边界也可以解决,不过说起来,我还没有系统的做过有限状态机的代码,可以适当的刷一刷。主要就是边界的判断,但这题其实并没有说不能用long型,我自己给自己设限了,说起来还是不够灵活思考。
9. 回文数
给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
- 例如,
121是回文,而123不是。
这题看似简单,如果用转化字符串的方法,只要是用双指针就可以了。但是看了参考之后,发现解法还是巧妙,反转一半的数字,判断后一半与前一半是否相等即可。至于判断什么时候到一半,就是反转的数大于等于剩余的数时。针对个数的奇偶性,比较的时候比较reverse==x||reverse/10==x即可。不得不说真是巧妙至极。值得好好学习。这种连贯的思路,一步一步的推导值得借鉴。
11. 盛最多水的容器
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明: 你不能倾斜容器。
这题老实说也不是我做出来的,直接看参考答案。我一开始考虑什么动态规划,单调栈,没有任何作用,参考直接使用双指针法求解。从两端开始,不断缩小,每次改变数值较小的一端,因为数值较大的一端无论怎么移动,值都不会再大于当前值了,只有数值较小的一端变大才有可能(因为距离缩短,必须要边长变大)。
12. 整数转罗马数字
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I可以放在V(5) 和X(10) 的左边,来表示 4 和 9。X可以放在L(50) 和C(100) 的左边,来表示 40 和 90。C可以放在D(500) 和M(1000) 的左边,来表示 400 和 900。
给你一个整数,将其转为罗马数字。
这题就是两个数组,然后贪心算法从高到底罗列即可。
73. 矩阵置零
给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法 。
这题很有趣,原地算法即不适用额外的空间。一开始我是想使用额外的数组标记列和行,后来看了参考之后觉得很有趣,因为要把当前行与当前列全部都置0,那么第一行与第一列肯定也要这样,那么直接使用第一行与第一列进行标记不是就可以了吗?只要先把第一行第一列遍历看改行是否需要置0就可以了。看似麻烦但是确实只需要使用常量空间就可以了。当然,是使用空间换时间,不过还是值得学习。
22. 括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
这题算是回溯算法的一个应用,也是我第一时间能够想到的,其实这个回溯我并没有想的很清楚,但是误打误撞还是能解出来的。
看了参考后,发现还有一个比较妙的递归方法。一定是这个形式的,a,b就是符合条件的子序列,递归的函数就是
auto lefts = generate(i);
auto rights = generate(n - i - 1);
for (const string& left : *lefts)
for (const string& right : *rights)
*result.push_back("(" + left + ")" + right);
将a和b求出来然后递归。
27. 移除元素
给你一个数组 nums **和一个值 val,你需要 原地 移除所有数值等于 val **的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
这题比较简单,但我竟然还是思考了很久而且也没有写出简单的解法。其实就是用一个变量标记不等于的下标,如果不等于就赋值然后下标加一,如果等于就直接跳过,这样遍历完就可以了。
或者我用双指针法,后面标记不满足的,前面标记满足的,从左右两端向中间遍历。
描述
王强决定把年终奖用于购物,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
| 主件 | 附件 |
|---|---|
| 电脑 | 打印机,扫描仪 |
| 书柜 | 图书 |
| 书桌 | 台灯,文具 |
| 工作椅 | 无 |
如果要买归类为附件的物品,必须先买该附件所属的主件,且每件物品只能购买一次。
每个主件可以有 0 个、 1 个或 2 个附件。附件不再有从属于自己的附件。
王强查到了每件物品的价格(都是 10 元的整数倍),而他只有 N 元的预算。除此之外,他给每件物品规定了一个重要度,用整数 1 ~ 5 表示。他希望在花费不超过 N 元的前提下,使自己的满意度达到最大。
满意度是指所购买的每件物品的价格与重要度的乘积的总和
请你帮助王强计算可获得的最大的满意度。
输入描述:
输入的第 1 行,为两个正整数N,m,用一个空格隔开:
(其中 N ( N<32000 )表示总钱数, m (m <60 )为可购买的物品的个数。)
从第 2 行到第 m+1 行,第 j 行给出了编号为 j-1 的物品的基本数据,每行有 3 个非负整数 v p q
(其中 v 表示该物品的价格( v<10000 ), p 表示该物品的重要度( 1 ~ 5 ), q 表示该物品是主件还是附件。如果 q=0 ,表示该物品为主件,如果 q>0 ,表示该物品为附件, q 是所属主件的编号)
这题其实第一眼看见的时候就知道是动态规划了,但是一个小变种我就不知道怎么做了,当然这个类型也算是说之前没见过。
主附件有从属关系,只有有主件才能选附件,所以一个做法就是把主件和附件捆绑在一起。所幸最多只有两个附件,所以就需要三个量进行标记。分别是不带附件,一个附件,或另一个附件。这样在动态规划遍历时相当于只遍主件。把四种可能:不选附件,附件1,附件2,两个附件可能组成的结果全部比较然后选择最大的值,遍历完后,就可以获得结果了。
还是缺乏思考,不会拓展,被自己的思路所束缚,一旦稍微变换一下形式就不知所措了。
描述
原理:ip地址的每段可以看成是一个0-255的整数,把每段拆分成一个二进制形式组合起来,然后把这个二进制数转变成
一个长整数。
举例:一个ip地址为10.0.3.193
每段数字 相对应的二进制数
10 00001010
0 00000000
3 00000011
193 11000001
组合起来即为:00001010 00000000 00000011 11000001,转换为10进制数就是:167773121,即该IP地址转换后的数字就是它了。
数据范围:保证输入的是合法的 IP 序列
这题还是相当有趣的,我吭哧半天自己写转换函数,其实并不需要那么复杂。如果读入的时候用scanf,可以按照分割要求读,如
scanf("%lld.%lld.%lld.%lld",&a,&b,&c,&d)!=EOF
这样就按照"."分开了。
然后就是十进制与二进制的转换,可以用<<和>>表示
num>>24就是10进制转换为二进制表示后(32)位所有数字右移24位,那剩下的就是开头的8位了。 同理 <<就是右移了。 道阻且长啊
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构
这题其实还是比较有趣的,序列化的实现并不难,但是如何序列化以便于后续反序列化的思考很重要,怎么表示左右子树,怎么将不同的元素区别开来,这些都需要考虑到。而且我在做这题时与标准答案一个不同的创新点在使用了一个index的全局变量来记录当前遍历的元素,这就不需要额外的删除操作,大大节省了时间。为什么可以用一个全局的index量来记录也是因为我使用的是先序遍历也是一种深度优先,这种遍历的特点就是前面的遍历完后,当前的index自动对应子树所在的位置。
另一种解法是使用巴克斯范式
T -> (T) num (T) | X
表示树的结构要么是X为空要么是左子树值右子树的形式,依照结构递归的解析子树。
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
这题我的思路还是差不多的,就是每次找当前头结点中的最小值然后不断更新,但是实现起来比较愚蠢,竟然是遍历加递归的形式。但是这题实际上是个天然的适合优先队列的题,即队列的顶部始终保持最小值,然后不断更新。
值得注意的点是,priority_queue<T,container,comp>包含三个部分,分别是保存的元素类型,存储用的容器,以及排序依据,这里值得注意的是comp,是一个类,不过例子中可以使用decltype进行推导。
auto comp = [](ListNode* a,ListNode* b){return a->val>b->val;};
priority_queue<ListNode*,vector<ListNode*>,decltype(comp)> q;