排序相关面试题总结

  1. 旋转数组中的最小元素如{3,4,5,1,2},就是{1,2,3,4,5}左旋两个得到,因为数组可以分为两个递增的部分,左边大于右边,用二分法的思想,中间元素如5大于2,说明5在左边部分,最小元素在右半部分,缩小区间到{5, 1, 2}之间。时间复杂度为O(logN).
    注:如果左边元素与右边元素中间元素相同,需要另外处理。如{1,1,1,0,1}和{1,0,1,1,1}都是旋转数组,无法用二分法判断。
int  _Min(int *arr, int left, int right)
{
    if (NULL == arr || left < 0 || right < 0 || left > right)
        throw new exception("参数错误");
    for (int i = left+1; i <= right; ++i)
    {
        if (arr[left] > arr[i])
            arr[left] = arr[i];
    }
    return arr[left];
}

int Min(int *arr, int sz)
{
    if (NULL == arr || sz < 0)
        throw new exception("参数错误");
    int left = 0;
    int right = sz - 1;
    int mid = 0;
    while (arr[left] >=  arr[right])
    {
         mid = left + ((right - left) >> 1);
        if (right - left == 1)
        {
            arr[mid] = arr[right];
            break;
        }

        if (arr[left] == arr[right] &&
            arr[left] == arr[mid])
        {
            return _Min(arr, left, right);
        }
        else if (arr[mid] >= arr[right])
        {
            left = mid;
        }
        else
        {
            right = mid;
        }
    }
    return arr[mid];
}

2.数组中出现超过一半的数字
数组中超过一半的数字排序后一定位于数组的中间位置。但是全部排序耗费时间。
快速排序可以将数组分为两部分,左半部分小于等于关键值,右半部份大于等于关键值,当关键值恰好位于中间位置时,它就是要找的元素。
注:这种方法会改变数组的顺序

int partition(int *arr, int left, int right)
{
    int slow = left - 1;
    for (int fast = left; fast < right; ++fast)
    {
        if (arr[fast] < arr[right])
        {
            slow++;
            if (slow != fast)
                swap(arr[slow], arr[fast]);
        }
    }
    ++slow;
    swap(arr[slow], arr[right]);

    return slow;
}

bool invalied = false;

bool check(int *arr, int sz, int ret)
{
    int count = 0;
    for (int i = 0; i < sz; ++i)
    {
        if (arr[i] == ret)
            count++;
    }
    if (2 * count > sz)
    {
        invalied = true;
        return ret;
    }
    else return 0;
}

int MoreThanHalf(int *arr, int sz)
{
    if (NULL == arr || sz < 1)
        return 0;
    int index = partition(arr, 0, sz - 1);
    int left = 0;
    int right = sz - 1;
    int mid = (sz >> 1);
    while (index != mid)
    {
        if (index > mid)
            index = partition(arr, left, index - 1);
        else
            index = partition(arr, index + 1, right);
    }

    int ret =check (arr,sz,arr[mid]);
    return ret;

}

如果不希期忘改变顺序,可以用计数的方法,ret与下一次数相同加一,小于0更新ret。超过一半最后一定会留下。

int MoreThanHalf_2(int *arr, int sz)
{
    invalied = false;
    if (NULL == arr || sz < 1)
        return 0;
    int result = arr[0];
    int times = 1;
    for (int i = 1; i < sz; ++i)
    {
        if (times == 0)
        {
            result = arr[i];
            times = 1;
        }
        else if (arr[i] == result)
            times ++ ;
        else 
            times--;
    }
    result = check(arr, sz, result);
    return result;
}

3.数组最小的K个数
方法与上一题相同,快速排序可以将数据分为两部分,小于关键值,大于关键值得,如果关键值下标恰好是K,前面的K个数就是要找的数。
注:会改变数据的顺序

void SmallKnum(int *arr, int sz, int K)
{
    if (NULL == arr || sz < K)
        return;
    int left = 0;
    int right = sz - 1;
    int index = partition_(arr, left, right);
    while (index != K)
    {
        if (index < K)
        {
            left = index + 1;
            index = partition_(arr, left, right);
        }
        else
        {
            right = index - 1;
            index = partition_(arr, left, right);
        }
    }

    for (int i = 0; i < index; ++i)
        cout << arr[i] << " ";
    cout << endl;
}

用multiset(底层红黑树,可以报存相同的元素)/大堆, 保存K个最小值,有值小于multiset中的最大值时,更新。
这里用multiset实现:

typedef multiset<int, greater>  Set;
typedef multiset<int, greater>::iterator Setit;

void SmallKnum_(int *arr, int sz, int K)
{
    if (NULL == arr || sz < 1 || sz < K)
        return;
    Set myset;
    Setit it = myset.begin();
    for (int i = 0; i < sz; ++i)
    {

        if (myset.size() < K)
        {
            myset.insert(arr[i]);
        }
        else
        {
            Setit it  = myset.begin();
            if (arr[i] < *myset.begin())
            {
                myset.erase(it);
                myset.insert(arr[i]);
            }
        }
    }

    for (it = myset.begin() ; it != myset.end(); ++it)
        cout << *it << " ";
    cout << endl;

}

4.数组中的逆序对
用归并排序,先将数组分到最小,排序合并。

int _InverseMerge(int *arr, int *copy, int left, int right)
{
    if (left == right)
        return 0;
    int mid = (left + ((right - left) >> 1));   
    int leftPairs = _InverseMerge(arr, copy, left, mid);
    int rightPairs = _InverseMerge(arr, copy, mid+1, right);
    int end = right;
    int i = mid;
    int j = right;
    int Pairs = 0;
    while (left <= i && mid + 1 <= j)
    {
        if (arr[i] > arr[j])
        {
            Pairs += (j - mid);
            copy[right--] = arr[i];
            i--;
        }
        else
        {
            copy[right--] = arr[j];
            j--;
        }
    }
    if (left <= i) 
    {
        copy[right--] = arr[i--];
    }
    if (mid + 1 <= j)  
    {
        copy[right--] = arr[j--];
    }

    for (; left <= end; ++left)
    {
        arr[left] = copy[left];
    }
    return Pairs + leftPairs + rightPairs;
}

int InversePairs(int arr[], int sz)
{
    if (NULL == arr || sz < 2)
        return 0;

    int *copy = new int[sz];
    return _InverseMerge(arr, copy, 0, sz-1);
    delete[]copy;
}

5.数字在排序数组中出现的次数
排序好的数组可以用二分法,因为是重复数字,先找第一次出现的位置,找到K,如果K左边的数字相同,继续往左边找,直到下标为0或者左边元素不相同为止,同方法找到最后一次出现的位置。两个相减就是出现的次数。

int GetFirstK(int *arr, int sz, int K)
{
    if (NULL == arr || sz < 1)
        return -1;
    int left = 0;
    int right = sz - 1;
    while (left <= right)
    {
        int mid = left + ((right - left) >> 1);
        if (arr[mid] > K)
        {
            right = mid - 1;
        }
        else if (arr[mid] < K)
        {
            left = mid + 1;
        }
        else
        {
            if (mid == 0)
                return mid;
            else if (arr[mid - 1] != K)
                return mid;
            else
                right = mid - 1;
        }
    }

    return -1;
}

int GetLastK(int *arr, int sz, int K)
{
    if (NULL == arr || sz < 1)
        return -1;
    int left = 0;
    int right = sz - 1;
    while (left <= right)
    {
        int mid = left + ((right - left) >> 1);
        if (arr[mid] > K)
        {
            right = mid - 1;
        }
        else if (arr[mid] < K)
        {
            left = mid + 1;
        }
        else
        {
            if (mid == sz-1)
                return mid;
            else if (arr[mid + 1] != K)
                return mid;
            else
                left = mid + 1;
        }
    }

    return -1;
}

int HasMoreK(int *arr, int sz, int K)
{
    int lastK = GetLastK(arr, sz, K);
    int firstK = GetFirstK(arr, sz, K);
    if (lastK == -1 || firstK == -1)
        return 0;
    return lastK - firstK + 1;
}

6.扑克牌是否为顺子,大王可以看做任意牌
把扑克牌用数字替代,A为1,JQK为,11,12,13,大王看作0,先对数据排序,统计0的个数,在统计相差(两张牌之间差几个)的个数,看是否能用0抵消。

int partition_2(int *arr, int left, int right)
{
    int slow = left - 1;
    for (int i = left; i < right; ++i)
    {
        if (arr[i] < arr[right])
        {
            slow++;
            if (slow != i)
                swap(arr[i], arr[slow]);
        }
    }
    ++slow;
    swap(arr[slow], arr[right]);
    return slow;
}

void qSort(int *arr, int left, int right)
{
    if (left < right)
    {
        int index = partition_2(arr, left, right);
        qSort(arr, left, index - 1);
        qSort(arr, index + 1, right);
    }
}

//把大王看作0
bool IsContinue(int *arr, int sz)
{
    if (NULL == arr || sz < 1)
        return false;

    qSort(arr,0,sz-1);
    //0的数目
    int zeronums = 0;
    for (int i = 0; i < sz; ++i)
    {
        if (0 == arr[i])
            zeronums++;
        else
            break;
    }
    //第一个不为0的下标
    int small = zeronums;
    int big = small + 1;

    int difference = 0;
    while (big < sz) 
    {
        //如果相等一定不为顺子
        if (arr[big] == arr[small])
            return false;
        //计算相邻卡片多余的差
        difference += (arr[big] - arr[small] - 1);
        small++;
        big++;
    }
    //多余的差是否能用0填充
    return (difference  <= zeronums );
}

7.n个数,数的范围是0~n-1。求任意一个重复出现的数。
由于数的大小在0~n-1之间,所以可以用arr[n] = n的顺序排序,如{2, 3, 1, 0, 2, 5, 3},从第一个数开始,下标为0,值为2,不匹配,把2交换到下标为2的位置.
{1, 3, 2, 0, 2, 5, 3},不匹配,把1交换到下标为1的位置
{3, 1, 2, 0, 2, 5, 3},不匹配,把3交换到下标为3的位置
{0, 1, 2, 3, 2, 5, 3}匹配,下标为1,2,3也匹配,跳过
下标为4的值是2,交换,发现与下标为2的值相同,则找到相同元素返回。
这种方法每次查找都会把一个元素归位,所以最坏是O(n)。

bool repeatnum(int *arr, int sz)
{
    if (NULL == arr || sz < 2)
        return false;

    for (int i = 0; i < sz;++i)
    {
        while (arr[i] != i)  //不等于下标交换位置
        {
            //待交换位置上的元素与当前元素相同,找到重复元素
            if (arr[i] == arr[arr[i]])  
            {
                cout << arr[i] << endl;
                return true;
            }
            swap(arr[i], arr[arr[i]]); //移动元素位置
        }
    }
    return false;  //没找到重复元素
}
  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魏尔肖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值