编程珠玑读书笔记-第8章

版权声明:本文为 DouMiaoO_Oo 原创文章,未经允许不得转载!转载请注明出处 https://blog.csdn.net/DouMiaoO_Oo/article/details/75670424

本章为了说明算法设计的重要性。这里引出一个 一维模式识别问题,也就是最大连续子串和。在最大连续子串和这个问题中,通过比较不同实现方法下的效率来说明这个问题。

相关题目可以参考 LeetCode maximum-subarray、牛客网 连续子数组的最大和 或者杭电OJ Max Sum

书中列举的5种方法

(1)枚举i, j,再累加[i, …, j]的元素,复杂度O(n^3)。

for i = [0, 1, ..., n-1]:
	for j = [i+1, ..., n-1]:
		cur = 0
		for k = [i, i+1, ..., j]:
			cur += arr[k]
		ans = max(ans, cur)

(2)枚举i,再从arr[i]开始依次累加元素直到到arr[n-1],每次累加尝试判断是不是需要更新最优解。

ans = arr[0]
for i = [0, 1, ..., n-1]:
	cur = arr[i]
	for j = [i+1, ..., n-1]:
		cur += arr[j]
		ans = max(ans, cur)

(3) sum[k]代表 arr[0, …, k]的连续元素的和(这一步时间复杂度为O(n)),然后枚举i, j 计算 sum[j] - sum[i-1] 来得到[i, j]的和,时间复杂度为O(n^2)

sum[-1] = 0
sum[0] = arr[0]
for i = [1, ..., n-1]:
	sum[i] = sum[i-1] + arr[i]
ans = arr[0]
for i = [0, ..., n-1]:
	for j = [i+1, ..., n-1]:
		ans = max(ans, sum[j]-sum[i-1])

(4) 动态规划 时间复杂度为O(n)

cur = arr[0]
ans = arr[0]
for i = [1, ..., n-1]:
	cur = max(cur+arr[i], arr[i])
	ans = max(cur, ans)

(5) 书中还提到一个分治的算法。时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
思路就是每次把数组均等分为两半,最大连续子串和要么在左半边,要么在右半边,或者在包含了中心点的连续子序列中。

int maxsum(vector<int>& arr, int l, int r){
    if(l == r) return arr[l];
    int m = l + (r-l)/2;  // 中心点
    int cur = arr[m], lmax = arr[m], rmax = arr[m];
    for(int i = m-1; i >= l; --i){
        cur += arr[i];
        lmax = max(lmax, cur);
    }
    cur = arr[m];
    for(int i = m+1; i <= r; ++i){
        cur += arr[i];
        rmax = max(rmax, cur);
    }
    int ans = lmax+rmax-arr[m];  // 包含中心点的子序列对应的最优解
    if(l <= m-1) ans = max(ans, maxsum(arr, l, m-1));  // 这里也可以maxsum(arr, l, m)
    if(m+1 <= r) ans = max(ans, maxsum(arr, m+1, r));
    return ans;
}

int FindGreatestSumOfSubArray(vector<int> arr) {
    if(arr.size() == 0) return 0;
    return maxsum(arr, 0, arr.size()-1);
}

现在我们分析一下这个方法。
(1)对于处于相同递归深度的所有函数,在判断包含中心点的最大子序列和的过程中,这些函数遍历的元素个数之和其实就是整个数组,所以对应的操作的时间复杂度为 O ( n ) O(n) O(n)。在这里举例来说,对于第一层的递归,只有一个函数遍历了整个数组的 n n n个元素;对于第二层的函数来说,一个函数遍历了左半边 n / 2 n/2 n/2个元素,另一个函数遍历了右半边 n / 2 n/2 n/2个元素,所以共计遍历了 n n n个元素;以此类推。
(2)在递归中,每次子问题的规模减半,所以递归的深度是 O ( l o g n ) O(log n) O(logn)的。

综上,该方法的时间复杂度是O(n logn),分析的方法类似于归并排序。
若用T(n)表示解决规模为n的该问题所用的时间,则该分治方法可以得到如下的递推关系:T(n) = 2T(n/2)+O(n), 且T(1) = O(1)。

课后习题

第9题

我们将负数数组的最大连续子序列的和定义为0,即空向量的和。假设我们重新定义,将最大子向量的和定义为最大元素的值,那么应该如何修改各个程序?
解答:在之前的题目要求下, 一开始我们将最大连续子序列的和(ans)定义为0,然后在程序中尝试修改它。在更新的要求下,我们把ans初始化为给定序列arr中的某个元素即可,例如初始化ans=arr[0]即可。事实上,我在上文中给出的代码就是按照这个更新版本的要求来写的。

第10题

(1)假设我们想要查找的是总和最接近0的连续子序列,你能设计出的最有效的算法是什么,可以应用哪些算法设计技术?(2)假设我们想要查找的是总和最接近 t t t 的连续子序列,结果又将怎样?
解答:这个问题需要用到之前的预处理技术,构造 s u m [ i ] sum[i] sum[i] 来表示 a r r [ 0 , . . . , i ] arr[0, ..., i] arr[0,...,i] 的和。此时要找连续子序列的和接近于 t t t,就意味着要找到 s u m [ j ] − s u m [ i − 1 ] sum[j] - sum[i-1] sum[j]sum[i1] 最接近于 t t t, 等价于arr[i, …, j]的和最接近于 t t t。我们需要先构造sum[i] (时间复杂度O(n)),再对sum[i]排序(O(nlogn)),最后还需要用双指针法找到 i i i j j j (O(n)). 注:为了在排序sum之后能得到原来序列的下标,sum[i]需要是个结构体,即保存了arr[0, …, i]的和以及当前的下标 i i i
类似题目 https://leetcode.com/problems/subarray-sum-equals-k/

(0)构造sum数组并排序

sum[0] = arr[0]
for(int i = 1; i < n; ++i){
    sum[i] = sum[i-1] + arr[i];
} sort(sum, sum+n);

(1) 当 t t t 等于0时,我们只需要判断排好序的sum数组中,所有相邻的两个元素即可,找到 i i i 使得sum[i-1]和sum[i]的差距最小。

(2) 对于一般情况下的 t > 0 t > 0 t>0,使用双指针法。

/* 这个代码没有验证过,仅写了大概思路 */
int i = 0, j = 1;
diff = sum[j] - sum[i];
while(1){
    int cur = sum[j]-sum[i];
    if(cur == t){
        diff = 0;
        break;  // 找到答案并退出
    }
    else if(cur < t){
        ++j;
    } else{  // cur > t
        ++i;
        if(i == j) {
            ++j;
        }
    } if(j == n) break;
}

在这个双指针算法中, i i i j j j 都是最多遍历数组一遍,所以这个过程的复杂度是O(n)的。

第13题

在最大子数组问题中,给定m×n的实数数组,我们需要求出矩形子数组的最大总和。该题的复杂度如何?
解答 O ( n 3 ) O(n^3) O(n3), 这是一道经典的题,思路就是构造出二维的sum[i, j] 代表从原点(0, 0) 到 (i, j)矩形区域的和。
相同题目可见 牛客网 最大和子矩阵

第14题

给定整数m和大小为n的数组x,请找出使总和x[i]+…+x[i+m]最接近0的整数i (0 ≤ i < n-m)
解答:感觉这题就是滑动窗口,窗口的大小为m。还是用预处理计算,只不过每次滑到一个新的元素时,需要加上右边的元素,减去左边的元素,维持窗口内的元素个数为m。

sum = x[0]
for(int i = 1; i < m; ++i){
    sum += x[i];
}
diff = abs(sum - t);
ans = m-1;
for(int i = m; i < n; ++i){
    sum = sum - x[i-m] + x[i];
    if(abs(sum-t) < diff) {
        diff = abs(sum-t);
        ans = i;
    }
}

第15题

当T(1) = 0且n为2的幂时,递推公式T(n) = 2T(n/2)+cn的解是什么?如果T(1) = c,结果又怎么样?
解答
(1)当T(1) = 0时,
T(2) = 2T(1)+2c = 2c = 2 l o g 2 ( 2 ) c 2log_2(2)c 2log2(2)c
T(4) = 2T(2)+4c = 8c = 4 l o g 2 ( 4 ) c 4log_2(4)c 4log2(4)c
T(8) = 2T(4)+8c = 24c = 8 l o g 2 ( 8 ) c 8log_2(8)c 8log2(8)c
T(16) = 2T(8)+16c = 64c = 16 l o g 2 ( 16 ) c 16log_2(16)c 16log2(16)c
所以此时T(n) = n l o g 2 ( n ) c nlog_2(n)c nlog2(n)c

(2) 当T(1) = c时,
我们设 n = 2 k n = 2^k n=2k, 则
T ( n ) = T ( 2 k ) = 2 T ( 2 k − 1 ) + c n T(n)= T(2^k) = 2T(2^{k-1})+cn T(n)=T(2k)=2T(2k1)+cn
= 2 { 2 T ( 2 k − 2 ) + ( c n / 2 ) } + c n = 2\{2T(2^{k-2})+(cn/2)\}+cn =2{2T(2k2)+(cn/2)}+cn
= 2 2 T ( 2 k − 2 ) + 2 c n = 2^2T(2^{k-2}) +2cn =22T(2k2)+2cn
= 2 k T ( 2 0 ) + k c n = 2^kT(2^{0}) +kcn =2kT(20)+kcn
所以此时T(n) = c n + l o g 2 ( n ) c n cn+log_2(n)cn cn+log2(n)cn
验证一下:
T(2) = 2T(1)+2c = 2c+2c = 4c
T(4) = 2T(2)+4c = 12c
T(8) = 2T(4)+8c = 32c
T(16) = 2T(8)+16c = 80c

版权声明:本文为 DouMiaoO_Oo 原创文章,未经允许不得转载!转载请注明出处 https://blog.csdn.net/DouMiaoO_Oo/article/details/75670424

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值