CS170 · 讨论课 2

讨论 2

分治(divide and conquer)专题练习:逆序对计数(基于归并排序)、最大子数组和(分治变体)、帕累托最优点(Pareto-optimal points)筛选、复数与单位根(n-th roots of unity)复习,以及单调矩阵(monotone matrix)每行最小值的高效查找(选做)。

知识焦点

先建立概念地图

  • divide and conquer
  • merge sort
  • counting inversions
  • maximum subarray sum
  • Pareto optimality
  • complex numbers
  • roots of unity
  • monotone matrix

建议顺序

题面 → 提示 → 答案

先完整作答,再按需展开提示;最后才展开官方参考解答并逐步核对证明与计算。

本地来源

可离线完成

题面与解答均由本地一手资料整理:dis02.pdf、dis02-sol.pdf

本地一手资料完整提取 · 讨论课

完整题目、提示与答案

本节按本地一手资料完整整理,共 5 道大题。本地官方解答已完整收录并默认折叠;建议先独立完成,再逐题核对推导。

使用说明

  • 所有题面均已本土化,并补足网页离线作答所需的数值、代码、表格与约束。
  • 题目与答案分层展示,展开答案前可先查看提示。
  • 本地来源:dis02.pdf、dis02-sol.pdf
discussion02-counting-inversions1 逆序对计数(Counting inversions)题目 · 提示 · 答案
官方题面翻译可离线作答官方解答已收录

完整题面

该问题源自排名(rankings)分析。考虑比较两种排名方式:一种方法是按照其中一种排名给元素(书籍、电影等)打上 \(1\) 到 \(k\) 的标签,再按照另一种排名排列这些标签,观察有多少对元素是“顺序错乱”的。

给定一个由 \(k\) 个不同数组成的序列 \(n_1, \cdots, n_k\)。我们称两个下标 \(i < j\) 构成一个逆序对(inversion),当且仅当 \(n_i > n_j\),即这两个元素是“顺序错乱”的。请给出一个分治(divide and conquer)算法,在 \(O(k \log k)\) 时间内确定序列 \(n_1, \cdots, n_k\) 中逆序对的总数。

只需给出算法描述与运行时间分析。

查看解题提示

尝试修改归并排序(merge sort),在合并(merge)的过程中同步计数:当从右半部分取出元素时,左半部分剩余的所有元素都与它构成逆序对。

自检:若直接逐对检查,时间复杂度会退化为 \(O(k^2)\)。思考如何在不逐一查看每对逆序的情况下,利用“已排序”的性质批量统计跨左右两半的逆序对数量。

展开官方参考解答

(i) 主思路

逆序对的数量可能达到二次级别,因此算法必须在不逐对检查的情况下确定总数。

思路是修改归并排序。将序列分成两半 \(n_1, \cdots, n_l\) 与 \(n_{l+1}, \cdots, n_k\),在分别排序两半的同时统计每半内部的逆序对数;然后在合并两半时,统计两个数分别位于不同半区的逆序对 \((a_i, a_j)\) 的数量。总数等于这三部分之和。

(ii) 伪代码

procedure count(A)
    if length[A] = 1 then
        return A, 0
    B, x <- count(A 的前半部分)
    C, y <- count(A 的剩余部分)
    D <- 空列表
    z <- 0
    while B 非空或 C 非空 do
        if B 为空 then
            将 C 追加到 D 并移除 C 的元素
        else if C 为空 then
            将 B 追加到 D 并移除 B 的元素
        else if B[1] < C[1] then
            将 B[1] 追加到 D 并从 B 中移除 B[1]
        else
            将 C[1] 追加到 D 并从 C 中移除 C[1]
            z <- z + length[B]
    return D, x + y + z

(iii) 正确性证明

考虑合并过程中的某一步,假设指针分别指向 \(b_i\) 与 \(c_j\)。由于 \(B\) 和 \(C\) 已经有序,若将 \(b_i\) 追加到 \(D\),不会产生新的逆序对,因为 \(b_i\) 小于 \(C\) 中剩余的所有元素,并且排在所有这些元素之前。

反之,若将 \(c_j\) 追加到 \(D\),则 \(c_j\) 小于 \(B\) 中所有剩余元素,并且排在所有这些元素之后,因此逆序对的计数应增加 \(B\) 中剩余元素的个数。

(iv) 运行时间分析

在每次递归调用中,合并两个有序列表并计数耗时 \(O(n)\)。运行时间为 \(T(n) = 2T(n/2) + O(n)\),由主定理(Master theorem)得 \(O(n \log n)\)。

解答来源:dis02-sol.pdf · 1-2 · confidence: high

题面来源:dis02.pdf · 1 · confidence: high

discussion02-maximum-subarray-sum2 最大子数组和(Maximum Subarray Sum)题目 · 提示 · 答案
官方题面翻译可离线作答官方解答已收录

完整题面

给定一个包含 \(n\) 个整数的数组 \(A\),最大子数组和(maximum subarray sum)是 \(A\) 的任意连续子数组(包括空子数组)的最大和。换言之,最大子数组和为:

\[ \max_{i \le j} \sum_{k=i}^{j} A[k] \]

例如,数组 \([-2, 1, -3, 4, -1, 2, 1, -5, 4]\) 的最大子数组和为 \(6\),即连续子数组 \([4, -1, 2, 1]\) 的和。

设计一个 \(O(n \log n)\) 时间的算法,求出最大子数组和。请给出三部分(3-part)解答。

查看解题提示

把数组切成等大的两半 \(L\) 和 \(R\)。最大子数组要么完全落在 \(L\) 或 \(R\) 中(递归求解),要么跨越中点,即从 \(L\) 内开始、到 \(R\) 内结束。对于跨越的情况,整个子数组必然由“以中点结尾的最大后缀”和“以中点下一个位置开始的最大前缀”拼接而成——这两部分能否各自在线性时间内求出?

自检:若只用“左 / 右”递归而忽略跨越中点的情况,能否处理最大子数组恰好横跨两半的输入?

展开官方参考解答

(i) 主思路:

直觉上我们似乎要找出两个下标 \(i\) 和 \(j\),使得子数组 \(A[i{:}j]\) 的和尽可能大,共有 \(O(n^2)\) 种可能。但存在分治解法:把 \(A\) 分成相等的两半 \(L\) 和 \(R\),即 \(A[1{:}n/2]\) 与 \(A[n/2+1{:}n]\)。最大子数组有两种可能:

(a) 完全包含在 \(L\) 或 \(R\) 中:通过递归求解。令 \(s_1\) 为递归调用 \(L\) 的结果,\(s_2\) 为递归调用 \(R\) 的结果。

(b) 跨越边界(crosses the boundary),即起始于 \(L\)、终止于 \(R\):若最大子数组为 \(A[i{:}j]\),则 \(A[i, n/2]\) 必须是以 \(A[n/2]\) 结尾的最大子数组,且 \(A[n/2+1, j]\) 必须是以 \(A[n/2+1]\) 开始的最大子数组。看起来我们似乎需要求解两个最大子数组问题:找出使 \(A[i, n/2]\) 和最大的 \(i\),以及使 \(A[n/2+1, j]\) 和最大的 \(j\)。注意每个问题都只需确定一个下标,各自只有 \(n/2\) 种可能,因此每个都可在 \(O(n)\) 步内完成。例如,为求 \(i\),依次计算 \(A[k, n/2]\)(\(k = 1\) 到 \(n/2\))并取最大值。最终令 \(s_3\) 为 \(A[i, n/2]\) 的元素和加上 \(A[n/2+1, j]\) 的元素和。

若 \(n = 1\),返回 \(\max\{A[1], 0\}\);否则返回 \(\max\{s_1, s_2, s_3\}\)。

(ii) 正确性证明:对 \(n\) 进行归纳。当数组长度为 \(1\) 时,算法显然正确。

当数组长度至少为 \(2\) 时,最大子数组有两种可能:

(a) 完全包含在 \(L\) 或 \(R\) 中。

(b) 跨越边界,即在第 \(n/2\) 个元素之前开始、在第 \(n/2\) 个元素之后结束。

第一种情况下,算法会输出 \(s_1\) 或 \(s_2\),由归纳假设它们正确。

第二种情况下,跨越边界的最大子数组必须由“以第 \(n/2\) 个元素结尾的和最大的子数组”与“以第 \(n/2+1\) 个元素开始的的和最大的子数组”组成。因此该情况下算法输出 \(s_3\),是正确的。

(iii) 运行时间分析:计算 \(s_3\) 的方法耗时 \(O(n)\),因为每个 \(A[i{:}n/2]\) 与 \(A[n/2+1{:}i]\) 的和都可以在给定前一个和的情况下以 \(O(1)\) 时间算出。于是得到递推式:

\[ T(n) = 2T(n/2) + O(n) \]

由主定理可得运行时间为 \(O(n \log n)\)。

解答来源:dis02-sol.pdf · 3-4 · confidence: high

题面来源:dis02.pdf · 2 · confidence: high

discussion02-pareto-optimality3 帕累托最优性(Pareto Optimality)题目 · 提示 · 答案
官方题面翻译可离线作答官方解答已收录

完整题面

给定点集 \(P = \{(x_1, y_1), (x_2, y_2), \ldots, (x_n, y_n)\}\),称点 \((x_i, y_i) \in P\) 是帕累托最优的(Pareto-optimal),当且仅当不存在 \(j \ne i\) 使得 \(x_j > x_i\) 且 \(y_j > y_i\)。换言之,\(P\) 中没有任何点严格地位于 \((x_i, y_i)\) 的上方且右侧。请设计一个 \(O(n \log n)\) 时间的分治算法,给定 \(P\),输出 \(P\) 中所有帕累托最优点。

只需给出算法描述与运行时间分析。

查看解题提示

按 \(x\) 坐标对数组进行分割。思考:在递归返回的两部分结果中,有一部分的所有返回点在合并后是否天然保持帕累托最优?另一部分中,哪些点会被“支配”掉,能否在线性时间内剔除?

反例方向:若只按 \(x\) 切分而忽略左右之间的 \(y\) 坐标关系,左侧哪些点会被右侧新出现的点淘汰?

展开官方参考解答

算法:将点按 \(x\) 坐标排序,令 \(L\) 为左半部分、\(R\) 为右半部分。对 \(L\) 和 \(R\) 分别递归,记返回的帕累托最优点集合分别为 \(L'\) 与 \(R'\)。通过一次线性扫描计算 \(R\) 中最大的 \(y\) 坐标 \(y_{\max}\),然后移除 \(L'\) 中所有 \(y\) 坐标小于 \(y_{\max}\) 的点。返回 \(L'\) 与 \(R'\) 的并集。

证明:现在证明该算法在已按 \(x\) 坐标排序的点集上的正确性。最简单情况下只有一个点,它默认是帕累托最优的。设有多个点,则可假设 \(L'\) 包含 \(L\) 中的帕累托最优点,\(R'\) 包含 \(R\) 中的帕累托最优点。

\(R'\) 中的每个点在 \(L \cup R\) 中都是帕累托最优的,因为 \(L\) 中所有点的 \(x\) 坐标都更小,无法破坏 \(R'\) 中点的帕累托最优性。

对于 \(L'\) 中的每个点,当且仅当其 \(y\) 坐标大于 \(y_{\max}\)(\(R\) 中最大的 \(y\) 坐标)时,它在 \(L \cup R\) 中才是帕累托最优的。因此,算法返回的集合在 \(L \cup R\) 中是帕累托最优的。

运行时间:由主定理,该算法运行时间为 \(T(n) = 2T(n/2) + O(n) = O(n \log n)\)。

解答来源:dis02-sol.pdf · 5 · confidence: high

题面来源:dis02.pdf · 3 · confidence: high

discussion02-complex-numbers-review4 复数复习(Complex numbers review)题目 · 提示 · 答案
官方题面翻译可离线作答官方解答已收录

完整题面

复数是能够写成矩形形式 \(a + bi\) 的数(\(i\) 为虚数单位,满足 \(i^2 = -1\))。以下著名公式(欧拉公式,Euler's formula)将复数的极坐标形式与矩形形式联系起来:

\[ r e^{i\theta} = r(\cos \theta + i \sin \theta) \]

在极坐标形式中,\(r \ge 0\) 表示复数到 \(0\) 的距离,\(\theta\) 表示其角度。注意:由于 \(\sin(\theta) = \sin(\theta + 2\pi)\),\(\cos(\theta) = \cos(\theta + 2\pi)\),因此对任意 \(r, \theta\) 都有 \(r e^{i\theta} = r e^{i(\theta + 2\pi)}\)。

\(n\) 次单位根(\(n\)-th roots of unity)是满足 \(\omega^n = 1\) 的 \(n\) 个复数,由下式给出:

\[ \omega_k = e^{2\pi i k / n}, \quad k = 0, 1, 2, \ldots, n-1 \]

(a) 设 \(x = e^{2\pi i \cdot 3/10}\),\(y = e^{2\pi i \cdot 5/10}\),它们都是 \(10\) 次单位根。计算乘积 \(x \cdot y\)。它是否是某个 \(n\) 的 \(n\) 次单位根?它是否是 \(10\) 次单位根?

若改为 \(x = e^{2\pi i \cdot 6/10}\),\(y = e^{2\pi i \cdot 7/10}\),会发生什么?请尽可能化简所有答案。

(b) 证明:对任意 \(n\) 次单位根 \(\omega \ne 1\),当 \(n > 1\) 时,\(\sum_{k=0}^{n-1} \omega^k = 0\)。

提示:使用等比数列求和公式 \(\sum_{k=0}^{n} \alpha^k = \frac{\alpha^{n+1} - 1}{\alpha - 1}\),该公式对复数同样适用。

(c) (i) 求所有满足 \(\omega^2 = -1\) 的 \(\omega\)。

(ii) 求所有满足 \(\omega^4 = -1\) 的 \(\omega\)。

查看解题提示

(b) 利用 \(\omega^n = 1\) 把等比数列求和公式的分子化简为 \(0\)。注意前提 \(\omega \ne 1\) 保证分母非零。

(c) 对等式两边平方,可把 \(\omega^2 = -1\) 与 \(\omega^4 = -1\) 分别转化为求 \(4\) 次与 \(8\) 次单位根的问题;再剔除会使等式左端变成 \(+1\) 的那些根。

展开官方参考解答

(a) \(x \cdot y = e^{2\pi i \cdot 8/10}\)。它总是一个 \(10\) 次单位根(这是一般性质)。但因为 \(8/10 = 4/5\),它也是 \(5\) 次单位根。

若 \(x = e^{2\pi i \cdot 6/10}\),\(y = e^{2\pi i \cdot 7/10}\),则乘积会“绕圈”(wind around),变为 \(e^{2\pi i \cdot 13/10} = e^{2\pi i \cdot 3/10}\)。

(b) 由 \(\omega^n = 1\),代入等比数列求和公式:

\[ \sum_{k=0}^{n-1} \omega^k = \frac{\omega^n - 1}{\omega - 1} = \frac{1 - 1}{\omega - 1} = 0 \]

(c) (i) \(\omega = i,\, -i\)。

证法之一:两边平方得 \(\omega^4 = 1\),故只需考虑 \(4\) 次单位根:\(e^{2\pi i \cdot 0/4},\, e^{2\pi i \cdot 1/4},\, e^{2\pi i \cdot 2/4},\, e^{2\pi i \cdot 3/4}\),即 \(1,\, i,\, -1,\, -i\)。几何上,它们对应沿复平面单位圆走 \(0\)、\(1\)、\(2\)、\(3\) 个四分之一圈。其中平方为 \(-1\) 的是 \(i\) 与 \(-i\)。

(ii) \(\omega = e^{2\pi i \cdot 1/8},\, e^{2\pi i \cdot 3/8},\, e^{2\pi i \cdot 5/8},\, e^{2\pi i \cdot 7/8}\)。

类似地,两边平方得 \(\omega^8 = 1\),故只需考虑 \(8\) 次单位根。但 \(\omega^4 \ne 1\),因此 \(\omega\) 不能是 \(4\) 次单位根。那些不是 \(4\) 次单位根的 \(8\) 次单位根恰好是 \(e^{2\pi i \cdot 1/8},\, e^{2\pi i \cdot 3/8},\, e^{2\pi i \cdot 5/8},\, e^{2\pi i \cdot 7/8}\),经验证它们均满足 \(\omega^4 = -1\)。

解答来源:dis02-sol.pdf · 6-7 · confidence: high

题面来源:dis02.pdf · 4-6 · confidence: high

discussion02-monotone-matrices5 (选做)单调矩阵(Monotone matrices)题目 · 提示 · 答案
官方题面翻译可离线作答官方解答已收录

完整题面

一个 \(m \times n\) 矩阵 \(A\) 是单调的(monotone),当且仅当 \(n \ge m\)、\(A\) 的每一行都没有重复元素,并且满足如下性质:若第 \(i\) 行的最小值位于列 \(j_i\),则 \(j_1 < j_2 < j_3 < \cdots < j_m\)。例如,下面的矩阵就是单调的(每行的最小值以粗体标出):

\[ \begin{bmatrix} \mathbf{1} & 3 & 4 & 6 & 5 & 2 \\ 7 & 3 & \mathbf{2} & 5 & 6 & 4 \\ 7 & 9 & 6 & 3 & 10 & \mathbf{0} \end{bmatrix} \]

给出一个高效(即优于 \(O(mn)\) 时间)的算法,找出单调矩阵 \(A\) 每一行的最小值。请给出三部分(3-part)解答。运行时间分析中不必写出形式化的递推关系,使用非正式的总结(如图示法“proof by picture”)即可。

图表条件 · 重画题目给出的 3×6 单调矩阵示例,并标出每行最小值。

\[ \begin{bmatrix} \mathbf{1} & 3 & 4 & 6 & 5 & 2 \\ 7 & 3 & \mathbf{2} & 5 & 6 & 4 \\ 7 & 9 & 6 & 3 & 10 & \mathbf{0} \end{bmatrix} \]

来源:dis02.pdf · 5

查看解题提示

考虑取第 \(m/2\) 行并直接扫描这一行找到其最小值所在列 \(j\)。利用单调性:所有上方行的最小值必然位于第 \(j\) 列左侧,所有下方行的最小值必然位于第 \(j\) 列右侧。能否把问题归约到两个更小的子矩阵上?

自检:每层递归对若干子问题的扫描总长度是否被 \(n\) 控制?为什么同层的子矩阵列互不重叠?

展开官方参考解答

(i) 主思路:

若 \(A\) 只有一行,则直接扫描该行并输出其最小值。

否则,仅通过扫描第 \(m/2\) 行找到 \(A\) 的最小元素。设该元素位于第 \(j\) 列,则由于 \(A\) 是单调矩阵,第 \(m/2\) 行以上所有行的最小值必定位于第 \(j\) 列左侧,即位于子矩阵 \(A[1{:}m/2-1,\; 1{:}j-1]\) 中;第 \(m/2\) 行以下所有行的最小值必定位于第 \(j\) 列右侧。因此我们递归调用算法处理子矩阵 \(A[1{:}m/2-1,\; 1{:}j-1]\) 和 \(A[m/2+1{:}m,\; j+1{:}n]\),分别找出第 \(1\) 到 \(m/2-1\) 行与第 \(m/2+1\) 到 \(m\) 行的最小值并输出。

(ii) 正确性证明:对 \(m\) 进行完全归纳(total induction)。

基础情况 \(m = 1\):算法显式地找出并输出该单一行的最小值。

若 \(A\) 有多行,算法当然会找出并输出第 \(m/2\) 行的正确最小值。如前所论证,\(A\) 的第 \(1\) 到 \(m/2-1\) 行的最小值与子矩阵 \(A[1{:}m/2-1,\; 1{:}j-1]\) 的最小值相同,而第 \(m/2+1\) 到 \(m\) 行的最小值与子矩阵 \(A[m/2+1{:}m,\; j+1{:}n]\) 的最小值相同。由归纳假设,算法正确输出这些矩阵的最小值,连同上面的第 \(m/2\) 行一起,便找出并输出了 \(A\) 所有行的最小值。

(iii) 运行时间分析:

有两种分析方法。其一是显式统计每层递归的总步数。由于 \(m\) 在每一步递归中减半,共有 \(\log m\) 层递归。在每一层递归中,矩阵的列被分成两部分——分别属于左子矩阵与右子矩阵。而且,执行分割只需扫描单行全部元素,耗时 \(n\)。这意味着在任意递归层,所有子矩阵的列互不相交:若各子矩阵分别有 \(n_k\) 列,则 \(\sum_k n_k \le n\)。将这些矩阵再分割到下一层所需的总步数为 \(\sum_k n_k \le n\)。因此共有 \(\log m\) 层递归,每层总耗时 \(n\),总计 \(n \log m\)。

更精确地,当 \(m = 1\) 时 \(\log m = 0\),因此表达式应为 \(n(\log m + 1)\) 以正确处理基础情况。

对于“图示法”(proof by picture),考虑下图示意:灰色框代表每层递归中要求解的子矩阵,蓝线代表每层递归中扫描的行。可以看到每层递归中蓝线的总长度为 \(O(n)\)。

另一种分析方法是通过写出并求解递推关系(尽管这不是得分必需)。设 \(T(m, n)\) 为求解 \(m \times n\) 数组所需的步数。找到第 \(m/2\) 行的最小值耗时 \(n\);若该行最小值位于第 \(j\) 列,则递归规模至多为 \(m/2 \times j\) 与 \(m/2 \times (n - j)\)。于是可写出递推式:

\[ T(m, n) \le T(m/2, j) + T(m/2, n - j) + n \]

它不像主定理中的标准形式,是“二维”的。猜测 \(T(m, n) \le n(\log m + 1)\),并用对 \(m\) 的强归纳法证明。

基础情况:\(T(1, n) = n = n(\log 1 + 1)\)。

归纳步骤:

\[ \begin{aligned} T(m, n) &\le T(m/2, j) + T(m/2, n - j) + n \\ &\le j(\log(m/2) + 1) + (n - j)(\log(m/2) + 1) + n \\ &= n(\log(m/2) + 1) + n = n(\log m + 1) \end{aligned} \]

解答来源:dis02-sol.pdf · 7-8 · confidence: high

题面来源:dis02.pdf · 5 · confidence: high


一手资料