leetcode hot100 中等难度 day03-刷题
day03 leetcode hot100题单
1. 前言
对应资料
leetcode网站:https://leetcode.cn/
hot100题单:https://leetcode.cn/problem-list/2cktkvj/
说明
本人没有系统的刷过题目,临时抱佛脚,先从hot100开始刷,这是第一次刷。写博客,是强迫自己刷题。当前是第二天刷题,与前一次刷题隔了1天。
- 从简单难度开始往后刷。
- 这是第一遍刷题,主要目的是通过,而不是追求效率。
- 语言采用的python
目录
文章目录
- day03 leetcode hot100题单
- 1. 前言
- 2. 中等 - 两数相加
- 3. 中等 - 无重复字符的最长子串
- 4. 中等 - 最长回文子串
- 5. 中等 - 盛最多水的容器
- 6. 中等 - 三数之和
- 7. 中等 - 电话号码的字母组合
- 8. 中等 - 删除链表的倒数第N个结点
- 9. 中等 - 括号生成
- 10. 总结
2. 中等 - 两数相加
题目
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
思考
这道题的难点在于,他是逆序存储的,因此没有办法直接相加,得想个办法。
想到一个很简单的办法,就是直接遍历两个链表,然后求和后创建一个新的链表返回即可。这个思路简单,就是不知道会不会超出时间限制。
实现
- 好消息上面的方法可以实现并且通过,坏消息是太臃肿了,等下次刷的时候再来优化吧,主要是输出的时候,是分类输出的。
class Solution:def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:# 先遍历链表a,b = '',''while l1:a += str(l1.val)l1 = l1.nextwhile l2:b += str(l2.val)l2 = l2.nextans = str(int(a[::-1])+int(b[::-1]))[::-1]# 逆序输出为链表# 如果长度为1,直接输出if len(ans) == 1:return ListNode(int(ans),None)# 如果长度为2,直接输出if len(ans) == 2:return ListNode(int(ans[0]),ListNode(int(ans[1]),None))# 否则就迭代输出head = cur = ListNode(int(ans[0]),None)nxt = ListNode(int(ans[1]),None)for i in ans[2:]:p = ListNode(int(i),None)cur.next = nxtcur = nxtnxt = pcur.next = nxtreturn head
3. 中等 - 无重复字符的最长子串
题目
给定一个字符串 s
,请你找出其中不含有重复字符的 最长 子串 的长度。
思考
这种子串问题,最常见的解题方式就是滑动窗口,这种是不定长滑动窗口,可以考虑结合对列来做。
刚开始我的窗口大小为1,从最左边开始迭代,第二个元素进入其中,如果不重复,则继续拉入元素,当出现重复元素的时候,排除最左边的,直至不重复,然后再继续上面操作,直至迭代完成。
在整个过程中,最需要注意的就是维护一个最大值即可。
思路非常清晰,开始写。
实现
class Solution:def lengthOfLongestSubstring(self, s: str) -> int:win = []ans = 0# 迭代for v in s:ans = max(ans,len(win))# 如果存在,删除至不重复while v in win:win.pop(0)win.append(v)ans = max(ans,len(win))return ans
4. 中等 - 最长回文子串
题目
给你一个字符串 s
,找到 s
中最长的 回文 子串。
思考
这个题目看起来和上面有点类似,但是差别很大。这道题如果用上面的思路去做,不太好做,因为没有一个明显的告诉你应该删除元素的标志。
因此,重新想一个办法:如果滑动窗口最初就是字符串这么长,
- 如果此时为回文,那么直接结束
- 如果不是回文:删除左边,是回文吗?删除右边是回文吗?如果都不是,就都删除,如果某一边是,也直接结束
(看了题解) 开始实现的时候发现不对了,因为如果是eabcb,那么上面这个判断思路就不行了,因为会变成:abc、b,发现不存在回文子串。
既然整体出发不行,还是从局部出发,比如eabcb,从第2个元素开始:
- a左右的元素是e、b,不同,肯定不是回文串
- b左右的元素是a、c,不同,肯定不是回文串
- c左右的元素都是b,相同,是回文串,那么继续:bcb左右的元素是啥,不存在,肯定不同,不是回文串
这样记录最长的值即可。但是,如果是eabccba呢,偶数的字符串,必须以cc为中心才能正常拓展。
这样我们分奇偶来解决即可。
实现
- 向强大的灵神致敬,参考他的代码写出来的:https://leetcode.cn/problems/longest-palindromic-substring/solutions/2958179/mo-ban-on-manacher-suan-fa-pythonjavacgo-t6cx/
class Solution:def longestPalindrome(self, s: str) -> str:n = len(s)l,r = 0,0# 奇数情况for i in range(n):left = right = i# 以这个元素为中心,去找到最大的回文串while left >= 0 and right < n and s[left] == s[right]:left -= 1right += 1# 更新最长的答案if right-left-1 > r-l:l,r = left+1,right# 偶数情况for i in range(n-1):left,right = i,i+1 # 两个元素# 以这2个元素为中心,去找到最大的回文串while left >= 0 and right < n and s[left] == s[right]:left -= 1right += 1# 更新最长的答案if right-left-1 > r-l:l,r = left+1,rightreturn s[l:r]
5. 中等 - 盛最多水的容器
题目
给定一个长度为 n
的整数数组 height
。有 n
条垂线,第 i
条线的两个端点是 (i, 0)
和 (i, height[i])
。
找出其中的两条线,使得它们与 x
轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
**说明:**你不能倾斜容器。
思考
这道题看似抽象,但是配合它给的示例,就很清晰:
用双指针,left=0,right=n,以两者中较小的计算面积(假设left对应的height[left]较小):
left*(right-left-1)
记录此时的面积值,然后由于left对应的较小,因此让left+=1,然后再次执行上面的判断流程,直至left与right相遇。
开始写代码。
实现
class Solution:def maxArea(self, height: List[int]) -> int:left,right=0,len(height)-1ans = 0while left < right:if height[left] < height[right]:# 计算面积,此时为n-1,就不需要再减去1了ans = max(ans,(right-left)*height[left])# 更新指针left += 1else:ans = max(ans,(right-left)*height[right])right -= 1return ans
6. 中等 - 三数之和
题目
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为 0
且不重复的三元组。
**注意:**答案中不可以包含重复的三元组。
思考
首先,这种多个数求和的问题,简单的比如两数之和,可以直接变成迭代一个数的问题。复杂的就是这种三个数之和。
同样,先转为nums[i]+nums[j]=-nums[k],这样变成两个数的和了。
由于结果对于顺序不看重,因此可以先对数组进行排序,然后进行处理:首先肯定需要双重循环,因为需要迭代一个k,还迭代一个i+j:
- 外层循环:初始化k = nums[k],然后对应的i=k+1,j=length-1(因为是右边的元素之和是否等于-k)
- 内层循环:如果i+j=-k,直接结束;否则如果i+j>-k,也就是值太大了,此时j-=1;否则i+=1
实现
- 实现过程中,发现没有考虑到去重的问题:对于外层循环,如果k=k+1,那么需要跳过,因为后面两者值都相同,后面找到的外层循环自然相同;对于内层循环,也是跳过i/j相同的。
class Solution:def threeSum(self, nums: List[int]) -> List[List[int]]:nums.sort()ans = []n = len(nums)# 外层循环:三个元素,肯定只循环n-2次for k in range(n-2):x = nums[k]# 跳过重复if k > 0 and x == nums[k-1]:continuei = k+1j = n-1# 内层循环while i<j:s = x + nums[i] + nums[j]if s > 0: # 太大了,右边需要向左移动j -= 1elif s < 0:i += 1else: # 相等的话ans.append([x,nums[i],nums[j]])i += 1j -= 1# 跳过相同的while i < j and nums[i] == nums[i-1]: # 这里-1的原因是上面+1了i += 1while j > i and nums[j] == nums[j+1]:j -= 1return ans
7. 中等 - 电话号码的字母组合
题目
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
思考
简单来说,就是字符串“23”,就是从2、3里面各取一个字母组成字符串,可以组成多少个,并且需要返回每个字符串。
如果暴力做法就是一直迭代即可求解。然后我看了最大的长度是4,即最多四重循环,其实感觉还好。先暴力做法试试。
真正开始实现的时候发现一个问题:虽然最大长度为4,但是实际长度并不知道,因此没有办法直接去多重循环实现。发现存在一个递归的思想,比如我有一个n字符长度的字符串,要处理他,肯定先处理第一个字符,它可以是a、b、c;然后这三个a、b、c,又可以分别与后续的n-1个字符组合。这样就形成了递归。
这个思路是没问题的,但是递归如何写?看了题解,发现是一个回溯问题,所谓的回溯问题,就是递归 + 增量构造(记住了,涉及这种一个确定,后续还需组合的问题,可以考虑回溯)。
那么,解题思路就是:
- 边界条件:如果枚举到第n个字符,说明已经凑成一个完整字符串了,此时添加答案,返回None即可
- 递归:迭代数字i对应的字符串,然后把这个字符添加到path中(path就是一个用于存储的临时字符串),然后递归访问下一个i+1的字符串。
实现
class Solution:def letterCombinations(self, digits: str) -> List[str]:# 构建一个哈希表hs = {'2':'abc','3':'def','4':'ghi','5':'jkl','6':'mno','7':'pqrs','8':'tuv','9':'wxyz'}n = len(digits)ans = []path = ['']*n# 特殊情况if n == 0:return []# 定义递归def dfs(index):# 边界条件:如果迭代到n了,说明字符串完整了if index == n:ans.append(''.join(path))return None# 递归for v in hs[digits[index]]:path[index] = vdfs(index+1)dfs(0)return ans
==> 发现一个问题:一般通过后,这种柱状图越少,说明题解方式越唯一:
8. 中等 - 删除链表的倒数第N个结点
题目
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
思考
看到这道题的瞬间,我想的是:遍历知道长度,由此知道倒数第N个结点是哪里,然后直接删除。然后,又看了下,链表最长30,感觉好像还行,试试。
实现
- 实现过程中,发现如果删除掉头节点,必须另外处理,因此考虑加一个哨兵结点
class Solution:def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:# 遍历:长度cur = headlength = 0while cur:length += 1cur = cur.next# 获取正数第几个n = length - n + 1# 加一个哨兵结点dumpy = ListNode(next=head)# 然后再去删除:先找到对应的pre、cur、nxtcur = headpre = dumpycount = 1while count < n:pre = pre.nextcur = cur.nextcount += 1nxt = cur.next# 执行删除pre.next = nxtreturn dumpy.next
9. 中等 - 括号生成
题目
数字 n
代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
思考
首先,括号只是(),不包含其他。
感觉这道题和前面的那道电话号码有点类似,因为:
- 假设给你n对括号,意味着:你有一个长度为2n的字符串
- 然后每个索引的字符都要去迭代,只是记数的时候,需要判断是否为合理括号
这么一想,应该就是回溯问题。那把它当作一个递归问题来做:
- 边界条件:如果迭代到第2n个字符位置,那么就结束(是此时判断字符串合理,还是递归的时候判断?感觉都可以吧)
- 递归:去迭代第i个字符,如果前面出现了右括号,那么此时这个括号为左括号的唯一成立条件就是左边的已经成对了,这也就可以避免不合理字符串了.
实现
- 我最初的写法是下面这也,直接一起考虑,但是这也考虑需要细分的情况太多了,所以写错了,看了题解,发现大家都是分左右来写的,因此改变代码:
class Solution:def generateParenthesis(self, n: int) -> List[str]:ans = []path = ['']*(2*n)hs = {'(':0,')':0} # 这个用来判断是否合理def dfs(i):# 边界条件if i == 2*n:ans.append(''.join(path))return None# 递归for v in '()':# 如果左括号数目<=右括号,可以填左括号if (hs['('] <= hs[')'] and v == '(') or hs['('] < n:path[i] = v# 如果右括号小于左括号,可以填右括号if hs[')'] < hs['('] and v == ')':path[i] = vhs[v] += 1dfs(i+1)dfs(0)return ans
- 分为左右来写
class Solution:def generateParenthesis(self, n: int) -> List[str]:ans = []path = ['']*(2*n)# 目前填了 left 个左括号,right 个右括号def dfs(left,right):# 因为优先填充左括号,因此如果右括号都填充完了,肯定已经结束了if right == n:ans.append(''.join(path)) return None# 如果左括号数目小于n,可以填充if left < n:path[left+right] = '('dfs(left+1,right)# 右括号数目不能大于左括号if right < left:path[left+right] = ')'dfs(left,right+1)dfs(0,0)return ans
我其实感觉我写的总的形式,和分开写的差不多,我用了一个哈希表来统计左右括号的个数,只是在筛选是否为合理括号的时候,没有理清思路,所以搅在一团了。
10. 总结
今日刷题收获:
- 回溯问题的形式,以及思考的方式
- 滑动窗口的形式,以及解题方式
感觉中等难度对于我来说,每一道题都需要一定时间思考了,并且经常出现那种无从下手的状态,肯定是因为题刷得少了,类型见得太少了。