剑指Offer 0700 重建二叉树 (中等, 2021-11)
剑指Offer 3900 数组中出现次数超过一半的数字(摩尔投票) (简单, 2021-12)
剑指Offer 5100 数组中的逆序对 (困难, 2022-01)
剑指Offer2 076 数组中的第K大的数字 (中等, 2022-02)
给出二叉树前序遍历和中序遍历的结果,重建该二叉树并返回其根节点。
详细描述
输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
示例 1:
Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]
示例 2:
Input: preorder = [-1], inorder = [-1]
Output: [-1]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
- 前序遍历,节点按照
[ 根节点 | 左子树 | 右子树 ]
的顺序输出。 - 中序遍历,节点按照
[ 左子树 | 根节点 | 右子树 ]
的顺序输出。 - 可知:
- 前序遍历的首元素为根节点 node 的值。
- 在中序遍历的结果中搜索根节点的索引 ,可将中序遍历划分为
[ 左子树 | 根节点 | 右子树 ]
。 - 根据中序遍历中的左(右)子树的节点数量,可将前序遍历划分为
[ 根节点 | 左子树 | 右子树 ]
。
Python
# 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:
if len(preorder) < 1 or len(inorder) < 1: # 两个都判断一下
return None
# 建立根节点
root_val = preorder[0]
root = TreeNode(root_val)
root_idx = inorder.index(root_val) # 找到根节点在中序遍历的位置
# 截取左子树的 preorder 和 inorder,递归建立左子树
inorder_left = inorder[:root_idx]
preorder_left = preorder[1: len(inorder_left) + 1]
root.left = self.buildTree(preorder_left, inorder_left)
# 截取右子树的 preorder 和 inorder,递归建立右子树
inorder_right = inorder[root_idx + 1:]
preorder_right = preorder[-len(inorder_right):]
root.right = self.buildTree(preorder_right, inorder_right)
return root
- 更常见的写法会使用一个字典来保存每个节点在中序遍历中的位置,取代
root_idx = inorder.index(root_val)
这一步, - 但是这样做就必须每次从最初的 preorder 和 inorder 中截取左右子树的片段,代码会变得比较复杂,传递的参数比较多,故没有采用这种写法;
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
详细描述
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
限制:
1 <= 数组长度 <= 50000
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
- 排序后,数组中间位置的数一定满足题意;
- 时间复杂度
O(NlogN)
;
Python
class Solution:
def majorityElement(self, nums: List[int]) -> int:
return sorted(nums)[len(nums) // 2]
- 一次遍历,记录每个数出现的次数;
- 空间复杂度
O(N)
;
Python
class Solution:
def majorityElement(self, nums: List[int]) -> int:
from collections import defaultdict
cnt = defaultdict(int)
for x in nums:
cnt[x] += 1
if cnt[x] > len(nums) // 2:
return x
# return -1
- “摩尔投票法”的核心思想是一一抵消;
- 假设已知目标数为 x,遍历时若出现一次 x 记
+1
票,否则为-1
票;- 推论1:最终票数和必大于 0;
- 推论2:若前 n 个数的票数和为 0,那么剩余部分依然满足推论1,即目标数字依然为 x;
Python
class Solution:
def majorityElement(self, nums: List[int]) -> int:
cnt = 0
for x in nums:
if cnt == 0: # 当票数和为 0 时,假设当前值为目标值
ret = x # 如果这个数不是目标值,那么它迟早会因为不断 -1,被替换掉
if x == ret:
cnt += 1
else:
cnt -= 1
return ret
- 本题使用分治在时间和空间上都不是最优,仅用于理解分治的思想;
Python
class Solution:
def majorityElement(self, nums: List[int]) -> int:
def recur(lo, hi): # [lo, hi] 闭区间
if lo == hi: # 当数组中只有一个元素时,这个数就是目标值
return nums[lo]
# 分治
mid = (hi - lo) // 2 + lo
l = recur(lo, mid)
r = recur(mid + 1, hi)
# 如果左右返回值相同时,显然这个值就是目标值
if l == r:
return l
# 否则需要判断哪个出现的次数更多
lc = sum(1 for i in range(lo, hi + 1) if nums[i] == l)
rc = sum(1 for i in range(lo, hi + 1) if nums[i] == r)
return l if lc > rc else r
return recur(0, len(nums) - 1)
在数组中的两个数字,如果前一个数字大于后面的数字,则这两个数字组成一个逆序对。
输入一个数组,求该数组中的逆序对的总数。
详细描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
限制:
0 <= 数组长度 <= 50000
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
-
归并排序
-
在合并过程中统计逆序对的数量
- 归并排序的合并过程:依次比较两个子数组的首元素,将其中较小的放置到一个新的数组中;
- 每当遇到
左子数组当前元素 > 右子数组当前元素
时,意味着「左子数组当前元素 至 末尾元素」与「右子数组当前元素」构成了若干「逆序对」
-
归并排序需要用到辅助数组,因此其空间复杂度为
O(N)
;- 辅助数组一般有两种用法,分别见写法1 和 写法2;
Python:写法1
class Solution:
def reversePairs(self, nums: List[int]) -> int:
# 临时数组 for 归并排序:空间复杂度 O(N)
tmp = [0] * len(nums)
def merge(lo, hi): # 闭区间 [lo, hi]
if lo >= hi: return 0
m = (lo + hi) // 2
ret = merge(lo, m) + merge(m + 1, hi) # 分治
# 辅助数组
tmp[lo: hi + 1] = nums[lo: hi + 1] # 先复制,再赋值
l, r = lo, m + 1 # 左右指针
for i in range(lo, hi + 1):
# 必须先判断是否越界
if l == m + 1: # 左子数组遍历完毕
nums[i] = tmp[r]
r += 1
elif r == hi + 1 or tmp[l] <= tmp[r]: # 右子数组遍历完毕,或 tmp[l] <= tmp[r] 时,即左指针位置小于右指针位置
nums[i] = tmp[l]
l += 1
else: # tmp[l] > tmp[r] 时
nums[i] = tmp[r]
r += 1
ret += m - l + 1 # 累计逆序对数
return ret
return merge(0, len(nums) - 1)
Python:写法2
class Solution:
def reversePairs(self, nums: List[int]) -> int:
# 临时数组 for 归并排序:空间复杂度 O(N)
tmp = [0] * len(nums)
def merge(lo, hi): # 闭区间 [lo, hi]
if lo >= hi: return 0
m = (lo + hi) // 2
ret = merge(lo, m) + merge(m + 1, hi) # 分治
l, r = lo, m + 1 # 左右指针
for i in range(lo, hi + 1):
# 必须先判断是否越界
if l == m + 1: # 左子数组遍历完毕
tmp[i] = nums[r]
r += 1
elif r == hi + 1 or nums[l] <= nums[r]: # 右子数组遍历完毕,或 nums[l] <= nums[r]
tmp[i] = nums[l]
l += 1
else: # nums[l] > nums[r]
tmp[i] = nums[r]
r += 1
ret += m - l + 1 # 累计逆序对数
# 辅助数组
nums[lo: hi + 1] = tmp[lo: hi + 1] # 先赋值,再覆盖
return ret
return merge(0, len(nums) - 1)
Python
class BIT:
def __init__(self, n):
self.n = n
self.tree = [0] * (n + 1)
@staticmethod
def lowbit(x):
return x & (-x)
def query(self, x):
ret = 0
while x > 0:
ret += self.tree[x]
x -= BIT.lowbit(x)
return ret
def update(self, x):
while x <= self.n:
self.tree[x] += 1
x += BIT.lowbit(x)
class Solution:
def reversePairs(self, nums: List[int]) -> int:
n = len(nums)
# 离散化
tmp = sorted(nums)
for i in range(n):
nums[i] = bisect.bisect_left(tmp, nums[i]) + 1
# 树状数组统计逆序对
bit = BIT(n)
ans = 0
for i in range(n - 1, -1, -1):
ans += bit.query(nums[i] - 1)
bit.update(nums[i])
return ans
Python:快排?
class Solution:
def reversePairs(self, nums: List[int]) -> int:
if len(nums) <= 1: return 0
more, less = [], []
count = 0
center_count = 0
center = random.choice(nums)
for i in nums:
if i > center:
more.append(i)
elif i == center:
center_count += 1
count += len(more)
else:
count += center_count
count += len(more)
less.append(i)
count += self.reversePairs(more) + self.reversePairs(less)
return count
Python:二分?
class Solution:
def reversePairs(self, nums: List[int]) -> int:
tmp = []
ret = 0
for num in nums[::-1]:
cur = bisect_left(tmp, num)
ret += cur
tmp[cur:cur] = [num] # 用这句是 732ms
# tmp.insert(cur, num) # 用这句是 1624ms
return ret
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
详细描述
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
提示:
1 <= k <= nums.length <= 10^4
-10^4 <= nums[i] <= 10^4
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/xx4gT2
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路1:partition操作(分治)
- partition操作描述:先随机确定一个锚点,然后将数组划分为小于锚点和大于锚点的两部分呢;
import random
class Solution:
""""""
def findKthLargest(self, nums: List[int], k: int) -> int: # noqa
""""""
lo, hi = 0, len(nums) - 1
while True: # 第 k 大,排序后期下标应该是 k - 1
idx = self.partition(nums, lo, hi)
if idx + 1 == k:
return nums[idx]
elif idx + 1 < k:
lo = idx + 1
else:
hi = idx - 1
def partition(self, nums: List[int], lo: int, hi: int) -> int:
""""""
# === 挑选锚点 ===
# 方式1)默认选 lo 作为锚点
# pivot = nums[lo]
# 方式2)随机选择一个锚点,并把锚点固定到首位或末位,这里交换到首位
flag = random.randint(lo, hi)
pivot = nums[flag]
nums[flag], nums[lo] = nums[lo], nums[flag]
# === partition 操作 ===
# 方式1)单向遍历
idx = lo # 记录锚点在数组中的升序顺位
for i in range(lo + 1, hi + 1):
if nums[i] > pivot: # 找到一个大于锚点的值
idx += 1
nums[idx], nums[i] = nums[i], nums[idx]
nums[idx], nums[lo] = nums[lo], nums[idx] # 把锚点交换到 idx 的位置
return idx
# 方式2)左右交换
# l, r = lo, hi
# while l < r:
# while l < r and nums[r] <= pivot:
# r -= 1
# while l < r and nums[l] >= pivot:
# l += 1
# if l < r:
# nums[l], nums[r] = nums[r], nums[l]
# nums[lo], nums[l] = nums[l], nums[lo]
#
# return l
思路2:大顶堆(Python,调库)
import heapq
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
""""""
heap = []
for x in nums:
heapq.heappush(heap, -x) # 默认是小顶堆,这里传入 -x,模拟大顶堆
for _ in range(k - 1):
heapq.heappop(heap)
return -heap[0]