数组中重复的数字
Difficulty: Easy
Link: https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/
Tag: Array, Classic
Hash
class Solution:
def findRepeatNumber(self, nums: List[int]) -> int:
s = set()
for i in nums:
if i in s:
return i
s.add(i)
能用Set/Map的时候避免Dict/HashMap
Time Complexity: O(N)
Space Complexity: O(N)
In-Place Sorting
暴力法对题目的信息利用不完全,题目给出了长度为n的数组内的所有数字都在0 ~ n-1的范围内。因此如果没有重复元素,数组下标和对应值必然是一一对应的。否则在排序过程中一定能找到重复值。
class Solution:
def findRepeatNumber(self, nums: List[int]) -> int:
i = 0
while i < len(nums):
if nums[i] == i:
i += 1
continue
if nums[i] == nums[nums[i]]:
return nums[i]
nums[nums[i]], nums[i] = nums[i], nums[nums[i]]
Time Complexity: O(N) 每一次swap一定会将一个数字放到其对应的下标位置,不涉及反复比较过程
Space Complexity: O(1): In-Place
特别注意一下最后一行的同时赋值:
nums[nums[i]], nums[i] = nums[i], nums[nums[i]]
# instead of
# nums[i], nums[nums[i] = nums[nums[i]], nums[i]
Python的同时赋值实际上是把等号右边的值同时存储在一个tuple里,但在左边赋值的时候是按照从左到右的顺序赋值的,因此下面的赋值语句先改动了nums[i],会导致对nums[num[i]]的引用发生变化,造成赋值错误。
二维数组的查找
Difficulty: Medium
Link: https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/
No: 240
Tag: Queue, Search
Brute Force
遍历二维数组搜索
Time Complexity: O(NM)
Tiny Little Better
纵向遍历,横向二分
Time Complexity: O(NlogM)
Binary-Tree
数组是沿着左上角到右下角递增的,但按照这个方向在每个元素只能排除掉两个方向。换个思路从二维数组的右上角开始搜索,小则向左,大则向右(像二叉树)。每次可以唯一确定一个查找方向。
class Solution:
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
i, j = len(matrix) - 1, 0
while i >= 0 and j < len(matrix[0]):
if matrix[i][j] < target:
j += 1
elif matrix[i][j] > target:
i -= 1
else:
return True
return False
Time Complexity: O(N+M)
重建二叉树(前序遍历、中序遍历)
Created Time: July 11, 2022 11:26 PM
Difficulty: Medium
Last Edited Time: July 13, 2022 10:36 PM
Link: https://leetcode.cn/problems/zhong-jian-er-cha-shu-lcof/
No: 105
Tag: Tree
核心思想——分治
树本身属于规模化数据结构,可以考虑采用分治策略解题。
根据前序遍历和中序遍历的特点,我们可以把其做如下分割:
前序遍历:[ 根节点 | 根节点左子树 | 根节点右子树 ]
中序遍历:[ 根节点左子树 | 根节点 | 根节点右子树 ]
每一个segment的长度都可能为0(边界情况),为方便考虑问题暂且假设每一个非叶子节点都有两个子节点。边界情况将后续讨论。
根据前序遍历,可以找到唯一确定的根节点、根节点的左儿子(如果存在一定是当前节点右边的),但不能确定右边的部分在哪里进行切割(左子树有多长)。此外知道:如果可以切分,根节点右子树部分的第一个节点一定是当前节点的右儿子(前序遍历的特性)。
根据中序遍历,如果根节点的下标确定,可以找到左右子树的长度,即可带回上一步进行分治。
分治算法:
定义递归函数recur(root, left, right)
- 参数:根节点在前序遍历的下标
root
,当前root
树在中序遍历的左边界left
,右边界right
- 递归过程
- 取中序遍历当前
root
值的下标idx
- 左子树:
recur(root + 1, left, idx - 1)
- 右子树:
recur(root + idx -left + 1, idx + 1, right)
root + idx - left + 1
为当前节点下标 + 左子树长度 + 1
- 取中序遍历当前
- 边界条件: left > right
- 这样也同时解决了子节点不满的情况,若左节点为空,则左子树递归部分的
left
仍为当前idx
,右边界idx - 1
一定小于left
。 右节点为空同理。
- 这样也同时解决了子节点不满的情况,若左节点为空,则左子树递归部分的
代码
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
def solver(root, left, right):
if left > right: return
val = preorder[root]
idx = in_order_idx[val]
node = TreeNode(val)
node.left = solver(root + 1, left, idx - 1)
node.right = solver(root + idx - left + 1, idx + 1, right)
return node
in_order_idx = {}
for i in range(len(inorder)):
in_order_idx[inorder[i]] = i
return solver(0, 0, len(preorder) - 1)
用两个栈实现一个队列
Created Time: July 13, 2022 10:50 PM
Difficulty: Easy
Last Edited Time: July 13, 2022 10:59 PM
Link: https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/
No: 105
Tag: Queue, Stack
核心思路
两个栈一个负责入队,一个负责出队,每次出队时先把入队栈里的元素都弹出并且压入出队栈。
可以把入队栈理解成一个缓存?差不多是这个意思。
class CQueue:
def __init__(self):
self.a, self.b = [], []
def appendTail(self, value: int) -> None:
self.a.append(value)
def deleteHead(self) -> int:
if self.b:
return self.b.pop()
if not self.a:
return -1
while self.a:
self.b.append(self.a.pop())
return self.b.pop()
# Your CQueue object will be instantiated and called as such:
# obj = CQueue()
# obj.appendTail(value)
# param_2 = obj.deleteHead()
寻找旋转排序数组中的最小值 II
Created Time: July 13, 2022 11:15 PM
Difficulty: Hard
Last Edited Time: July 13, 2022 11:21 PM
Link: https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii/
No: 154
Tag: Two Pointers
核心思路
一个比较直白的二分查找,旋转后,轴左边的任意一个数字均比右边的数字大,利用这个特性做二分缩小范围即可。
难点在于和153相比,数字可重复,这时候需要一格一格地缩小查找范围。(因此最坏情况下会出现O(N) 的时间复杂度)
Amortized Time Complexity: O(logN)
Worst Case: O(N)
class Solution:
def minArray(self, numbers: [int]) -> int:
i, j = 0, len(numbers) - 1
while i < j:
m = (i + j) // 2
if numbers[m] > numbers[j]:
i = m + 1
elif numbers[m] < numbers[j]:
j = m
else:
j -= 1
return numbers[i]