leetcode hot100 简单难度 day02-刷题
day02 leetcode hot100题单
1. 前言
对应资料
leetcode网站:https://leetcode.cn/
hot100题单:https://leetcode.cn/problem-list/2cktkvj/
说明
本人没有系统的刷过题目,临时抱佛脚,先从hot100开始刷,这是第一次刷。写博客,是强迫自己刷题。当前是第二天刷题,与前一次刷题隔了1天。
- 从简单难度开始往后刷。
- 这是第一遍刷题,主要目的是通过,而不是追求效率。
- 语言采用的python
目录
文章目录
- day02 leetcode hot100题单
- 1. 前言
- 2. 简单 - 环形链表
- 3. 简单 - 相交链表
- 4. 简单 - 多数元素
- 5. 简单 - 反转链表
- 6. 简单 - 翻转二叉树
- 7. 简单 - 回文链表
- 8. 简单 - 移动零
- 9. 简单 - 比特位计数
- 10. 简单 - 找到所有数组中消失的数字
- 11. 简单 - 汉明距离
- 12. 简单 - 二叉树的直径
- 13. 简单 - 合并二叉树
- 14. 总结
2. 简单 - 环形链表
题目
给你一个链表的头节点 head
,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true
。 否则,返回 false
。
思考
如上图所示,有环的一个重要特征就是某个结点的下一个结点是已经存在过的结点。
类似这种环的问题,我首先想到的是双指针,比如快慢指针,只要两个指针可以相遇,且相遇处不是None,肯定存在环。
那么初始化两个指针都指向3,一个名为fast,一次走两步,一个为slow,一次走一步。那么,上面的情况,两者相遇处为4,都走了3次。
换一个情况:假设为3->2->0->4-5->2(多了一个,环在5->2处),那么两者:
fast:3-->0-->5-->0-->5
slow:3-->2-->0-->4-->5
没问题,开始写。
实现
- 我就是根据上面的思路实现的,但是我发现存在一个巨大的问题:就是最初fast=slow=head,此时就不会进入循环,因此我必须强迫它进入一次循环,加入了一个名为count的参数,这样就会强迫进入循环,上面的代码就可以正常执行了:
class Solution:def hasCycle(self, head: Optional[ListNode]) -> bool:slow = fast = head# 看我说明count = 1# 开始走,直至两者相遇while fast != slow or count == 1:count -= 1# 如果此时已经为None了,就不需要next了,不然会报错if slow is None:slow = Noneelse:slow = slow.nextif fast is None or fast.next is None:fast = Noneelse:fast = fast.next.nextreturn True if fast is not None else False
3. 简单 - 相交链表
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
。
图示两个链表在节点 c1
开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
intersectVal
- 相交的起始节点的值。如果不存在相交节点,这一值为0
listA
- 第一个链表listB
- 第二个链表skipA
- 在listA
中(从头节点开始)跳到交叉节点的节点数skipB
- 在listB
中(从头节点开始)跳到交叉节点的节点数
评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA
和 headB
传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。
思考
这道题刚开始看的时候,题目很长,但是仔细一看,发现后面都是废话,只有一个关键点:就是判断两个链表是否有相交的部分。
对于这类两个链表的问题,一般都是考虑两个指针,只是要找到如何让他们走一样的步数并且相交。这里的话,如果不是要返回相交的结点,最简单的方式就是判断两者结束的位置是否相同即可。但是,要返回相交的结点,那么结束的位置必须在相交处。
发现一个点:
- 假设A链长为a+c;B链长为b+c;其中c为两者公有的部分,即相交后的部分
- 那么,要让两者走到一起:
A:先走a,再走c,再走b
B:先走b,再走c,再走a
==> 此时两者都走了a+b+c,且都停留在结点处
实现
- 实现过程中,发现key:如何处理不相交的?如果两次走到None,说明肯定不相交:因此我设计了一个count计数器
class Solution:def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:A,B = headA,headBcount = 0# 如果两者不相等,就继续循环while A != B:# 正常就更新A = A.nextB = B.next# 如果A走到末尾,就走Bif A is None:count += 1A = headBif B is None:B = headA# 如果遇到两次None,就结束if count == 2:return Nonereturn A
4. 简单 - 多数元素
题目
给定一个大小为 n
的数组 nums
,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋
的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
思考
这个很简单,不管那么多的话,先把数组集合化去重,然后迭代,用count方法统计个数即可。
实现
- 发现是返回元素个数最多的,不是统计多数元素个数,这一点注意
class Solution:def majorityElement(self, nums: List[int]) -> int:target = list(set(nums)) # 去重n = len(nums)//2ans = [nums.count(i) for i in target] # 统计个数return target[ans.index(max(ans))] # 返回个数最大的索引对应的元素
5. 简单 - 反转链表
题目
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
思考
首先,想到反转链表,重点是得知道尾结点是啥,但是链表本身无法知道长度,不过我们可以通过迭代去探寻尾部,并且在探寻过程中进行反转操作。
反转操作的核心:
- temp = n.next,保存n+1结点
- n.next = n-1,n结点的下一个是n-1
- 更新上一个结点和当前结点,分别变为n、n+1
实现
class Solution:def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:cur = head # 当前结点,即npre = None # 上一个结点,即n-1while cur:# 只要没有访问道末尾temp = cur.next # n+1这个结点cur.next = prepre = cur # 记得更新上一个结点cur = tempreturn pre
6. 简单 - 翻转二叉树
题目
给你一棵二叉树的根节点 root
,翻转这棵二叉树,并返回其根节点。
思考
二叉树的问题,基本都涉及到递归。
先当作一整棵树来做,那么翻转它,就是交换左右子树:
- temp = A.left
- A.left = A.right
- A.right = temp
这样的话,假设一个递归方式即可,边界条件就是访问到None,自然结束。
实现
class Solution:def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:# 边界条件if root is None:return None# 翻转temp = root.leftroot.left = root.rightroot.right = tempself.invertTree(root.left)self.invertTree(root.right)return root
7. 简单 - 回文链表
题目
给你一个单链表的头节点 head
,请你判断该链表是否为回文链表。如果是,返回 true
;否则,返回 false
。
思考
个人觉得一个简单的方式,先遍历一遍链表,直接变为列表,然后用回文最简单的判断方式,即:
- A == A[::-1]
来判断即可。
实现
class Solution:def isPalindrome(self, head: Optional[ListNode]) -> bool:ans = []# 遍历链表while head:ans.append(head.val)head = head.next# 开始判断return ans == ans[::-1]
8. 简单 - 移动零
题目
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
思考
最先想到的是,迭代整个数组,当遇到0的时候,删除它,并添加0到末尾,这样重复n次即可。
实现
class Solution:def moveZeroes(self, nums: List[int]) -> None:"""Do not return anything, modify nums in-place instead."""n = len(nums)i = 0while n:if nums[i] == 0:nums.pop(i)nums.append(0)# 记得更新i,此时i不变才对else:i += 1n -= 1
9. 简单 - 比特位计数
题目
给你一个整数 n
,对于 0 <= i <= n
中的每个 i
,计算其二进制表示中 1
的个数 ,返回一个长度为 n + 1
的数组 ans
作为答案。
思考
这道题思路还是比较清晰,对于一个十进制整数i,可以用bin(i)变为二进制,然后用字符串形式统计1个数即可。
实现
class Solution:def countBits(self, n: int) -> List[int]:ans = [0]for i in range(1,n+1):ans.append(bin(i).count('1'))return ans
10. 简单 - 找到所有数组中消失的数字
题目
给你一个含 n
个整数的数组 nums
,其中 nums[i]
在区间 [1, n]
内。请你找出所有在 [1, n]
范围内但没有出现在 nums
中的数字,并以数组的形式返回结果。
思考
最简单的方法,暴力迭代,去迭代1-n范围的数字,查看是否存在于nums中即可。
这个暴力解法超出时间限制,重新想个办法。考虑到是重复问题,首选哈希表,而且哈希表的查询是O(1),非常快。
实现
- 解法正确,但是超时,因此需要进行优化,一个简单的优化方式,就是我访问1-n是否存在于nums中,肯定必须访问n次,如果我先对nums去重,访问nums中的元素是否存在于1-n中即可
class Solution:def findDisappearedNumbers(self, nums: List[int]) -> List[int]:n = len(nums)ans = []for i in range(1,n+1):if i not in nums:ans.append(i)return ans
- 优化后,之前卡住的通过了,但是后面仍然有案例报错超出时间
class Solution:def findDisappearedNumbers(self, nums: List[int]) -> List[int]:n = len(nums)nums = list(set(nums))ans = list(range(1,n+1))for i in nums:if i in ans:ans.remove(i)return ans
- 改用哈希表,正常通过,只是我用的方式肯定不是最佳,还是用了双重循环
class Solution:def findDisappearedNumbers(self, nums: List[int]) -> List[int]:n = len(nums)hs = defaultdict(int)nums = list(set(nums))ans = []for i in nums:hs[i] = 1# 判断是否存在for i in range(1,n+1):if i not in hs:ans.append(i)return ans
11. 简单 - 汉明距离
题目
两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。
给你两个整数 x
和 y
,计算并返回它们之间的汉明距离。
思考
二进制 + 不同位置数目 = 异或。秒了。
二进制问题,首先考虑是否可以用异或算法。所谓异或,就是相同的位置记为0,不相同的记为1,因此我们只需要统计1的个数即可。
异或在python中的计算符合是:^。
实现
- 注意,异或算符可以直接用于int整数,不是非要二进制表示
class Solution:def hammingDistance(self, x: int, y: int) -> int:return bin(x^y).count('1')
12. 简单 - 二叉树的直径
题目
给你一棵二叉树的根节点,返回该树的 直径 。
二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root
。
两节点之间路径的 长度 由它们之间边数表示。
思考
其实所谓的直径,和深度类似,如下图:
那么,我们就是遍历二叉树,同时统计一下最大的深度,然后最大的路径是左右两边最大深度的和,比如上图左边最大深度为2,右边为1,和为3。
实现
- 为什么return是-1,是因为叶子结点不算边,因此遇到叶子节点,需要少一个
class Solution:def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:ans = 0def dfs(node):nonlocal ans# 边界条件if node is None:return -1# 计算层数left = dfs(node.left) + 1right = dfs(node.right) + 1# 最大直径就是两者和的最大值ans = max(ans,left+right)return max(left,right)dfs(root)return ans
13. 简单 - 合并二叉树
题目
给你两棵二叉树: root1
和 root2
。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
注意: 合并过程必须从两个树的根节点开始。
思考
上面题目很长,简单概述一下:就是合并两棵树,都有的部分就是相加存在,独特的部分也必须保留。
同样,我们先当成两颗最简单的树来判断。
- 当前结点的值,相加作为新结点
- 左子树的值相加
- 右子树的值相加
- ==> 如果某颗树为空了,直接返回另外一个树,这就是边界条件
实现
- 一次实现,感觉这个代码还挺漂亮
class Solution:def mergeTrees(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:# 边界条件if root1 is None:return root2if root2 is None:return root1# 递归return TreeNode(root1.val+root2.val,self.mergeTrees(root1.left,root2.left),self.mergeTrees(root1.right,root2.right))
14. 总结
今天的题目出现很多递归的问题,还有一些少见的异或符号等问题,又学习到很多新鲜知识。
- 感觉判断回文,用python超级简单,直接A==A[::-1]即可
- 另外,涉及到二进制的问题,多考虑异或等算符
- 二叉树的问题,多为递归,考虑好边界条件,以及带着全局思维先把大问题列出来,再去思考递归的细节
- 另外,有些时候不要纠结,暴力做法也是可取的,先走出来再说,再想办法去优化