本文最后更新于 2024年10月19日 晚上
2-7
解: 原问题实际上是要计算以 n 1 , ⋯ , n d n_1,\cdots,n_d n 1 , ⋯ , n d 为根的多项式的各系数,使用分治法:
P ( x ) = ∏ i = 1 d ( x − n i ) = ∏ i = 1 ⌊ d / 2 ⌋ ( x − n i ) ∏ i = ⌊ d / 2 ⌋ + 1 d ( x − n i ) = P 1 ( x ) P 2 ( x ) \begin{equation*}
P(x)=\prod_{i=1}^{d}\left(x-n_{i}\right)=\prod_{i=1}^{\lfloor d/2\rfloor}\left(x-n_{i}\right)\prod_{i=\lfloor d/2\rfloor+1}^{d}\left(x-n_{i}\right)=P_{1}\left(x\right)P_{2}\left(x\right)
\end{equation*}
P ( x ) = i = 1 ∏ d ( x − n i ) = i = 1 ∏ ⌊ d /2 ⌋ ( x − n i ) i = ⌊ d /2 ⌋ + 1 ∏ d ( x − n i ) = P 1 ( x ) P 2 ( x )
记次数为 d d d 的多项式所需时间为 T ( d ) T(d) T ( d ) ,可得如下递推公式:
T ( d ) = { O ( 1 ) d = 1 2 T ( d / 2 ) + O ( d log d ) d > 1 \begin{equation*}
T(d)=\begin{cases}
O(1)&\quad d=1\\
2T(d/2)+O(d \log d)&\quad d>1
\end{cases}
\end{equation*}
T ( d ) = { O ( 1 ) 2 T ( d /2 ) + O ( d log d ) d = 1 d > 1
设 d = 2 t d = 2^t d = 2 t ,于是有
T ( d ) = T ( 2 t ) = 2 T ( 2 t − 1 ) + O ( 2 t log 2 t ) = ⋯ = 2 t T ( 1 ) + ∑ k = 1 t 2 t − k O ( 2 k log 2 k ) = O ( d ) + ∑ k = 1 t O ( k ⋅ 2 t ) = O ( d ) + O ( d ) ⋅ ∑ k = 1 t O ( k ) = O ( d ) + O ( d ) ⋅ O ( t 2 ) = O ( d log 2 d ) \begin{align*}
T(d) &= T(2^t) = 2T(2^{t-1}) + O(2^t \log 2^t) \\
&= \cdots = 2^t T(1) + \sum_{k = 1}^{t} 2^{t-k} O(2^k \log 2^k) \\
& = O(d) + \sum_{k = 1}^{t} O(k \cdot 2^t) = O(d) + O(d) \cdot \sum_{k = 1}^{t} O(k) \\
& = O(d) + O(d) \cdot O(t^2) = O(d \log^2 d)
\end{align*}
T ( d ) = T ( 2 t ) = 2 T ( 2 t − 1 ) + O ( 2 t log 2 t ) = ⋯ = 2 t T ( 1 ) + k = 1 ∑ t 2 t − k O ( 2 k log 2 k ) = O ( d ) + k = 1 ∑ t O ( k ⋅ 2 t ) = O ( d ) + O ( d ) ⋅ k = 1 ∑ t O ( k ) = O ( d ) + O ( d ) ⋅ O ( t 2 ) = O ( d log 2 d )
2-9
解: 如果数组存在主元素 x x x ,由于 ∣ S ( x ) ∣ > n / 2 |S(x)| > n/2 ∣ S ( x ) ∣ > n /2 ,所以 x x x
必定是数组的中位数。因此我们先找出原数组的中位数,再验证它是否是主元素即可。
寻找数组中位数的算法时间复杂度为 O ( n ) O(n) O ( n ) ,得到中位数 x 0 x_0 x 0 ;然后再遍历一遍数组,统计 x 0 x_0 x 0 在数组中出现的次数,这个操作时间复杂度也是 O ( n ) O(n) O ( n ) 。上述算法总体的时间复杂度是 O ( n ) O(n) O ( n ) ,符合题意。
2-10
O(nlogn)算法
采用分治法。
如果数组存在主元素 x x x ,由于 ∣ S ( x ) ∣ > n / 2 |S(x)| > n/2 ∣ S ( x ) ∣ > n /2 ,所以 x x x 必定是数组中出现次数最多的数(众数);并且,如果将原数组划分为规模相近的两部分,由抽屉原理,这个众数必定至少是某一部分的众数,我们称具有这种特殊性质的众数为“大众数”。设 T ( n ) T(n) T ( n ) 为找到(或否定)长度为 n n n 的数组中主元素所耗费的计算时间,H ( n ) H(n) H ( n ) 为找到长度为 n n n 的数组中“大众数”所耗费的计算时间。
对于一个长度为 n n n 的数组,为了找到其“大众数” x 0 x_0 x 0 ,我们将其划分为规模相近的两部分,分别找出两部分的“大众数” x 1 , x 2 x_1,x_2 x 1 , x 2 (可能相同也可能互异)。然后再将整个数组扫描一遍,计算出这两个“大众数”分别在整个数组中出现的次数,出现次数较多的那个即为 x 0 x_0 x 0 ,扫描过程需要花费 O ( n ) O(n) O ( n ) 的时间开销。于是有下面的递推,
H ( n ) = { O ( 1 ) n ⩽ 4 2 H ( n / 2 ) + O ( n ) n > 4 \begin{equation*}
H(n)=
\begin{cases}
O(1)&\quad n\leqslant4\\
2H(n/2)+O(n)&\quad n>4
\end{cases}
\end{equation*}
H ( n ) = { O ( 1 ) 2 H ( n /2 ) + O ( n ) n ⩽ 4 n > 4
由主定理,
H ( n ) = O ( n log n ) \begin{equation*}
H(n) = O(n \log n)
\end{equation*}
H ( n ) = O ( n log n )
由上面的论述,判断“大众数”是否为主元素,只需验证它在整个数组中出现次数是否大于 n / 2 n/2 n /2 即可,扫描一遍数组产生 O ( n ) O(n) O ( n ) 的时间开销,于是,
T ( n ) = H ( n ) + O ( n ) = O ( n log n ) \begin{equation*}
T(n) = H(n) + O(n) = O(n \log n)
\end{equation*}
T ( n ) = H ( n ) + O ( n ) = O ( n log n )
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #include <iostream> #include <vector> using namespace std;double findMode (vector<double >& nums, int left, int right) { if (left == right) return nums[left]; int mid = (left + right) / 2 ; double leftMode = findMode (nums, left, mid); double rightMode = findMode (nums, mid+1 , right); if (leftMode == rightMode) return leftMode; int leftCnt = 0 , rightCnt = 0 ; for (int i = left; i <= right; i++) { if (nums[i] == leftMode) leftCnt++; if (nums[i] == rightMode) rightCnt++; } if (leftCnt > rightCnt) return leftMode; else return rightMode; }class Solution { public : double findMainElement (vector<double >& nums) { int n = nums.size (); double res; res = findMode (nums, 0 , n-1 ); int cnt = 0 ; for (int i = 0 ; i < n; i++) if (nums[i] == res) cnt++; if (cnt > n/2 ) return res; else return INT_MAX; } };int main () { Solution s; vector<double > nums = {3 ,2 ,1 ,2 ,3 ,2 ,3 }; cout << "Main element: " << s.findMainElement (nums) << endl; return 0 ; }
O(n)算法
设数组为 A 0 [ 0 : n − 1 ] A_0 [0:n-1] A 0 [ 0 : n − 1 ] ,则对数组首尾元素进行两两配对:
A 0 [ 0 ] ↔ A 0 [ n − 1 ] , A 0 [ 1 ] ↔ A 0 [ n − 2 ] , ⋯ , A 0 [ ( ⌊ n / 2 ⌋ ) / 2 ] ↔ A 0 [ ( ⌊ n / 2 ⌋ + 1 ) / 2 ] A_0 [0] \leftrightarrow A_0 [n-1], A_0 [1] \leftrightarrow A_0 [n-2], \cdots , A_0 [(\lfloor n/2\rfloor)/2] \leftrightarrow A_0 [(\lfloor n/2\rfloor+1)/2]
A 0 [ 0 ] ↔ A 0 [ n − 1 ] , A 0 [ 1 ] ↔ A 0 [ n − 2 ] , ⋯ , A 0 [(⌊ n /2 ⌋) /2 ] ↔ A 0 [(⌊ n /2 ⌋ + 1 ) /2 ]
如果 n n n 是偶数则刚好完全配对,如果是奇数则剩下 A 0 [ ( n + 1 ) / 2 ] A_0 [(n+1)/2] A 0 [( n + 1 ) /2 ] ,它直接进入下一步。
然后对每一对二元组进行比较,判断二者是否相等:如果相等,则将其中一个写入数组 A 1 A_1 A 1 中;否则对该对不进行操作,继续检查下一对。以此类推,反复进行上述操作,直至剩下一个数,这个数即为主元素。
设 T ( n ) T(n) T ( n ) 为找到(或否定)长度为 n n n 的数组中主元素所耗费的计算时间,则
T ( n ) ≤ { O ( 1 ) n ⩽ 2 2 T ( n / 2 ) + 1 n > 2 \begin{equation*}
T(n) \leq \begin{cases}
O(1)&\quad n\leqslant2\\
2T(n/2) + 1&\quad n>2
\end{cases}
\end{equation*}
T ( n ) ≤ { O ( 1 ) 2 T ( n /2 ) + 1 n ⩽ 2 n > 2
设 n = 2 t n = 2^t n = 2 t ,于是有,
T ( n ) ≤ 2 T ( n / 2 ) + 1 ≤ 4 T ( n / 4 ) + 2 + 1 ≤ ⋯ ≤ 2 t T ( 1 ) + ∑ k = 1 t − 1 2 k = O ( 2 t ) + 2 t − 1 = O ( 2 t ) = O ( n ) \begin{align*}
T(n) &\leq 2T(n/2) + 1 \leq 4T(n/4) + 2 + 1 \\
&\leq \cdots \leq 2^t T(1) + \sum_{k = 1}^{t-1} 2^k \\
&= O(2^t) + 2^t - 1 = O(2^t) \\
&= O(n)
\end{align*}
T ( n ) ≤ 2 T ( n /2 ) + 1 ≤ 4 T ( n /4 ) + 2 + 1 ≤ ⋯ ≤ 2 t T ( 1 ) + k = 1 ∑ t − 1 2 k = O ( 2 t ) + 2 t − 1 = O ( 2 t ) = O ( n )
因此上述算法是线性时间的。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 #include <iostream> #include <vector> using namespace std;class Solution { public : double findMainElement (vector<double >& nums) { int n = nums.size (); vector<double > arr (nums) ; int cnt = n; double res; while (cnt > 1 ) { int cnt_temp = 0 ; for (int i = 0 ; i < cnt / 2 ; i++) { if (arr[i] == arr[cnt-1 -i]) { int temp = arr[i]; arr[cnt_temp] = temp; cnt_temp ++; } if (cnt % 2 != 0 ) { arr[cnt_temp] = arr[cnt/2 + 1 ]; cnt_temp ++; } } cnt = cnt_temp; } if (cnt == 1 ) res = arr[0 ]; else return INT_MAX; cnt = 0 ; for (int i = 0 ; i < n; i++) if (nums[i] == res) cnt++; if (cnt > n/2 ) return res; else return INT_MAX; } };int main () { Solution s; vector<double > nums = {1 ,3 ,1 ,2 ,2 ,1 }; cout << "Main element: " << s.findMainElement (nums) << endl; return 0 ; }
2-28
采用递归方法。
设 T ( n ) T(n) T ( n ) 为找出上述 2 n 2n 2 n 个数的中位数的时间开销。
对于问题 X [ l e f t 1 , r i g h t 1 ] , Y [ l e f t 2 , r i g h t 2 ] X[left1,right1],Y[left2,right2] X [ l e f t 1 , r i g h t 1 ] , Y [ l e f t 2 , r i g h t 2 ] ,分别计算 X、Y 两数组的中位数 x , y x,y x , y ,找出中位下标 m 1 = ⌊ ( l e f t 1 + r i g h t 1 ) / 2 ⌋ , ⌊ m 2 = ( l e f t 2 + r i g h t 2 ) / 2 ⌋ m1 = \lfloor (left1 + right1) / 2\rfloor , \lfloor m2 = (left2 + right2) / 2 \rfloor m 1 = ⌊( l e f t 1 + r i g h t 1 ) /2 ⌋ , ⌊ m 2 = ( l e f t 2 + r i g h t 2 ) /2 ⌋ 。若 x = y x = y x = y ,则返回 x x x 。
否则,不妨设 x < y x < y x < y (x > y x > y x > y 的情形同理)
y y y 左侧的元素是 X [ l e f t 1 , r i g h t 1 ] , Y [ l e f t 2 , r i g h t 2 ] X[left1,right1],Y[left2,right2] X [ l e f t 1 , r i g h t 1 ] , Y [ l e f t 2 , r i g h t 2 ] 中最大的元素,x x x 右侧的元素是 X [ l e f t 1 , r i g h t 1 ] , Y [ l e f t 2 , r i g h t 2 ] X[left1,right1],Y[left2,right2] X [ l e f t 1 , r i g h t 1 ] , Y [ l e f t 2 , r i g h t 2 ] 中最小的元素,于是将两端元素同时舍去(需要分奇偶讨论,保证删去元素数量一致)。原问题被转化为求解子问题 X [ m 1 + 1 , r i g h t 1 ] , Y [ l e f t 2 , m 2 ] X[m1+1,right1], Y[left2,m2] X [ m 1 + 1 , r i g h t 1 ] , Y [ l e f t 2 , m 2 ] (或 Y [ l e f t 2 , m 2 − 1 ] Y[left2,m2-1] Y [ l e f t 2 , m 2 − 1 ] )
上述过程中,查找了 X 、 Y X、Y X 、 Y 数组中的中位元素,时间复杂度为 O ( 1 ) O(1) O ( 1 ) ;每步将原问题规模缩小 1/2。所以
\begin{equation*}
T(2n) = T(n) + T(n) = 2T(n/2) + O(1)
\begin{cases}
O(1)&\quad n=1\\
T(n/2)+O(1)&\quad n>1
\end{cases}
\end{equation*}
令 $n = 2^t$,有
\begin{align*}
T(n) &= T(2^t) = T(2^{t-1}) + O(1) + = \cdots \\
&= T(1) + \sum_{k = 1}^{t} O(1) = O(1) + O(t)\\
&= O(\log n)
\end{align*}
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 #include <iostream> #include <vector> using namespace std;double Median (vector<double >& X, int left, int right) { int n = right - left + 1 ; if (n % 2 == 0 ) return (X[left + n/2 - 1 ] + X[left + n/2 ]) / 2.0 ; else return X[left + n/2 ]; }class Solution { public : double findMedian (vector<double >& X, vector<double >& Y, int left1, int right1, int left2, int right2) { int m1 = (left1 + right1) / 2 ; int m2 = (left2 + right2) / 2 ; int n = right1 - left1 + 1 ; if (n == 1 ) return (X[left1] + Y[left2]) / 2.0 ; double x = Median (X, left1, right1); double y = Median (Y, left2, right2); if (x == y) return x; else if (x < y) { if (n % 2 == 0 ) return findMedian (X, Y, m1+1 , right1, left2, m2); else return findMedian (X, Y, m1+1 , right1, left2, m2-1 ); } else { if (n % 2 == 0 ) return findMedian (X, Y, left1, m1, m2+1 , right2); else return findMedian (X, Y, left1, m1-1 , m2+1 , right2); } } };int main () { vector<double > X = {1 , 2 , 4 , 4 , 6 }; vector<double > Y = {2 , 2 , 3 , 5 , 5 }; int n = X.size (); Solution s; double res = s.findMedian (X, Y, 0 , n-1 , 0 , n-1 ); cout << res << endl; return 0 ; }