Problem Set 3
Problem Set 3.1
Problem 3.1.1
\((1)\)利用二分答案。令\(l,r\)表示答案存在的区间(初始化\(l=2,r=n-1\))。在每一次二分中:令\(m=\lfloor\frac{l+r}{2}\rfloor\),判断\(A[m]\)与其两个邻居之间的大小关系,若\(A[m]\)是最小的,则找到了局部最小元素,否则的话,不妨设\(A[m]>A[m-1]\),于是令\(r=m-1\),重复上述过程即可。若当\(r<l\)了还没有找到,那么就说明无解
正确性证明:下面假设原问题有解。利用数学归纳法,认为任意时刻一定存在一个\(p∈[l,r]\),使得\(A[p]\)是答案。对于\(l=2,r=n-1\)的时候,显然成立;在二分的过程中,不妨设区间从\([l,r]\)变成了\([l,m-1]\),那么后者区间中是一定有解的,这是因为从\(m-1\)开始递减,假设\(A\)元素值一直在单调递减,那么最多到\(A[2]\),就有\(A[2]\leq A[1]\)且\(A[2]\leq A[3]\),则\(A[2]\)是局部最小元素,否则没有减到\(A[2]\)序列就不再单调递减了,那么当前位置就是一个局部最小元素
def find_local_min(A):
n = len(A)
if n < 3:
return -1
l, r = 1, n - 2
while l <= r:
m = (l + r) // 2
if A[m] <= A[m-1] and A[m] <= A[m+1]:
return m
elif A[m] > A[m-1]:
r = m - 1
else:
l = m + 1
return -1
\((2)\)证明见上问的证明过程
Problem 3.1.2
\((1)\)首先将\(S\)中的元素排序,然后就可以找出中位数\(M\);将\(S\)中的每个元素与\(M\)相减并取绝对值;再重新将\(S\)排序,输出前\(k\)个就好了(如果要求输出原始数值的话,用一个离散化映射一下就好了)。时间复杂度为\(O(n\log n+k)\),瓶颈是排序
def find_closest_k(S, k):
S_sorted = sorted(S)
n = len(S_sorted)
if (n & 1) == 0:
M = (S_sorted[n//2]+S_sorted[n//2+1])/2
else:
M = S_sorted[n//2]
elements = [x for x in S_sorted if x != M]
elements.sort(key=lambda x: (abs(x - M), x))
return elements[:k]
\((2)\)首先用选择算法找出中位数\(M\),时间复杂度为\(O(n)\);将\(S\)中的每个元素与\(M\)相减并取绝对值;用选择算法找出第\(k\)小,再将\(S\)中不超过第\(k\)小的元素全部输出即可。时间复杂度为\(O(n)\).没有内置库,就偷懒写伪代码了
def find_closest_k(S, k):
n = len(S)
M = 选择算法找出的S的中位数
elements = [abs(x - M) for x in S if x != M]
val = 选择算法找出的elements的第k小
ans = elements中不超过val的数组成的列表
if ans的长度大于k:
此时有多个val,删除val的副本直到ans的长度等于k
return ans
Problem 3.1.3
a)此时一共有\(\lceil\frac{n}{3}\rceil\)组,各组的中位数中,不超过中位数的中位数的有\(\lfloor\frac{1}{2}\times(\lceil\frac{n}{3}\rceil+1)\rfloor\)个,在这些组中,我们令其中最大的数都比中位数的中位数大,一共有\(\lfloor\frac{1}{2}\times(\lceil\frac{n}{3}\rceil+1)\rfloor\)个;各组的中位数中,超过中位数的中位数的有\(\lfloor\frac{1}{2}\times\lceil\frac{n}{3}\rceil\rfloor\)个,在这些组中,我们令其中最小的数都比中位数的中位数大,一共有\(3\times\lfloor\frac{1}{2}\times\lceil\frac{n}{3}\rceil\rfloor\)个数超过中位数的中位数,故最坏情况下小于中位数的中位数的有\(n-\lfloor\frac{1}{2}\times(\lceil\frac{n}{3}\rceil+1)\rfloor-3\times\lfloor\frac{1}{2}\times\lceil\frac{n}{3}\rceil\rfloor\leq\frac{n}{3}+3\)
b)一共有\(\lceil\frac{n}{3}\rceil\)组,对每一组找出中位数至少需要\(3\)次比较,故找出每组的中位数的时间复杂度为\(\Omega(n)\);找出中位数的中位数的时间复杂度为\(T(\lceil\frac{n}{3}\rceil)\);根据a),最坏情况下小于中位数的中位数的数的个数不超过\(\frac{n}{3}+3\),所以不小于中位数的中位数的数至少有\(\frac{2n}{3}-3\)个,假设我们要向这边递归,则时间复杂度更大。故\(T(n)\geq T(\lceil\frac{n}{3}\rceil)+T(\frac{2n}{3}-3)+\Omega(n)\)
c)假设当\(n\)比较小的时候有\(T(n)\geq cn\log n\);则当\(n\)较大时有
如果\(T(n)\geq cn\log n\),那么有
\(c_1\)的取值上不封顶,所以证毕
Problem 3.1.4
a)此时一共有\(\lceil\frac{n}{7}\rceil\)组,各组的中位数中,不超过中位数的中位数的有\(\lfloor\frac{1}{2}\times(\lceil\frac{n}{7}\rceil+1)\rfloor\)个,在这些组中,我们令其中最大的数都比中位数的中位数大,一共有\(\lfloor\frac{1}{2}\times(\lceil\frac{n}{7}\rceil+1)\rfloor\)个;各组的中位数中,超过中位数的中位数的有\(\lfloor\frac{1}{2}\times\lceil\frac{n}{7}\rceil\rfloor\)个,在这些组中,我们令其中最小的数都比中位数的中位数大,一共有\(7\times\lfloor\frac{1}{2}\times\lceil\frac{n}{7}\rceil\rfloor\)个数超过中位数的中位数,故最坏情况下小于中位数的中位数的有\(n-\lfloor\frac{1}{2}\times(\lceil\frac{n}{7}\rceil+1)\rfloor-3\times\lfloor\frac{1}{2}\times\lceil\frac{n}{7}\rceil\rfloor\geq\frac{2n}{7}-8\)
b)一共有\(\lceil\frac{n}{7}\rceil\)组,找出所有组的中位数的时间复杂度为\(O(n)\)(每组数都较小,找出每组的中位数可看做\(O(1)\));找出中位数的中位数的时间复杂度为\(W(\lceil\frac{n}{7}\rceil)\);根据a),不小于中位数的中位数的数至多有\(\frac{5n}{7}+8\)个,假设我们要向这边递归,则时间复杂度更大。故\(W(n)\leq W(\lceil\frac{n}{7}\rceil)+W(\frac{5n}{7}+8)+O(n)\)
c)假设当\(n\)比较小的时候有\(W(n)\leq cn\);则当\(n\)较大时有
如果\(W(n)\leq cn\),那么有
\(c\)的取值上不封顶,所以证毕
Problem Set 3.2
Problem 3.2.1
利用数学归纳法。当\(k=2\)的时候,我们将球进行编号为\(1,2,3,4\).我们首先比较\(1\)和\(2\)
- 两者相同:再比较\(1\)和\(3\)
- 两者不同:\(3\)是要找的球
- 两者相同:\(4\)是要找的球
- 两者不同:再比较\(1\)和\(3\)
- 两者不同:\(1\)是要找的球
- 两者相同:\(2\)是要找的球
不难知道上面的做法只需要两次比较,所以对\(k=2\)时,满足
假设现在对\(\forall k<c\)都满足,当\(k=c\)的时候,我们将\(2^k\)个球分成四堆相同数量的球(每一堆是\(2^{k-2}\)个);我们首先比较第一堆和第二堆
- 两堆相同:在第三堆和第四堆里面运用数学归纳法,可以知道命题成立
- 两堆不同:在第一堆和第二堆里面运用数学归纳法,可以知道命题成立
综上,证毕
# 为了简便,下面的balls数组不是重量而是球的标号,== 表示天平的一次操作
def find_odd_ball(balls, k):
if k == 2:
if balls[0] == balls[1]:
if balls[0] == balls[2]:
return 3
else:
return 2
else:
if balls[0] == balls[2]:
return 1
else:
return 0
else:
n = len(balls)
part = n // 4
group1 = list(range(part))
group2 = list(range(part, 2*part))
group3 = list(range(2*part, 3*part))
group4 = list(range(3*part, 4*part))
if balls[group1[0]] == balls[group2[0]]:
merged = group3 + group4
idx = find_odd_ball([balls[i] for i in merged], k-1)
return merged[idx]
else:
merged = group1 + group2
idx = find_odd_ball([balls[i] for i in merged], k-1)
return merged[idx]
Problem 3.2.2
a)将所有元素按照\(x\)为关键字从小到大排序;然后扫描序列,并统计前缀和\(S\);再扫描序列,设当前为\(x_i\),则如果\(S_{i-1}\lt\frac{W}{3}\)且\(W-S_{i}\leq \frac{2W}{3}\),则\(x_i\)就是要找的数
def find_one_third_median_a(elements):
elements.sort(key=lambda x: x[0])
total = sum(w for _, w in elements)
prefix = 0
for x, w in elements:
left = prefix
right = total - prefix - w
if left < total / 3 and right <= 2 * total / 3:
return x
prefix += w
return None
b)设solve(l,r,sum1,sum2)表示答案存在于\(x_1\sim x_n\)从小到大排序之后的第\(l\sim r\)个数(我们实际上不用真的去排序,具体见下),且第\(1\sim l-1\)个数的权重和为sum1,第\(r+1\sim n\)个数的权重和为sum2,找出答案的函数。我们递归解决这个函数,设存在一个数组now表示当前在\(l\sim r\)的\(x\),那么我们在now里面执行\(O(r-l)\)找中位数,设为\(m\),然后扫描now,统计出其中小于\(m\)的数的权重和(sum_3)和大于\(m\)的数的权重和(sum_4),于是可以知道全局中,小于\(m\)的数的权重和为sum1+sum3,大于\(m\)的数的权重和为sum2+sum4;如果题目给的两个不等式的第一个不等式不满足,说明\(m\)太大了,于是将now中大于等于\(m\)的数删除,并且令sum2+=大于等于m的数的权重和,然后向左边递归即可;如果是第二个不等式不满足,处理方法类似(显然两个不等式最多只会有一个不满足)
不难知道上述方法的时间复杂度为\(T(n)=O(n)+T(\frac{n}{2})=O(n)\)
当然其实不用说的这么细,评分标准是不会要求的
def find_one_third_median_b(elements):
total = sum(w for _, w in elements)
sum_1, sum_2 = 0, 0
while True:
m = 用选择算法在当前的elements中找出的中位数
sum_left = sum(w for x, w in elements if x < m)
sum_right = sum(w for x, w in elements if x > m)
sum_mid = total - sum_left - sum_right
if sum_1 + sum_left < total / 3 and sum_2 + sum_right <= 2 * total / 3:
return m[0]
elif sum_1 + sum_left >= total / 3:
sum2 = sum2 + sum(x[1] for x in elements if x[0] >= m)
elements = [x for x in elements if x[0] < m]
else:
sum1 = sum1 + sum(x[1] for x in elements if x[0] <= m)
elements = [x for x in elements if x[0] > m]
Problem 3.2.3
\((1)\)设solve(p)表示以节点\(p\)为根的子树的深度。首先递归solve(ls[p])和solve(rs[p])(ls[p]和rs[p]分别是\(p\)的左右儿子),令以\(p\)为根的子树的深度为以其两个儿子为根的两个子树的深度的更大值加一。上述DFS只会遍历每一个点一遍,所以时间复杂度为\(O(n)\)
class TreeNode:
def __init__(self, left=None, right=None):
self.left = left
self.right = right
def compute_height(root):
if not root:
return -1
left_height = compute_height(root.left)
right_height = compute_height(root.right)
return max(left_height, right_height) + 1
\((2)\)设solve(p)表示以节点\(p\)为根的子树的直径,同时维护dis[p]表示以节点\(p\)为根的子树的深度(这个可以由\((1)\)得到)。首先递归solve(ls[p])和solve(rs[p]),回溯后计算l=dis[ls[p]]+dis[rs[p]]+2,然后比较l与两个儿子的直径的大小,最大值作为\(p\)的直径;时间复杂度为\(O(n)∈O(n\log n)\)
class TreeNode:
def __init__(self, left=None, right=None):
self.left = left
self.right = right
def compute_diameter(root):
max_diameter = 0
def dfs(node):
nonlocal max_diameter
if not node:
return -1
left_depth = dfs(node.left)
right_depth = dfs(node.right)
max_diameter = max(max_diameter, left_depth + right_depth + 2)
return max(left_depth, right_depth) + 1
dfs(root)
return max_diameter

浙公网安备 33010602011771号