算法第2章实践报告
该问题是:给你两个n规模的非降序数组,找两个数组合并后的数组的中位数,要求算法时间复杂度为O(logn)
这道题我原本的想法是用两个数组合并排序然后直接取值,但是如果这样的话,时间复杂度是O(nlogn),超出了题目所限制的时间复杂度。
于是我就只能使用分治法(二分搜索),对两个数组分别求解中位数:
①如果两个数组的中位数相等,那么中位数即为该数。
②如果当第一个数组的中位数大于第二个的时候,那么最终的中位数一定在第一个中位数之前或是第二个中位数之后,所以可以只取这两部分,再继续进行二分搜索比较
③如果当第一个数组的中位数小于第二个的时候,那么最终的中位数一定在第一个中位数之后或是第二个中位数之前,所以可以只取这两部分,再继续进行二分搜索比较
(即每次比较两个数组的中位数,舍去较小的前部分以及较大的后部分,舍去的数量要相同)
④不停重复上述二分搜索比较的过程,直到两个数组中只含有一个元素时候为止,较小者即为所求的中位数。
具体例子如图:
ps:因为每次舍去的数量要相同,所以要注意分类讨论数组的长度是奇数还是偶数。
设数组a[left,…,right], mid=(left+right)/2,
若留前半部分(即舍后半部分),则为a[left,…,mid],
在留后半部分(即舍前半部分)的时侯,要注意区分数组a中的元素个数为奇数还是偶数,
若为奇数(即(left+right)%2==0),则后半部分为a[mid,…,right];若为偶数(即(left+right)%2==1),则后半部分为a[mid+1,…,right]
代码实现
1 #include<iostream> 2 using namespace std; 3 int n,a[100000],b[100000]; 4 int getAns(int a[],int b[],int n) { 5 int left1 = 0, left2 = 0, right1 = n - 1, right2 = n - 1; 6 while(left1 < right1 && left2 < right2) { 7 int mid1 = (left1 + right1) / 2; 8 int mid2 = (left2 + right2) / 2; 9 if (a[mid1] == b[mid2]) 10 return a[mid1]; 11 if (a[mid1] < b[mid2]) { 12 if ((left1 + right1) % 2 == 0) { 13 left1 = mid1; 14 right2 = mid2; 15 } 16 else { 17 left1 = mid1 + 1; 18 right2 = mid2 ; 19 } 20 } 21 else { 22 if ((left1 + right1) % 2 == 0) { 23 right1 = mid1; 24 left2 = mid2; 25 } 26 else { 27 right1 = mid1; 28 left2 = mid2 +1; 29 } 30 } 31 }; 32 if (a[left1] < b[left2]) 33 cout << a[left1] << endl; 34 else 35 cout << b[left2] << endl; 36 } 37 38 int main() { 39 cin >> n; 40 for (int i = 0; i < n; i++) cin >> a[i]; 41 for (int i = 0; i < n; i++) cin >> b[i]; 42 getAns(a, b, n); 43 return 0; 44 }
算法时间复杂度分析:
对于含有n个元素的非降序数组a和b,设调用getAns(a,b,n)求中位数的执行时间为T(n)则有以下递推式:
当n=1时,T(n)=1
当n>1时,T(n)=2T(n/2)+O(1)
2T(n/2)=O(log22)(即以2为底2的对数)=O(1)
因为2T(n/2)=O(1),所以最后结果为O(1*logn)
得出时间复杂度T(n)=O(logn)
算法空间复杂度分析:
用了若干个临时变量与两个数组,没有使用其他的辅助空间,
所以空间复杂度为O(1)
这是第一次结对编程,有时确实看队友打代码看得不够认真,一定一定下次注意。
这次实践让我更加了解分治法的具体妙用——二分搜索。
虽然二分搜索看起来思路挺简单挺单一的,但是在执行过程中,总会有一些需要按具体情况具体分析的细节,如:
① 边界点的等号是否取到,中间点的位置是否可取
② 如何处理二分后的部分
诸如此类的细节问题,会经常注意不到,然后在提交后发现结果只是部分正确,只能又开始逐一排查这些细节问题。
另外有时思路想到之后,却发现很难用代码实现,因为有些细节问题处理不好,比如在处理上题因数组长度奇偶性不同而二分边界条件不同的这个细节,就让人头疼很久。
分治法其实不仅仅只是二分,除此之外,还有三分,尺取法等。但它最核心的思想还是,分而治之:
具体步骤为:
(1)找出结束的条件,这种条件必须尽可能简单
(2)不断将问题分解(缩小规模),直到符合结束的条件。
(3)按原问题的要求,判断子问题的解是否就是原问题的解,或是需要将子问题的解逐层合并构成原问题的解。
分治法的设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之(即分,解,合)。