想养成刷题的习惯,之前刷过一点,但校招之后就断了,现在想捡起来,定个小目标,从头开始刷题吧,看能坚持多久。
1. Two Sum
2019/4/16,易。
这题以前做过,记得第一次做的时候应该是用的最蠢的两重遍历方法相加求解,毫无疑问超时了,隐约记得可以使用查找目标值-某个值
是否存在该vector中来反向求解,简单写了一下,通过了。其实是一个反向思维的题,正面求解超时,则反向来求。仍然属于蛮力法的范畴,n2时间复杂度,1空间复杂度,LeetCode平台耗时136ms。
尝试降低时间复杂度,想到以空间换时间,在查找目标值-某个值
的时候,上述蛮力法使用std::find
方法,说白了也是一层遍历,这层遍历目的是为了查找差值是否存在于vector中并返回下标,自然可以想到用哈希表来代替这一层遍历。当然,事先需要遍历一次原数组构建哈希表。如此时间复杂度n,空间复杂度n。
最终代码如下,本版本耗时仅16ms。
1 | class Solution { |
2. Add Two Numbers(again)
2019/4/19,中。
平心而论是个比较简单的链表题了,但是太久不做题竟然觉得很难理清楚,这也说明了常做题保持手感很重要。
本题也没啥特别的方法,注意循环进位即可,另外链表的题往往一开始会定义两个变量,一个用于最后返回,一个用来遍历处理。
1 | class Solution { |
3. Longest Substring Without Repeating Characters(again)
2019/4/21,中。
一道经典dp题,准备找工作的时候刷过,以为很简单,但写的时候却发现细节又已经遗忘了,虽然最后还是一遍过了,但还是感慨得要经常刷题保持手感啊,忘性太大了。
1 | int lengthOfLongestSubstring(string s) { |
4.Median of Two Sorted Arrays(again)
2019/4/22,难。
想法一开始就很明确,将两个有序数组Merge成一个新的有序数组就是了,然后直接返回中间值即可。后来稍微改进了下代码,因为Merge其实只需要Merge到中间数即可,后面的数并不需要处理了,所以去除了冗余排序,感觉时间复杂度是n+m,空间复杂度也是n+m。
1 | double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { |
但后来发现题目要求的时间复杂度是log(n+m),才感觉到为什么这道题目难度级别是hard,不过我习惯从这个要求的时间复杂度就得到该题应该要使用二分查找或者是二分查找的变形,看了网上的解答,应该是查找第K小数字的变体,最后代码如下:
1 | class Solution { |
参考https://blog.csdn.net/lis_12/article/details/53128594
5.Longest Palindromic Substring(again)
2019/4/23,中。
求最长回文子串,看到题目之后我感觉这是一道dp题,但是太菜了,还是写不出来,查了解析后代码如下:
1 | class Solution { |
时间复杂度n2,空间复杂度n2。
利用中心扩展法可以将空间复杂度降低到1。
1 | class Solution { |
最后还有一种Manacher方法,能将时间复杂度降到n,同时空间复杂度也是n,没看得太明白,以后再说。http://www.cnblogs.com/bitzhuwei/p/Longest-Palindromic-Substring-Part-II.html
6.ZigZag Conversion
2019/4/24,中。
之字形排列字符串然后按行打印。
刚看到时候第一种办法就是自己定义一个二维数组排一下然后按行输出得了,蛮力法,代码如下:
1 | class Solution { |
提交后虽然通过了,但花费时间560ms,空间占有400MB,感觉效率太低了,于是尝试另一种方法。
另一种方法也很好想到,既然是按某种规律排列并输出,那自然把规律找出来就好了,多在草稿纸上排了几个用例后发现每一行的间隔都是有规律的,主要规律是2*numRows-2,代码如下:
1 | class Solution { |
提交后时间降到16ms,空间占有降低到10MB,效率提升还是很明显的。
这道题算是这几天做的比较顺利的题了,阿弥陀佛。
7.Reverse Integer
2019/4/25,易。
反转一个整数,本来以为挺简单的,但是却没有注意大数越界,又琢磨了一会儿,要注意大数问题啊。
两种差不多的代码如下:
1 | class Solution { |
两种代码耗时都是4ms,空间占有也差不多,整体效率相仿。
8. String to Integer (atoi)
2019/4/26,中。
字符串转整数,需要注意大数问题、正负号、空格,题目里已经写得很清楚了,代码如下:
1 | class Solution { |
看了下其他提交,方法都大同小异,只不过大佬们写的C++代码要简洁一些,不像我写的那么臃肿,大佬们的代码就是在原字符串的基础上往后遍历处理,而我的代码里还用erase/substr方法把原字符串改来改去,导致时间上慢了几ms,慢慢练吧~
9.Palindrome Number
2019/4/27,易。
判断一个整数是否是回文数,最简单的方法就是转化成字符然后前后循环判断,代码如下:
1 | class Solution { |
运行时间32ms,内存8.4MB。
后来看到题目里的扩展,如何不转换成string进行判断?
想到回文数字其实就是前后读相同,那么将该数字对半分后应该能分成两个相同数字,比如1221,就分成12和12(后一个12从右往左读),而131就可以同样分成13和13(3用两次),根据这种想法写出以下代码:
1 | class Solution { |
同样是32ms,内存8.2MB。
后来看了下大佬们的提交,大致思路和上述第二种方法相似,但大佬的代码更加精炼,把我代码中很多直白的部分都进一步浓缩了,如下:
1 | class Solution { |
10.Regular Expression Matching(again)
2019/4/29,难。
一开始以为这道题和《剑指offer》上的某道题一样,遂用那道题的解法来进行求解,后发现有点问题,经过修补后写下如下代码:
1 | class Solution { |
最终通过了,但是代码很丑,效率很低,整体用了五百多毫秒,内存占用17.5MB左右,效率太差了。
然后发现可以用DP来求解,研究后代码如下:
1 | class Solution { |
利用DP解法耗时降到8ms,内存占用降低到9.1MB,主要思路参考https://zhuanlan.zhihu.com/p/40294596,但个人认为该链接里dp最后一种情况推导公式有误,应该是我代码中写的dp[i][j]=dp[i][j-2]||dp[i-1][j]||dp[i-1][j-2]
,且该链接给出的最后代码感觉有点问题,应该不能直接用,只是个伪代码吧,不过主要思路是阐述得很清楚了。
11. Container With Most Water
2019/4/30,中。
一开始看到这道题的时候想的是用最直接的暴力法——挨个遍历,但是可以预料到耗时肯定会超出限制,所以想着改进该方法。想到在暴力遍历中其实有很多种情况是重复的,所以想到用DP数组先存前一种情况的值,再根据已知情况的值来进行当前情况的求解,能够省去一重循环,遂写出下列代码:
1 | class Solution { |
提交,通过测试,但效率极低,耗时1300ms左右,占用内存10MB左右,仍需改进,该方法的时间复杂度是n2,所以考虑是否有时间复杂度为n的方法,看到一年前做这道题时用的是前后双指针法,代码如下:
1 | class Solution { |
耗时20ms,效率提升明显,主要重点在height[i]<height[j]?(++i):(--j)
,即每次收缩的时候都收缩边界较小的那一侧,因为这样就可以省去很多冗余情况的判断。
12. Integer to Roman
2019/5/2,中。
整数转换成罗马数字,整数范围1~3999。
按部就班就行,首先写出的代码如下:
1 | class Solution { |
耗时24ms,空间占用12.2MB。
提交后看了下其他人的代码,基本上思想没什么变化,只不过初始化的时候如果初始化的更具体一点,转换耗时就会更快,代码如下:
1 | class Solution { |
耗时16ms,占用空间8.4MB。
13. Roman to Integer
2019/5/3,易。
罗马数字转整数,很笨的办法就行,if
语句看起来有点乱,但能通过,耗时16ms,8.3MB内存,可以转成switch
语句,我这就没有转了。
1 | class Solution { |
14. Longest Common Prefix
2019/5/5,易。
求一组字符串的最长公共前缀,直接暴力解就行了,代码如下:
1 | class Solution { |
15. 3Sum(again)
2019/5/8,中。
求数组中三个数和为0的子集。
一开始自己用的回溯法,虽然结果能对,但是超时了,代码还是暂时列在这里,这代码里去掉了一些无用的迭代,然而还是没啥用,还是超时。
1 | //超时 |
后面看了其他提交者的代码,采用的是先排序再利用前后指针的方法:
1 | class Solution { |
16.3Sum Closest
2019/5/9,中。
和15很类似,这题求的是和给定值target最近的三个数的和,方法类似,也是用两个指针从前后向中间逼近,代码如下:
1 | class Solution { |
17. Letter Combinations of a Phone Number
2019/5/10,中。
一道简单的回溯题,没有啥特别的地方,代码如下:
1 | class Solution { |
18. 4Sum
2019/5/11,中。
和前面的2Sum以及3Sum是一个系列的,使用和3Sum一样的思想即可,AC,耗时8ms左右。
1 | class Solution { |
19. Remove Nth Node From End of List
2019/5/12,中。
移除链表中倒数第N个元素,做过,代码如下:
1 | /** |
20. Valid Parentheses
2019/5/13,易。
判断括号是否有效,用一个栈即可,没什么难度,自己是用ifelse写的,另外找了一个看起来清爽一些但意思一样的代码放在这里:
1 | class Solution { |
21. Merge Two Sorted Lists
2019/5/15,易。
合并两个有序数组,简单题,最常用的方法就是循环依次比较,这里就不放这种方法的代码了,放一个递归的代码,《剑指offer》里也是这种递归代码。
1 | class Solution { |
22. Generate Parentheses(again)
2019/5/16,中。
生成括号,用递归解法。
总结出来三点,就是选择、限制、结束条件是这种递归和回溯的主要部分,本题代码如下:
1 | class Solution { |
23. Merge k Sorted Lists
2019/5/17,难。
第21题的升级版,最容易想到的方法就是循环调用第21题的代码,代码如下:
1 | class Solution { |
最后提交发现该方法可以AC,但耗时240ms,占内存11MB左右,效率不是特别好。
为提高效率,采用分治法的思想,两两合并再合并,代码如下:
1 | class Solution { |
该方法耗时24ms,效率提升很多。
24. Swap Nodes in Pairs
2019/5/21,中。
链表题,主要就是搞清楚指针的变换和空指针的情况,循环遍历下去即可,代码如下:
1 | class Solution { |
25. Reverse Nodes in k-Group
2019/5/26,难。
最近等毕业论文审核通知,心烦意乱,没有什么心情,所以做题耽搁了些。
这是题24的升级版,题24是两两反转,而本题是按数字k来反转,变成了更一般的情况,我的想法是按段分割整个链表,对每一段链表分别进行反转,注意好节点指针,还是比较容易漏的,测试了好久,代码如下:
1 | class Solution { |
最后耗时20ms,内存9.7MB左右。
26. Remove Duplicates from Sorted Array
2019/5/27,易。
去除排序数组中的重复数组。
本题本来以为是需要将重复数字删掉,但题目的意思其实是只需要让单独的数字出现在前面,超出单独数字长度后的数字可以不用删除,因为不用删除所有可以省很多时间,代码如下:
1 | class Solution { |
不过一开始我以为是需要删除元素,所以用了迭代器来删除数组元素,最后也可以跑通,只是耗时会比较多,代码如下:
1 | class Solution { |
27. Remove Element
2019/5/31,易。
删除数组中指定元素,很简单的一道题,可以用vector的erase方法来删除,但联想到上一题,觉得题目要求应该只需要将非指定值的元素放在数组前面部分即可,所以最后写出的代码也没删除,只是替换了一下。
1 | class Solution { |
正常AC了,后来看别人的提交,其实连替换都不用,若有指定数字直接覆盖就行了,不过大同小异,不去深究。
28. Implement strStr()
2019/6/1,易。
判断是否是子串,由于这道题难度是简单,所以用最简单的遍历查找即可,至于KMP算法可以参见另一篇文章《剑指Offer+常用手写算法(C++)》。
1 | //最简单的遍历比较法 |
29. Divide Two Integers(again)
2019/6/3,中。
不用乘法、除法以及模运算完成两个整数的除法,主要想法是使用移位运算,每次双倍,再做操作和累加,代码如下:
1 | class Solution { |
30. Substring with Concatenation of All Words(again)
2019/6/5,难。
有点难度的一道题,不过基本方法很好想,就依次遍历n*len的子串,针对每个子串进行验证是否成立,代码如下:
1 | class Solution { |
能够通过,只是效率比较低,看网上还有一种利用滑动窗口的方法,能够提高效率,可以百度看看。
31. Next Permutation
2019/6/10,中。
求字符数组按照字典序的下一个排列。
研究了几个排列后发现了一些规律,写出如下代码:
1 | class Solution { |
提交通过了。
当然,C++本身还有一个next_permutation
函数,直接用的话本题也能通过:
1 | class Solution { |
32. Longest Valid Parentheses(again)
2019/6/13,难。
求最长有效括号子串,一开始就想到应该用动态规划或者栈的方法来做,但自己编程水平还是太菜,写了半天没写出来,看了网上的解答后觉得自己很蠢,虽然难度是难,但本题其实感觉并不难,给出代码吧,头疼。
不过这题有个技巧,栈的话会先在栈里放一个-1,动态规划的话则会在原字符串前加一个)
,都是为了使处理更加方便,可能这就是自己没想到的地方吧,值得重视。
1 | //栈 |
33. Search in Rotated Sorted Array(again)
2019/6/21,中。
这两天奔波找房子参加毕业典礼啥的就没刷题,这道题之前做过类似的,主要使用二分法,并且在排除的过程中注意根据有序的部分数组来进行判断,以便决定去除左右哪一部分无效数据。
代码如下:
1 | class Solution { |
34. Find First and Last Position of Element in Sorted Array
2019/6/26,中。
《剑指Offer》上有类似的题,又复习了一遍,提交的时候发现之前在LeetCode上做过这道题,用的是比较笨的办法,但在LeetCode上的执行效率却比递归方法高,有点意思,给出下面两种代码,核心思想仍然是二分。
1 | //普通方法 |
35. Search Insert Position
2019/7/6,易。
本题相对简单,写的时候也一次通过了,代码如下:
1 | class Solution { |
但是后来看别人提交的代码的时候发现自己又把问题复杂化了,根本就不是二分查找的变种,而是最基本的二分法!要说变化也就是最后返回的值变成了left!还是对问题的理解程度太弱了,所谓寻找插入的下标,说白了原来就是返回left就行了,试验几次就会看出这个规律了,代码如下:
1 | class Solution { |
36. Valid Sudoku
2019/7/7,中。
这道题我还记得之前第一次做的时候以为要把整个数独都填完呢,后来发现自己还是把问题复杂化了(似乎我的思维有这个毛病,得改),本题的意思其实只是判断input里每一行每一列每个九宫格有没有重复数字而已,并不用填充整个数独。
所以最简单的办法就是遍历判断好了,代码如下:
1 | class Solution { |
查看别人的代码后发现有很精简的代码,但逻辑内核不变,这里也就不贴了。