结合《剑指offer(第二版)》面试题51来谈谈归并排序

一.题目大意

  给定一个数组A,对于数组A中的两个数字,如果排在前面的一个数字大于(必须大于,等于不算)后面的数字,则这两个数字组成一个逆序对。要求输出数组A中的逆序对的总数。例如,对于数组{7,5,6,4},一共存在5个逆序对,分别是(7,5)、(7,6)、(7,4)、(5,4)、(6,4)。

注:根据题意可知,必须根据原数组中元素的相对顺序来统计,给定的数组时怎样,那就按照怎样的顺序。

二.思路分析

  方法1:暴力破解。双重循环来判断出所有的逆序对数,时间复杂度为O(N^2),空间复杂度为O(1)。数据量大的话肯定超时。

  方法2:利用归并排序的思想,具体思路介绍参考《剑指offer》。由于这里参考了归并排序的思想,所以此处先讲一下经典的归并排序,然后在此基础上再给出本题目的实现。

1. 2-路归并排序的实现:

关于归并排序的思想,此处就不多说了(书上或者网上资料一大把),这里只给出具体的实现,算作是一个模板吧,代码如下:

#include<iostream>
#include<unordered_map>
#include<queue>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<sstream>
using namespace std;
#define MAX_LEN 1000
int temp[MAX_LEN] = {0};
void Merge(int a[],int low, int middle,int high)
{
    int i = 0 , j = 0, k = 0;
    for(k = low; k <= high; k++)
        temp[k] = a[k];
    for(i = low, j = middle + 1, k = i; i <= middle && j <= high; k++)//注意i和j的终止条件
    {
        if(temp[i] <= temp[j])
            a[k] = temp[i++];
        else
            a[k] = temp[j++];

    }
    while(i <= middle)
        a[k++] = temp[i++];
    while(j <= high)
        a[k++] = temp[j++];

}
void MergeSort(int a[], int low, int high)
{
    if(a == nullptr || low < 0 || high <= 0)//特殊输入和边界条件的判断与处理
        return;
    if(low < high)
    {
        int middle = (high - low) / 2 + low; //这样求中位数能够防止溢出
        MergeSort(a,low,middle); //将数组进行拆分
        MergeSort(a,middle + 1,high);
        Merge(a,low,middle,high); //将拆分的数组进行归并

    }
}

int main()
{
    int a[] = {2,1,3,4,6,5};
    MergeSort(a,0,5);
    for(int i = 0 ;i < 6; i++)
        cout<<a[i];
    cout<<endl;
}

运行结果如下:

其中,有几个需要注意的点:

(1).temp数组是用于辅助数组a进行排序的,这个数组的定义最好写在Merge函数之外(因为程序会多次调用Merge函数,如果每次都定义在Merge函数内的话,有可能会造成内存溢出),定义在MergeSort函数之中(这时需要把它作为参数传进Merge函数)或者作为全局变量都是可以的。

(2).low和high是指的数组的下标索引,2-路归并的话,就是将数组分成[low,middle]和[middle + 1,high]两个部分。

(3).Merge函数中的最后两个while循环,是为了处理两个数组长度不相同的情况,剩下多出的部分直接赋值到数组a的剩下部分即可。

2.本题的实现

  根据以上的2-路归并的实现,我们很容易能得到本题解,代码如下:

#include<iostream>
#include<unordered_map>
#include<queue>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<sstream>
using namespace std;
#define MAX_LEN 1000
int temp[MAX_LEN] = {0};
static int cnt = 0;
void Merge(int a[],int low, int middle,int high)
{
    int i = 0 , j = 0, k = 0;
    for(k = low; k <= high; k++)
        temp[k] = a[k];
    for(i = low, j = middle + 1, k = i; i <= middle && j <= high; k++)
    {
        if(temp[i] <= temp[j])
            a[k] = temp[i++];
        else
        {
            a[k] = temp[j++];
            cnt = (cnt + middle - i + 1) % 1000000007;
        }

    }
    while(i <= middle)
        a[k++] = temp[i++];
    while(j <= high)
        a[k++] = temp[j++];

}
void MergeSort(int a[], int low, int high)
{
    if(a == nullptr || low < 0 || high <= 0)
        return;
    if(low < high)
    {
        int middle = (high - low) / 2 + low;
        MergeSort(a,low,middle);
        MergeSort(a,middle + 1,high);
        Merge(a,low,middle,high);

    }
}

int main()
{
    int a[] = {2,1,3,4,6,5};
    MergeSort(a,0,5);
    for(int i = 0 ;i < 6; i++)
        cout<<a[i];
    cout<<endl;
    cout<<cnt<<endl;
}

实际上就是在2-路归并排序的基础上增加了一步:cnt = (cnt + mid - i + 1) % 1000000007

相当于在归并的过程中就完成了逆序对数的统计,此处由于按照的是牛客上练习题的要求,所以cnt在计算过程中%了1000000007,但是需要注意的点是:

(1)此处用的是

cnt = (cnt + mid - i + 1) % 1000000007

而不是

cnt += (mid - i + 1) % 1000000007

要意识到这两种写法的实现过程是不同的,前者是把cnt整体取余,往往不会发生溢出;而后者只是对增加的部分取余,可能会发生溢出的。

(2)那么,此处为什么是mid - i + 1呢?这是因为在归并的过程中,实际统计的是不同数组之间的(或者说是同一数组的不同部分之间的)逆序对数。(而关于数组的内部的逆序对数,是该问题的子问题;只要解出了不同数组之间的逆序对数,就能够解出数组内部的逆序对数,更详细的介绍见《剑指offer》);而2-路归并排序,在对这两个数组刚开始进行归并时,这两个数组就已经是有序(此处是升序排序)的了,所以说如果temp[i]>temp[j]的话,说明从位置i到位置middle之间的所有元素都是大于temp[j]的了,所以说总数和增加了middle - i +1个。

(3)除此之外,还有一个需要注意的点,就是如果数组中存在相等的元素,也是按照小于的情况处理的,所以当temp[i] <= temp[j]时并不算作逆序对。

该方法的时间复杂度为O(N*logN),空间复杂度为O(N),与方法1相比,也属于一种空间换时间的策略。

原文地址:https://www.cnblogs.com/wangkundentisy/p/9042717.html

时间: 2024-10-10 13:44:24

结合《剑指offer(第二版)》面试题51来谈谈归并排序的相关文章

剑指offer第二版-9.用两个栈实现队列

描述:使用两个栈实现一个队列.队列中实现尾部插入和头部删除函数. 思路:stack1负责插入,stack2负责弹出,如果stack2为空了,将stack1的元素依次弹出并存放到stack2中,之后对stack2进行弹出操作. 考点:对栈和队列的理解:对泛型的使用等. /** * Copyright(C) 2019 Hangzhou Differsoft Co., Ltd. All rights reserved. * */ package com.java.offer; import java.

剑指offer第二版面试题11:旋转数组的最小数字(JAVA版)

题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转.输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素.例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1. 解题思路: 1.暴力解法,从头到尾遍历一次,我们就能找出最小的元素,复杂度为O(n),但是没有利用输入的旋转数组的特性,肯定达不到面试官的要求. 2.二分查找,这里的数组可以看成两个有序的子数组,二分查找对有序数组非常有效,复杂度为O(logn). 具体分析: 1.先分析数组的特点

剑指offer第二版面试题7:二叉树的下一个节点(JAVA版本)

题目:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回.注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针. 分析: 根据中序遍历的特点,要找到一个节点的下一个节点无非就是三种情况: 1.有右子树,这时只需要把其右孩子作为下一个遍历的(并不是要找的)节点,然后沿着该节点的左子树(如果有的话)出发,直到遇到叶子节点,那么该叶子节点就是其下一个要找的节点: 2.没有右子树,则判断该节点是否是其父节点的左孩子,如果是则其下一个要找的节点是其父节点: 3.如果不是其父节点

剑指Offer第二十一题 栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序.假设压入栈的所有数字均不相等.例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列.(注意:这两个序列的长度是相等的) 思路:压栈操作次数固定,用变量记录压栈次数,同时当栈顶元素与弹出序列指针所指位置相同时,执行出栈操作. 代码实现如下: 1 import java.util.*; 2 3 public class

剑指offer—第二章算法之快速排序

算法:排序和查找(二分查找,归并排序,快速排序),位运算等. 查找:顺序查找,哈希查找,二叉排序树查找,哈希表. 二分查找可以解决:"旋转数组中的最小数字","数字在排序数组出现的次数"等问题. 哈希表:优点:O(1)时间内查找一个元素的效率最高. 缺点:需要额外的空间来实现哈希表. 二叉排序树查找:对应二查搜索树查找,可以解决:"二叉收索树的后续遍历","二查搜索树和双向链表" 排序:排序算法要熟练,额外空间消耗,平均时间复

剑指offer第二天

18.二叉树的镜像 操作给定的二叉树,将其变换为源二叉树的镜像. /** public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */ public class Solution { public void Mirror(TreeNode root) { if(root != null){ Tre

剑指offer python版 二维数组的查找

def find_integer(matrix, num): """ :param matrix: [[]] :param num: int :return: bool """ if not matrix: return False rows, cols = len(matrix), len(matrix[0]) row, col = rows - 1, 0 while row >= 0 and col <= cols - 1: if

剑指offer python版 实现单例模式

class singleTon(object): def __init__(self,x): self.val=x single2=singleTon(2) a=single2 b=single2 print(a==b) print(a.val) a.val=334 print(b.val) 原文地址:https://www.cnblogs.com/xzm123/p/9847787.html

剑指offer python版 替换空格

print('dd dd add'.replace(' ','dd')) 原文地址:https://www.cnblogs.com/xzm123/p/9847854.html