Week 3
Merging with smaller auxiliary array
原题
Suppose that the subarray a [ 0 ] \mathtt{a[0]} a[0] to a [ n − 1 ] \mathtt{a[n-1]} a[n−1] is sorted and the subarray a [ n ] \mathtt{a[n]} a[n] to a [ 2 ∗ n − 1 ] \mathtt{a[2*n-1]} a[2∗n−1] is sorted. How can you merge the two subarrays so that a [ 0 ] \mathtt{a[0]} a[0] to a [ 2 ∗ n − 1 ] \mathtt{a[2*n-1]} a[2∗n−1] is sorted using an auxiliary array of length n n n(instead of 2 n 2n 2n)?
分析
视频中的归并排序,也就是传统意义中的归并排序,是将两个待归并的子序列都复制出来,因此在本题中,传统的辅助数组需要 2 N 2N 2N空间,但实际上可以只把左半部分清出来,实际上节省了一半空间
思路
整体过程和算法不变,只是将更小的左半部分复制到辅助数组即可
Counting inversions
原题
An inversion in an array a [ ] a[\,] a[] is a pair of entries a [ i ] a[i] a[i] and a [ j ] a[j] a[j] such that i < j i < j i<j but a [ i ] > a [ j ] a[i] > a[j] a[i]>a[j]. Given an array, design a linearithmic algorithm to count the number of inversions.
分析【非原创】
求出一个数组中逆序对的个数,由于限定了时间复杂度,暴力扫描必然不可行,我的直观想法就是用归并排序做,一个比较有趣的性质是,对于待归并的左右两个子序列,尽管顺序不固定,但是左右的分布是独立的,左序列我们发现一个元素,比右面的某些元素大,那么无论这个数组怎么调整,在归并前这个大的关系都不会变化,一个思路就是,在归并前,如果左侧有一个元素比右侧的一个元素大,那么左侧这个元素及其后面的所有元素都比右面这个元素大(不要拿右边小值个数算,会算重)
思路
归并排序的变体,在归并时,判断得到a[i] > a[j]
,那么逆序对个数增加mid - i + 1
(左侧子序列包括第i个值在内的全部比右侧当前元素大的值的个数)
Shuffling a linked list
原题
Given a singly-linked list containing n n n items, rearrange the items uniformly at random. Your algorithm should consume a logarithmic (or constant) amount of extra memory and run in time proportional to n log n n \log n nlogn in the worst case.
分析
还是一个归并(就目前来讲最理想的能够达到这个时间复杂度的算法只能是归并了),不过每一次合并一个元素并不是比较大小,而时比点数,这个点数均匀随机产生,就能确保加入的元素的分布是均匀随机的(我猜的,然后发现网上都是这么说的……),由于是链表,并不需要额外的辅助数组,但是需要有指针维护每一次递归的两个子序列的头节点(这里应该用递归思路,因为非递归思路会导致每一次访问指定位置的节点不是常数时间)
思路
如上分析
Nuts and bolts
原题
A disorganized carpenter has a mixed pile of n n n nuts and n n n bolts. The goal is to find the corresponding pairs of nuts and bolts. Each nut fits exactly one bolt and each bolt fits exactly one nut. By fitting a nut and a bolt together, the carpenter can see which one is bigger (but the carpenter cannot compare two nuts or two bolts directly). Design an algorithm for the problem that uses at most proportional to n log n n \log n nlogn compares (probabilistically).
分析【非原创】
这道题比较有意思,一堆螺丝和螺母,要求寻找配对。但限制是,不能直接进行螺母与螺母,螺丝与螺丝之间的比较,只有通过螺丝试螺母的方式知道二者相对大小。根据限制和时间复杂度可知,这道题应该是用快排进行改进。但是不能直接对螺母或者螺丝进行排序,因此比较大小的过程需要通过已经匹配的一对螺丝螺母分别对另一堆配件比较相对大小(想到了快排的改进和相对比大小,但是没能想到以一对配对的为基准,寻找一对配对只需要N)
思路
http://www.wisdom.weizmann.ac.il/~naor/PUZZLES/nuts_solution.html
随机取一个螺母,让其与所有的螺丝比较并找到匹配的螺丝,然后再使用这个螺丝与所有的螺母比较。两趟下来之后,所有的螺丝和螺母都有了相对的大小,后续的匹配只需要重复上述过程即可,这个过程由于使用了随机化,可以极大概率地确保时间复杂度在 n log n n \log n nlogn,可以通过 n ! n! n!种可能性推定(不好意思这个题真的有点难度大)
Selection in two sorted arrays
原题
Given two sorted arrays a [ ] a[\; ] a[] and b [ ] b[ \; ] b[], of sizes n 1 n_1 n1 and n 2 n_2 n2, respectively, design an algorithm to find the k t h k^{th} kth largest key. The order of growth of the worst case running time of your algorithm should be log n \log n logn, where n = n 1 + n 2 n = n_1 + n_2 n=n1+n2.
- Version 1: n 1 = n 2 n_1 = n_2 n1=n2 and k = n / 2 k = n/2 k=n/2
- Version 2: k = n / 2 k = n/2 k=n/2
- Version 3: no restrictions
分析
快速选择的一个变体,有利的地方在于数组已经排好序,实际思路应该是实现成二分查找。
【非原创】此外文中还提示到一个小技巧,可以用常数时间确定a[i]
是不是第k个元素然后二分查找,假设数组是降序的(升序就反过来思考一样),那么确认a[i]
是第K个最大元素,需要判断b[k-i]
比其大和b[k-i+1]
比其小(即b中有k-i个比其大的元素),方可确认,然后二分查找即可
思路
版本1:因为已经排好序,直接取两个数组的后K个,从子序列的中位数比起,半个数组半个数组的精确,实质上就是二分查找
版本2:全数组查找,即不截k个,直接整个分析(不知道第二和第三的具体区别在什么地方,特例?)
版本3:全数组查找,即不截k个,直接整个分析
Decimal dominants
原题
Given an array with n n n keys, design an algorithm to find all values that occur more than n / 10 n/10 n/10 times. The expected running time of your algorithm should be linear.
分析
一个比较蠢的办法就是遍历一遍,然后记录出现的key,计数超过指定值就输出(但是这么蠢肯定不能面试问对吧……)
【非原创】文中的办法是,用快速选择找到第 n / 10 n/10 n/10大的元素然后查其是否有 n / 10 n/10 n/10次(不太明白……)
思路【非原创】
https://stackoverflow.com/questions/50558730/how-to-find-all-values-that-occur-more-than-n-k-times-in-an-array
一种可以直接解决n/k的情况的方案