3

leetcode刷题记录

 2 years ago
source link: https://moyangsensei.github.io/2021/11/20/leetcode%E5%88%B7%E9%A2%98%E8%AE%B0%E5%BD%95/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

leetcode刷题记录

11-20-2021 14:11:06 leetcode
Word count: 4.5k | Reading time: 16min

原创文章,转载、引用请注明出处!


  • hard:4、458

  • medium:2、3、5、6、11、12、15、17、56、148、165、189、215

  • easy:*1、7、9、20、21、26、27、53、66、70、88、*118/119、141、202、206、*231、283、*461、*2073

  • 重难点:2、3、^4、5、15、20、21、53、70、141、148、206、215


2. 两数相加 [LinkList]

按顺序加,主要就是保持一个进位flag,以及如果最后有进位,需要再新设一个node,值为1。

3. 无重复字符的最长子串 [array]

设两个指针head和tail,分别从0和1开始,tail每次向下走一个,head走到在[head(包),tail(不包)]区间内跟当前tail重复的位置,显然这个重复位置如果有的话那么就只有一个,如果无重复,那么head不动即可。每次tail走动一下后,将其区间长度的最大值保留即可。tail从头到尾走一遍就可以得到答案。

题解有提示到查看是否重复和以用到一些容器,比如python中的set和cpp中的map。上面解法时间太慢也是因为判断是否重复我没有用这类容器,但实际上也省下了空间,时间换空间属于是。

4. 寻找两个正序数组的中位数 [array/num/技巧题]

把两个数组放到一个里面,然后返回中位数即可。放的时候都从头循环,谁小就放谁。其中一个放空之后,把另一个剩下的所有跟上前面,就能保证新的依旧是有序的。

这题作为hard是因为要求的时间为O(log(m+n)),这样的话就需要二分查找来解决,说实话给的解析确实没看懂。

5. 最长回文子串 [array/DP/技巧题]

首先是好理解的方法,就是将这个倒置串,寻找本体和其倒置的 最长公共 回文 子串。该做法的时间和空间都是0(n*n)。对于这两个操作分两部分走:

  • 最长公共子串就是设置一个len*len的数组arr,初始全部置0,从头循环,如果两个指针i、j指的字符相同,那么其状态就是他们的前面的最大子串长度+1,也就是arr[i][j]=arr[i-1][j-1]+1。这里需要注意的是当i和j是0的时候要单独判断。设置2个容器记录这个长度和末尾位置,每当当前的长度(arr[i][j])大于记录的长度,那么就以当前的长度和i替换这两个记录容器。

  • 在上述进行两个容器的记录之前需要判断这一段是否是回文串,具体做法有两种,一种就是把这一段整体取下来整体判断,另一种就是判断最末尾一位的下标。

在这里,对最长公共子串的求法是DP,而对这个题目本身有DP的做法,这两者的状态转移是完全不一样的,要区分好。

其次,还是要了解本题的扩展中心法Manacher算法

6. Z字形变换 [array/num/技巧题]

没啥好说的,几行就设置几个str,按顺序往里存,最后从上到下排成一行。也可以找规律,每一行都是从某一位开始,间隔多少个数取(这个数肯定是不一样的)。

7. 整数反转 [array/num]

反转有符号整数,超过[−231, 231 − 1]就返回0。

常规方法设置一个n,每次对x取10的余数,再加上10倍的n(相当于前一位的进位)。

使用python的切片可以反转str,将int保留符号,转成str,然后再添上符号。

一定要注意反转后的数字有可能超过范围。

9. 回文数 [array]

判断一个数从前向后读和从后向前读是不是一样的,是就称为回文数。

常规思路就是和7. 整数反转,一样的操作,把这个整数反转然后看他们是否相等即可。偷鸡思路还是使用python切片反转str。

注意,负数一定不会是回文数,因为多个符号。

11. Container With Most Water [array]

直接两层循环会TLE,评论做法是从头和尾开始相对着走,实际上就是考虑了坐标轴长度,保留最大的h然后逐渐缩小坐标轴长度,时间O(1)。

12. 整数转罗马数字 [num/技巧题]

写好个位数,然后替换即可,替换规则都是相通的。

题解里有提到用贪心,就是写好1000、900、500、400、100、…,以9和4的开头是需要用减法构造的,单独写出来,剩下的所有内容都可以用加法构造,所以就每次用num减去最大的,说是贪心,但这是贪心的意义么,后面写到这类题再说。

15. 三数之和 [num/技巧题]

面试重点。

首先,思路方面,三重循环肯定是TLE,那么就力争变成二重循环。最外面的一层循环肯定不能省略,那么就要对里面的两层动心思。做法就是先排序,然后对内层的[i+1,n]区间的循环,设置双指针,j从i+1向后走,k从n向前走。能这么的核心就是因为排过序,前后操作是对应的。判断的时候,如果是0,那么记录结果,如果不是,就看是比0大还是比0小,结果比0大证明后面加的两个数过大了,那么k就往小变;同理,结果小了证明所加的数不够大,那么j就往大变。

其次,操作方面,比较重要的就是去重,对于ijk共同来说,就是要跳过相同的数,因为相同的数判断过会出现相同的结果,这里同时做到了去重和优化循环;对i来说,单独有一个优化,就是排了序之后,大于0的i,j和k都会大于,三个大于0的数相加必大于0。

最后整体做下来,排序用的是O(logN),循环遍历用的是O(N*N)

以及并不能为了去重而对nums上来就做去重,写题的时候就一直疑惑,写总结的时候突然发现确实不能这么写,不解释,懂得都懂。

17. 电话号码的字母组合 [DP]

并不算难,实际上是最简单的那种DP:把之前的内容,每一个都重新加上新加的这个就行。就是要记得对每个老内容,添加3或4个新内容之后,把老内容删了。

上述写法模拟了一个队列的操作,每次都操作队头的那个就行,count则控制每新添加一个数之前一共有多少老内容。

20. 有效的括号 [技巧题]

模拟栈(先进先出)的存储方式匹配括号序列。

21. 合并两个有序链表 [LinkList]

新设一个头和操作这个头的操作指针,两个链表从头遍历有两个操作指针,当前两个指针哪个指的值小,就把这个node接上,该node后面断掉,指针向前走一个。最后一定记得把两个表中剩下的部分接上。

26. Remove Duplicates from Sorted Array [array]

给定按非降序排序的整数数组nums,请删除重复项,以便每个唯一元素只显示一次。元素的相对顺序应保持不变。

这题返回值只有k,但检测结果还是操作num变成前k个不重复的值。

27. Remove Element [array]

将所有非val的数挪到list最前面,不论顺序,返回非val的个数。

283. Move Zeroes同一个原理,就是把0换成是val然后做一个反向问题,代码都是一个逻辑,也是需要注意list长度是0或者list内没有要操作的数。

细细看了下,与26. Remove Duplicates from Sorted Array应该也是同理,在26的去重中,无非就是把k=0,k=val换成了动态的k,就是当前的重复的那个值。当然,能这么想只存在于26是有序的,无序的话就不是这个道理了。

时间O(1),循环指针每次变动到新的不重复的值上,然后交换到当前k的位置,两件事情分开做就行。

53. 最大子数组和 [num/DP]

求在一个整数数组内,和最大的子序列的这个和。

屏蔽掉的是暴力求解,肯定TLE(亏我还想了半天,用矩阵存储了一部分结果,把每个子列求和的N省略了,以为从O(N^3)变成O(N^2),TLE之后再一寻思,nmd用切片和sum函数求和好像也是线性,人家本来就是O(N^2),我优化了个寂寞,我是个逆天)。

上面代码确实没体现出DP,好像就是用了常规的规律,但确实是DP的思想。

以及,这题是easy就多少有点说不过去了。

56. Merge Intervals [array]

融合所给区间,区间相交取交集。

自己想的方法:先求从第几个位置到第几个位置需要融合,然后融合了放进新的result,做得很复杂,要考虑开头结尾长度啥的,搞了半天还写的不对。

看评论区,实际上这题的思路非常简单,就是从头遍历,设置一个指针i,从0开始,看i和i+1,左区间是i[0],右区间是max(i[1],i+1[1])。如果有融合,那么指针不动,没有做融合操作,指针再向前走

66. Plus One [array]

设置一个进位flag。时间O(1)循环。

注意最后一位如果进位的话,要在第一个位置多加一个1.

70. Climbing Stairs [DP/array]

dp问题,本质上是fib,重点。

最高赞解释:

递归fib肯定会TLE,两种做法,一种是设置n个空间,一种是就用两个空间做连续数组存放。

这题也能看作是DFS,左子树是走一步,右子树是走两步,结果就是这棵树的子节点数量。

88. Merge Sorted Array [array]

两个升序数组融合,要求在nums1上修改。

这题有几个坑,首先如果用python的话,直接使用nums1=这种赋值方法,会改变内存空间,导致最后的结果系统不认;其次,需要考虑m和n是0的情况(图2),在考虑这个情况的时候同样要考虑前者。

python可以有两个做法,偷懒就是用切片把两个list合起来,然后sort()(图1);其次就是常规做法,在时间O(m+n)下,倒着遍历两个list,谁比较大就放到nums1的最后,收尾工作就是把最小的那些挨着放到nums1的最前面即可(图3)。

这题逆大天,一定一定要注意python的赋值,这个以前从来没考虑过。

141. 环形链表 [LinkList]

检查是不是环形链表。

必须要遵从链表思想的话是做法是快慢指针:两个指针,一个每次前进1步,一个每次前进2步,如果有环的话它们必定相遇。此时内存用的是O(1)。

常规做法就是每过一个内存地址,就存储,如果得到一个重复的,证明有环,指针遇到末尾了证明无环。此时用的内存是O(N)。检查重复自然需要用到一些数据结构比如hash和set等,实际上还是第一个方法简单。

148. Sort List [Sort/LinkList]

链表排序。

三种方法:

第一种是只对每个node的value进行冒泡排序,不动next,这样做显然会TLE。

第二种是偷鸡做法,就是把所有value取到一个list里,直接对list进行排序,这时的排序就是线性表的排序而不是链表的排序,最后把排好序的内容从链表头开始依次填进去,这样做面试估计不给过。

第三种是正常做法,就是归并排序

对于链表而言最好的排序方法就是归并。要牢牢记住第三种方法的流程:共三个函数主函数返回head的归并排序调用;归并排序函数就是将当前表头一分为二,并且递归的归并排左和右,最后返回的左右两部分的merge函数调用;merge函数就是设置一个新的暂时链表头和两个指针,把两个子链表,用操作指针从头遍历融合,返回的是暂时链表表头的指针next。

三种O(logn)的排序算法:快排、堆排序、归并排序。

165. 比较版本号

题目比较拗口,但是不难,中等就这?

主要用到python的splitint强制类型转换时会忽略前导0

189. Rotate Array [array]

根据题意,就是将list中的后k个挪到前面来,就是尾出头进k次,每次一个人,之后的队列顺序。

使用切片做即可。注意循环次数超过了队列长度,意味着要回归原位一次,那么先用k对list长度取余数即可。

206. 反转链表 [LinkList]

在我看来还是递归比较舒服一点:从头开始循环,两个指针分别是头p和头的下一个p_next,如果p_next的下一个,也就是p_next->next不为空,就递归传入p_next和p_next->next,p_next->next为空就证明循环到了最后俩,此时p_next->next指向p,也就是反转操作,p的下一个也就是p->next指空,也就是改递归边界。

不递归纯循环的话就从头设立两个指针p和p_next,初始化为head和head->next,然后翻转,反转之后根据p_next的定位,挪p,然后p_next向下指。

两种方法从操作顺序上来说,递归是从尾部开始反转,迭代就是从头部开始循环

非常规方法就是设其他空间去记录一部分信息,比如将值信息按顺序取下来,反着再装到这个表里;再或者用栈记录从头到尾的位置信息,然后每次反转栈头的两个。

215. 数组中的第K个最大元素 [quick sort]

主要是快排的代码,背下来。

283. Move Zeroes [array]

将数组内的所有0挪到最后。

从头开始遍历,设置一个flag=0,非0的就放到flag位置,flag++,也统计了非0的个数,最后把从第flag位置到最后设置成0即可,注意list长度是0或者list内没有要操作的数这两个问题。

458. 可怜的小猪 [技巧题/num]

比传统的那个用2进制做的题多了一个测试次数。使用测试次数可以扩展维度,即从2进制(测试次数=1,2=1+1)变为测试次数+1进制

可以以一个小时时长为例,每次15min,那么对于每个猪,就能测试4次。

当只有猪1时,测试4次就意味着猪1能断言一共5桶水的情况:如果前4次没死,有毒的就是第5桶,如果前4次死了的话那就是死了那次的那一桶。

两只猪的话,把25个桶排列成5*5,猪1每次喝列5桶,猪2每次喝行5桶,交叉死的就是有毒的

三只猪的话,把125个桶排列成5*5*5,猪1每次喝x面的25桶,猪2每次喝y面的25桶,猪3每次喝z面的25桶;

四只猪的话,排5组的5*5*5猪1每次喝这5组的x面共5*25=125桶,猪2、3同理,猪4每次喝一组的5*5*5共125桶

五只猪的话,排列5*5=25组的5*5*5,猪4每次喝一列的555,猪5每次喝1行的555…

注意上述过程中的乘法,有助于理解,因为我们只有3维,猪喝水喝到4维之后就是3维又3维的叠加。


都是python和cpp

  • python可以函数里面写函数;
  • python max和min、count能用,list的许多操作可用,主要是append和extend;
  • cpp中vector常用的函数:size(),可按数组循环;insert(locat,num,value)指定位置插入指定个数的指定元素;
  • python的list切片,记住[:a]和[a:]的区别;
  • 一些要求在原list上修改,不返回值的题,python操作数据赋值一定要用切片,不然会改变内存位置,导致过不去;
  • 【56】python如果想写变循环上限的循环,一定不要用for,不管用:就是说用i当循环变量写for,如果在for内部修改了这个i,对于循环来说i的值是没有变动的,这一点非常重要,和c语言的循环是不一样的,如果想写,可采用外设循环i,然后用while当循环,在while内部修改i的值,这样才行;
  • python str用切片反转:a -> a[::-1](两个冒号);
  • 负数取余数和正数不一样
  • cpp链表,新设节点最好用ListNode *temp = new ListNode(val,next),两个参数建议为-1和nullptr,用的时候直接用 -> 取元素即可;
  • python str replace的话,一定记得用变量去接一下结果,直接调用不接的话,原本的str是不会变的,会导致误判,觉得replace没生效或者写错了啥的;
  • python 用0初始化mn二维数组:arr=[[0]*m for _ in range(n)];不能用arr=[[0]\m]*n,这样做修改某一行的某个位置会导致所有行的该位置做同样修改,意思就是把第一行的内存存了n遍;

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK