LeetCode-腾讯精选练习 50 题-思路简述

本文章旨在用较少且通俗的话说明这50道题的做题思路(题目链接直接点击各题目标题即可)。只有思路,不放代码,主要是方便以后的复习和二刷、三刷。以思路为主,答案次之。(部分难以用文字理解的题会画图)。读者如果想避免边看答案边做的方式,以思路为起点动手学习,这也是个不错的学习资源。

简单难度

1.整数反转

定义一个while循环,x每次循环都要除以10,结束条件为x=0,每次通过x与10取余得到该位上的数digit,用res表示结果,每次res都乘10加上该位数digit,即可完成反转。

由于反转后可能越界,需要在循环内部,优先判断res是否大于INT_MAX/10或者是否小于INT_MIN/10。

2.回文数

就在题1的基础上比较一下反转后的数与原数是否相同。但要注意,负数一定不是,反转后溢出一定不是回文数。

3.最长公共前缀

先排序字符串数组,该数组中所有字符串的最长公共前缀就是最小字符串和最大字符串的公共前缀。所以求最小字符串和最大字符串的公共前缀即可。

4.有效的括号

这种跟括号匹配相关的题目,第一方法就是使用数据结构栈,左括号都入栈,碰到右括号时,若栈不为空且栈顶是和它同类型的左括号,就匹配栈顶出栈继续遍历下一个,反之就是括号无效。

5.合并两个有序链表

从两个链表的头节点开始,选中节点值较小的作为合并两边的头节点,被选中节点的链表右移继续比较,将较小者加入合并链表中并右移,直到其中一个链表被选完,那么另一个有剩余的链表的剩余部分直接接到合并链表的后面即可。

6.删除有序数组中的重复项

由于这题要求原地修改,且只需要我们输出不重复的数字个数n,不过必须保证我们把原数组的前n-1都修改为了不重复的数字,所以定义一个初始计数cnt,循环数组的时候,每次碰到当前位与前一位相同时,该cnt就不会+1(而循环i每次都会加1),因为重复,不需要,而其他情况保证nums[cnt++]=nums[i]

7.最大子序和

先考虑特殊情况,如果数组中没有大于0的数,返回数组中最大值即可。

然后从头开始算和,如果和小于0就更新开头从头开始计算(因为最大子数组的左子数组和右子数组一定不小于0),每次循环都要比较一下该值,如果更大就更新最大值(记录局部最大值)。最后遍历完就是最大值了。

8.爬楼梯

非常简单的dp问题。定义dp[i]为爬上i阶有几种方法,那么dp[i]=dp[i-1]+dp[n-2]。

详情可看这篇动态规划组合与排列

9.合并两个有序数组

使用三指针,l=m-1、r=n-1和pos=m+n-1。

将nums1和nums2中较大者放入nums1尾部直到一方处理完(即指针l或r减到小于0为止)。

若num1对应指针l还有剩余,则无需处理,因为已经排序好,若num2对应的指针r还有剩余则需要处理,即nums1[pos--]=nums2[r--]。

10.二叉树的最大深度

求二叉树最大深度,最容易想到的方法就是BFS了,通过队列来实现广度优先遍历,从而得到二叉树深度。

另一个方法是递归:

①终止条件:树为空时,深度为0。

②返回值:当前树的最大深度。

③本级递归:此时树就看成三个点root、root->left、root->right,root->left和root->right分别记录的是root的左右子树的最大深度。也就是说在root的左右子树中选择较大的一个,再加上1就是以root为根的子树的最大深度了,然后再返回这个深度即可。

11.买卖股票的最佳时期

我们需要一个变量记录最大利润,还需要一个变量记录局部最小价格,比如当我循环到第i天时,min_price代表我前0~i-1天的最小价格,这时候假设第i天卖出的话,算出获取的利润,每次循环若得到更大的利润则更新最大利润max_profit。

12.只出现一次的数字

方法一,用unordered_map来统计数字出现次数,返回出现一次的即可。

方法二,使用异或,因为相同的数异或为0,其余出现两次的元素都变为了0,那么最后结果就是只出现一次的元素。

13.环形链表

使用快慢指针,快指针一次走两步,慢指针一次一步,如果能相遇就有环。

14.最小栈

使用两个栈,栈一用来存储,栈二保证栈顶始终存储最小值。在入栈操作时,栈一正常入栈,若栈二为空或者当前入栈元素小于或等于栈二栈顶元素则入栈二;在出栈操作时,栈一正常弹出栈顶元素,若栈二栈顶元素与栈一相同也需要弹栈。

15.相交链表

巧用数据结构unordered_map解题,以链表结点为key,次数为value存储在这个map中,出现次数大于1(即value大于1)的对应的key即为所求结果(相交的链表结点)。

16.多数元素

使用unordered_map解题,统计各数字的次数,次数大于数组长度的一半的即为结果。

17.反转链表

定义三个结点指针,当前cur,前一个pre,后一个tmp,初始时cur=head,其余两个为NULL,循环直到cur为空停止,在循环中使tmp指向cur的后一个,cur的后一个指针修改为指向前一个pre,操作完后,pre和cur都后移一位。

18.存在重复元素

方法一,用unorder_map统计数字出现的次数,存在大于1的就返回true。

方法二,先排序,再判断是否有相邻元素相等。

19.2的幂

一定大于0,二进制一定就是100···000,那么n-1是011···111,n与n-1按位与的结果是0。

20.二叉搜索树的最近公共祖先

从树根节点开始遍历,若节点值大于结点p和q的值,则说明公共祖先在左子树,那么遍历往左子树走,若节点值小于结点p和q的值,则说明公共祖先在右子树,那么遍历往右子树走,其余情况就是该遍历节点即为最近公共祖先。

21.删除链表中的节点

删除非尾节点,将后一个节点的值赋给当前节点,然后当前节点指向后一个节点的后一个节点。

22.Nim游戏

只要剩下的石头是4的配速,就一定输。

23.反转字符串

使用双指针,l=0,r=n-1,进入循环,结束条件是l<=r,每趟循环交换l和r位置的字符。

24.反转字符串中的单词III

有点滑动窗口的味道,这题在遍历字符串的时候每次碰到空格i时,更新窗口右区间就i-1,然后对这一部分单词使用23题中的算法反转,再更新窗口左区间为i+1,继续;当然遍历到最后一位时,也需要右区间更新为i,对这部分单词进行反转。

中等难度

25.两数相加

从两个链表的头结点开始相加得到sum,以sum/10作为新链表结点值,sum%10作为进位,直到有链表遍历完为止,若此时有一个链表剩余,需要把之前的进位也算上继续对剩余链表进行同样操作。当然如果最后两个链表都完了,仍然有进位,则需要新链表下一位指向通过进位new的节点。

26.最长回文子串

dp问题。设dp[i][j]表示从下标i到j的字符串是否是回文字符串(这里下标从1开始,不从0开始)。

初始化。因为单个字符串一定是回文字符串,所以dp[i][i]=true。

状态转移方程:如果第i位等于第j位,则dp[i][j]=dp[i+1][j-1](若i和j只相差一位,则dp[i][j]=true)。

每次都需要根据新回文串的长度更新长度最大值以及取得该最大值的左右区间,通过左区间和长度可以截取字符串得到结果。

27.字符串转换整数

就是自己手写一个atoi函数。首先需要去掉前导空格得到第一个不是空格的字符索引,如果全是空格,则直接返回0。对于第一个字符,还需要判断是否是+或者-来取得正负性,继续下一个,如果是非数字字符,后面的直接省略了。如果将会越界,就返回int最大值或最小值即可。每次将结果*10加上这一轮字符得到的数字*正负性。

28.盛水最多的容器

使用双指针l和r,l=0,r=n-1,首先我们要知道,容纳的水等于两指针之间的距离*高度较小指针的高度。那么每次想尽可能找最大,那应该移动那个指针呢?如果我们移动高度大指针,首先距离较小,其次两者较小高度只会减小或者不变,所以说移动大指针是没办法找到的最大,只会越找越小,所以移动高度较小指针。记录每次移动的值,如果更大则更新最大值。

29.三数之和

之前做过两数之和的,是排序+双指针,这题也同理。

先排序,因为要找出三数之和为0的,所以说,一定存在一个数小于0或者等于0,也就是说我们可以先找到不大于0的数num[i],当然需要去重(因为这题需要返回三数集合)。然后以l=i+1,r=n-1使用两数之和的处理办法找出值等于-num[i]的,找到了,放入结果集,仍然需要去重。

具体去重方法就是(有两个位置需要去重)①如果前一位等于该位直接continue略过本次循环;②如果下一位和该位相等则++l,如果前一位和该位相等则--r。

30.最接近的三数之和

和题29很像,仍然需要排序,先外层循环i,从0到n-3,然后使用双指针,定义l=i+1,r=n-1,在内层循环使用while(l<r)类似于两数之和的方法。每次获得i,l,r位置三数之和与target的距离,更小,则更新距离值并记录此时得到的三数之和,当然如果该三数之和等于target直接返回,大于则左移r,小于则右移l。

31.搜索旋转排序数组

①由于是旋转数组,所以得先找出有序区间界点,这一步使用双指针+二分搜索(l=0和r=n-1,mid=l+(r-l)/2),如果nums[mid]大于或者等于nums[0],代表临界点不在mid左边,则l=mid+1,反之在左边,r=mid-1。这样得到的r就是前半段有序区间的末尾索引。

②这时候在判断target是否大于或等于nums[0],如果是,说明其在前半段有序区间内,那么直接在前半段有序区间内用双指针+二分搜索找到该数即可;如果不是,说明在后半段有序区间内,在后半段有序区间用双指针+二分搜索即可。

32.字符串相乘

按如上图所是计算,先计算出每位结果再统一处理每一位和进位。

 由于我装结果的向量c定义的长度是题目中给的两个数字长度之和,但是最后可能没有这么长,所以需要去掉前缀0,当然如果结果只有一位且为0就不用去掉。

33.全排列

建议直接看这篇递归回溯总结,前几期也最好都看一下,能很容易理解这类回溯题型。

34.螺旋矩阵

和35题一样,定义四个变量top=0、bottom=n、left=0、right=n,按照右下左上的顺序走遍历即可。

35.螺旋矩阵II

理解怎么顺时针走遍矩阵即可,定义四个变量top=0、bottom=n、left=0、right=n,按右下左上的顺序走和赋值,首先走右,下标是left~right-1,完后top+1;然后走下,下标是top~bottom-1,然后right-1,;然后走左,下标是right-1~left,然后bottom-1,然后走上,下标是bottom-1~top,然后left+1。就按这样的规律走,直到填完n*n个数。

36.旋转链表 

将链表成环,然后确定新的尾部并断开即可。

如何确定新尾部位置呢?首先得到链表长度len,然后得到移动偏移量offset=k%len,原头部移动len-offset-1步后的位置是新尾节点位置。

37.不同路径

两种方法:

①这个很容易想到,因为就是我们高中的时候学的排列组合,因为只能往下或者往右走,所以,因为走到终点必定需要走m-1次下,n-1次右,所以相当于在m+n-2里面选m-1个。就是C(上标m-1)(下标m+n-2)。

②使用dp解决。dp[i][j]表示走到第i行第j列的路径方式数(i、j从一开始)。因为只能向下或者右走,所以dp[i][j]=dp[i-1][j]+dp[i][j-1]。

38.子集

可以看这篇文章递归回溯总结。比较清晰全面的分析回溯类问题。

39.格雷编码

格雷编码主要是相邻数只有一位不同。然后题目要求只需要给出一种答案即可。

 我们可以看到G(2)可以通过把G(1)加一位最高位0和加一位最高位1的倒序组合起来得到,0和1加一位最高位0得到00和01,0和1加一位最高位1得到10和11,倒序就是11和10,那么结果就是00、01、11、10,G(3)同理可由G(2)得到。

由于最高位加0相当于没变,也就是说G(n)等于把G(n-1)加上最高为1之后再倒序加入G(n-1)的格雷编码中就可以得到G(n)的格雷编码。

40.买卖股票的最佳时期II

由于此题不需要交易次数,只要利润最大结果,所以可以类比为一个求数组总增量大小的题目。某一项相对于前一项增加就算进增量,减少就忽略。

41.环形链表II

这题要得到入环的第一个节点。定义快慢指针,快指针一次走两步,因为有环,则必定相遇,相遇后,将快指针速度减为一次一步并放回起点,两个指针再次相遇的点即可入环的第一个节点。

详情可看这篇文章环形链表相关结论证明分析

42.LRU缓存机制

使用哈希表+双向链表即可。哈希表的key为key值,value为对应的节点。在链表尾部的节点代表最近最少使用节点,每次使用或者更新都需要将对应的节点移动到头部表示最近才使用,容量满就优先去掉尾部节点。

所以这题其实主要是考察双向链表的基本操作,比如删除双向链表中的节点,加入新节点到头部。然后最好定义一个哨兵头节点和一个哨兵尾节点,这样在删除的时候不用进行条件边界判断,所有删除节点一视同仁。

43.排序链表

 使用题5.合并两个有序链表作为辅助函数,使用分治法,通过左闭右开划分区间(也就是一开始区间末尾节点为null,因为尾节点的下一个为null且为右开区间)进行链表拆分然后归并。(其中归并使用合并两个有序链表的函数即可)。每次划分区间的中点通过快慢指针来找。

拆分区间

 合并区间

44.数组中的第K个最大元素

直接排序然后返回索引为n-k的。至于排序方法,推荐使用快排或者推排序,排序算法可以看这篇文章十大排序算法

45.二叉搜索树中第K小的元素

二叉搜索树的中序遍历就是节点值从大到小的排序。

46.二叉树的最近公共祖先

这种树的题目一般使用递归。

①找递归终止条件:首先树为空时终止;其次如果指定的两个节点存在等于当前树的根节点,直接就得到最近公共祖先返回。

②找返回值:简单来说就是返回当前树的公共祖先,如果当前树的左右子树都不存在公共祖先(也就是说返回都不为空,代表节点是一边一个),所以最近公共祖先只能是root;否则,左右子树一边不存在,一边存在(只要一边空一边不空,那么必然是非空那边两个节点都有),这时候最近公共节点就在相应返回非空的子树中,因为要找最近的,所以需要继续往深处找。

③本地递归应该做什么:判断当前子树的根是否等于指定节点的一个或者两个,如果是,直接返回,不是的话,需要继续其左右子树去找,左右子树各找到一个,就返回该子树根节点,否则哪个子树找到就返回哪个子树,如果都没找到这种情况在该题中不存在。

47.除自身以外数组的乘积

这题不能用除法,所以我们需要定义两个数组left和right,left[i]表示下标从0~i的乘积,right[i]表示下标从i~n-1的乘积。这样的话结果可以根据这两个数组来推出。

①边界特殊处理:nums[0]=right[1],nums[n-1]=left[n-2]。

②一般情况,nums[i]=left[i-1]*right[i+1]。

困难难度

48.寻找两个正序数组的中位数

两种常见思路,①直接合并数组找出中位数;②使用指针从两数组中找到中位数下标。复杂度都是O(m+n)。(m和n分别是nums1和nums2的长度)。

这题我们可以分为两种情况:

①奇数的时候,相当于求两个有序数组的所有数中第(m+n+1)/2小的数。

②偶数的时候,相当于求两个有序数组的所有数中第(m+n+1)/2小的数和第(m+n+2)/2小的数的平均值

那么抽取出共同点方法,也就是说这题只需要我们能够写出一个求两个有序数组第k小的数的工具函数就能解决问题。

简单思路如下,求第k小的数,那么比较nums1中第k/2小的数和num2中第k/2小的数的大小,较小的一边的k/2个数可以直接排除掉,因为第k小的数不能在其中。这样的话,每次减少k/2的数。那么时间复杂度时O(log(k)),而k等于(m+n+1)/2或者(m+n+2)/2,所以最后为O(log(m+n))。

49.合并K个升序链表

 使用题5.合并两个有序链表作为辅助函数,使用分治法,使用左闭右开区间,选定中间节点划分区间,再两两合并,和43.排序链表思想相同(把该题中的每个链表看做43题图中的每个节点)。由于每次合并链表数为原来的1/2,所以时间复杂度为n(k·logk)。

50.二叉树中的最大路径和

这题我觉得只要弄清楚一个概念,就和10.求二叉树的最大深度差不多。要理解一个定义为节点的最大贡献和,叶子节点就是本身值,那么叶子节点上一层节点等于该结点值加上值较大的子节点(前提是子节点的值不小于0,不然就不加)。最后可以推出某个节点的最大贡献和等于本身加上子节点中最大贡献和较大的节点的最大贡献和(如果子节点的贡献和都小于0,就不加)。

①终止条件:树为空,最大贡献和为0。

②返回值:当前节点的最大贡献和。

③本级递归:看成只有三个点root,root->left,root->right。通过递归调用root->left和root->right分别得到root的左右子节点的最大贡献和,可能最大路径是root+left+right,所以每次这个值也得记录比大小来更新最大值,然后计算出root的最大贡献和返回即可(为root->val加上左右子节点中最大贡献和较大者)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值