概念
分治
归并排序中最重要的环节:如何将两个有序数组合并为一个有序数组
算法思想:
左边处理一下,得到左边的信息
右边处理一下,得到右边的信息
最后再处理,横跨左右两边的信息
二路归并
void merge_sort(int *arr, int l, int r){
if(l >= r) return;
int mid = (l + r)>>1;
merge_sort(arr, l, mid); // left sort
merge_sort(arr, mid + 1, r); // right sort
int *temp = (int *)malloc(sizeof(int) *(r - l + 1));
int k = 0, p1 = 1, p2 = mid + 1;
while(p1 <= mid || p2 <= r){
if((p2 > r) || (p1 <= mid && arr[p1] <= arr[p2])){
temp[k++] = arr[p1++];
}else {
temp[k++] = arr[p2++];
}
}
for(int i = l; i <= r; i++) arr[i] = temp[i - l];
free(temp);
return;
}
多路归并
归并排序在大数据场景下的应用
问题:电脑内存大小2GB 如何对40GB的文件进行排序?
temp只需要执行向末尾添加数据,所以额外的存储区不仅可以存在内存中,还可以存在外部存储区(如硬盘等)中
40GB的文件分成20分2GB的小数据进行归并排序,这时候就转化为了将20份有序的小文件合并成一个有序的大文件
这就是外部排序和内部排序的区别,如快速排序就必须在内存中执行,但归并排序的额外存储区可以使用外存
经典面试题 - 归并排序基础
剑指 Offer 51. 数组中的逆序对
- 首先求的左边区间的逆序对
- 求的右边区间的逆序对
- 求的横跨左右两边的逆序数(在归并排序的过程中,如果把a这个数字放入结果数列中,左边区间还剩k个元素,那么证明有k个元素大于a,证明有k个逆序对,所以每次右边区间放入结果的时候,将结果进行累加,可以得到所有横跨左右两边的逆序对)
class Solution {
public:
vector<int> temp;
int countResult(vector<int> &nums, int l, int r){
if(l >= r) return 0;
int ans = 0, mid = (l + r) >> 1;
ans += countResult(nums, l, mid);
ans += countResult(nums, mid + 1, r);
int k = l, p1 = l, p2 = mid + 1;
while(p1 <= mid || p2 <= r){
if((p2 > r) || (p1 <= mid && nums[p1] <= nums[p2])){
temp[k++] = nums[p1++];
}else{
temp[k++] = nums[p2++];
ans += (mid - p1 + 1);
}
}
for(int i = l; i <= r; i++) nums[i] = temp[i];
return ans;
}
int reversePairs(vector<int>& nums) {
while(temp.size() < nums.size()) temp.push_back(0);
return countResult(nums, 0, nums.size() - 1);
}
};
23. 合并 K 个升序链表
class Solution {
public:
struct CMP {
bool operator()(ListNode *p, ListNode *q) {
return p->val > q->val;
}
};
ListNode* mergeKLists(vector<ListNode*>& lists) {
priority_queue<ListNode *, vector<ListNode *>, CMP> q;
for(auto x: lists){
if(x == nullptr) continue;
q.push(x);
}
ListNode ret, *p = &ret;
while(!q.empty()){
ListNode *cur = q.top();
q.pop();
p -> next = cur;
p = cur;
if(cur -> next) q.push(cur -> next);
}
return ret.next;
}
};
148. 排序链表
class Solution {
public:
ListNode* mergeSort(ListNode* head, int n){
if(head == nullptr || head->next == nullptr) return head;
int l = n / 2, r = n - l;
ListNode *lp = head, * rp = lp, *p;
for(int i = 1; i < l; i++, rp = rp->next);
p = rp, rp = rp -> next;
p->next = nullptr;
lp = mergeSort(lp, l); // left sort
rp = mergeSort(rp, r); // right sort
ListNode ret;
p = &ret;
while(lp || rp){
if((rp == nullptr) || (lp && lp->val <= rp->val)){
p->next = lp;
lp = lp->next;
p = p->next;
}else{
p->next = rp;
rp = rp->next;
p = p->next;
}
}
return ret.next;
}
ListNode* sortList(ListNode* head) {
int n = 0;
ListNode *p = head;
while(p) p = p -> next, n += 1;
return mergeSort(head, n);
}
};
1305. 两棵二叉搜索树中的所有元素
class Solution {
public:
void getResult(TreeNode *root, vector<int> &arr){
if(root == nullptr) return;
getResult(root->left, arr);
arr.push_back(root->val);
getResult(root->right, arr);
return ;
}
vector<int> getAllElements(TreeNode* root1, TreeNode* root2) {
vector<int> lnums, rnums;
getResult(root1, lnums);
getResult(root2, rnums);
vector<int> ret;
int p1 = 0, p2 = 0;
while(p1 < lnums.size() || p2 < rnums.size()){
if((p2 >= rnums.size()) || p1 < lnums.size() && lnums[p1] < rnums[p2]){
ret.push_back(lnums[p1++]);
}else{
ret.push_back(rnums[p2++]);
}
}
return ret;
}
};
经典面试题 - 归并排序进阶
327.区间和的个数
原序列的区间和的值等于前缀和数组两项相减的结果
问题转化为 lower <= sum[j] - sum[i] <= upper (i <j)
sum[j] - lower >= sum[i] >= sum[j] - upper
b => sum[i] >= a
class Solution {
public:
int countTwoPart(vector<long long>&sum, int l1, int r1, int l2, int r2, int lower, int upper){
int ans = 0, k1 = l1 , k2 = l1;
for(int j = l2; j <= r2; j++){
long long a = sum[j] - upper;
long long b = sum[j] - lower;
while(k1 <= r1 && sum[k1] < a) k1 += 1;
while(k2 <= r1 && sum[k2] <= b) k2 += 1;
ans += k2 - k1;
}
return ans;
}
int mergeSort(vector<long long> &sum, int l, int r, int lower, int upper){
if(l >= r) return 0;
int mid = (l + r) >> 1, ans = 0;
ans += mergeSort(sum, l, mid, lower, upper);
ans += mergeSort(sum, mid + 1, r, lower, upper);
ans += countTwoPart(sum, l ,mid, mid + 1, r, lower, upper);
int k = l, p1 = l, p2 = mid + 1;
while(p1 <= mid || p2 <= r){
if((p2 > r) || (p1 <= mid && sum[p1] <= sum[p2])){
temp[k++] = sum[p1++];
}else{
temp[k++] = sum[p2++];
}
}
for(int i = l; i <= r; i++) sum[i] = temp[i];
return ans;
}
vector<long long> temp;
int countRangeSum(vector<int>& nums, int lower, int upper) {
vector<long long> sum(nums.size() + 1);
while(temp.size() < sum.size()) temp.push_back(0);
sum[0] = 0;
for(int i = 0; i < nums.size(); i++) sum[i + 1] = sum[i] + nums[i];
return mergeSort(sum, 0, sum.size() - 1, lower, upper);
}
};
315. 计算右侧小于当前元素的个数
class Solution {
public:
struct Data {
Data(int val, int ind) : val(val), ind(ind), cnt(0) {}
bool operator>(const Data &a){
return val > a.val;
}
int val, ind, cnt;
};
void mergeSort(vector<Data> &arr, int l ,int r){
if(l >= r) return;
int mid = (l + r) >> 1;
mergeSort(arr, l ,mid);
mergeSort(arr, mid + 1, r);
int k = l, p1 = l, p2 = mid + 1;
while(p1 <= mid || p2 <=r){
if((p2 > r) || (p1 <= mid && arr[p1] > arr[p2])){
arr[p1].cnt += (r - p2 + 1);
temp[k++] = arr[p1++];
}else{
temp[k++] = arr[p2++];
}
}
for(int i = l;i <= r; i++) arr[i] = temp[i];
return;
}
vector<Data> temp;
vector<int> countSmaller(vector<int>& nums) {
vector<Data> arr;
for(int i = 0; i < nums.size(); i++) arr.push_back(Data{nums[i], i});
while(temp.size() < arr.size()) temp.push_back(Data{0,0});
mergeSort(arr, 0, arr.size() - 1);
vector<int> ret(nums.size());
for(auto x: arr) ret[x.ind] = x.cnt;
return ret;
}
};
1508. 子数组和排序后的区间和
class Solution {
public:
struct Data {
Data(int i, int j, long long sum) : i(i), j(j), sum(sum) {}
int i, j, sum;
};
struct CMP{
bool operator()(const Data &a, const Data &b){
return a.sum > b.sum;
}
};
int rangeSum(vector<int>& nums, int n, int left, int right) {
priority_queue<Data, vector<Data>, CMP> q;
for(int i = 0; i < n; i++){
q.push(Data{i, i, nums[i]});
}
int ans = 0, mod = 1e9 + 7;
for(int i = 1;i <= right; i++){
Data d = q.top();
q.pop();
if(i >= left) ans = (ans + d.sum) % mod;
if(d.j + 1 < n) q.push(Data{d.i, d.j + 1, (d.sum + nums[d.j + 1]) % mod });
}
return ans;
}
};
面试题 04.08. 首个共同祖先
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == nullptr) return nullptr;
if(root == p || root == q) return root;
TreeNode *l = lowestCommonAncestor(root->left, p, q);
TreeNode *r = lowestCommonAncestor(root->right, p, q);
if(l != nullptr && r != nullptr) return root;
if(l != nullptr) return l;
return r;
}
};
1302. 层数最深叶子节点的和
class Solution {
public:
void getResult(TreeNode *root, int k, int &max_k, int &ans){
if(root == nullptr) return;
if(k == max_k) ans += root->val;
if(k > max_k){
max_k = k;
ans = root->val;
}
getResult(root->left, k + 1, max_k, ans);
getResult(root->right, k + 1, max_k, ans);
return;
}
int deepestLeavesSum(TreeNode* root) {
int max_k = 0, ans = 0;
getResult(root, 1, max_k, ans);
return ans;
}
};