排序和搜索
1. 合并两个有序数组
题目:给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
说明:
初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
1.1 从尾部插入
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int length=m+n;
while(n>0)
{
if(m==0)
nums1[--length]=nums2[--n];
else if(nums1[m-1]>nums2[n-1])
nums1[--length]=nums1[--m];
else
nums1[--length]=nums2[--n];
}
}
};
1.2 其他方法
可以先将数组合并后排序,也可以创建一个新的数组,保存排序后的数,再将排序的数赋值给nums1。
2.第一个错误的版本
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
示例:
给定 n = 5,并且 version = 4 是第一个错误的版本。
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。
分析:该题就是考察查找的方法,查找主要方法有顺序查找,二分查找,哈希表查找,二叉排序查找。
2.1顺序查找
class Solution {
public:
int firstBadVersion(int n) {
if(isBadVersion(1)==true)
return 1;
for(int i=1;i<n;i++)
if(isBadVersion(i)==false && isBadVersion(i+1)==true)
return i+1;
return 0;
}
};
2.2 二分查找
class Solution {
public:
int firstBadVersion(int n) {
long low=1,high=n,mid=0;
while(low<=high)
{
mid=(low+high)/2;
if(isBadVersion(mid)==false)
low=mid+1;
else
high=mid-1;
}
return low;
}
};
对于求值的查找,还可以将二分查找更新为插值查找,斐波那契查找(这三种方法只是分割点选择不同。)
题目刷完了,这里复习或者学习一下排序和查找的知识
排序知识
十大经典排序方法
这部分程序都是手打的,没有调试,可能有细节不对,但是整体思路和理解都是正确的。使用时请注意调试。
排序,分为内排序和外排序,
内排序主要有:1、冒泡排序,2、选择排序,3、插入排序(简单排序)
4、希尔排序,5、堆排序,6、归并排序,7、快速排序(改良排序)
3.1冒泡排序(时间复杂度O(n2))
算法:是一种交换排序方法,两两比较,如果反序则交换,直到没有反序为止。
最简单的排序
该方法是,两两比较把最小的数比较后依次存放,不能算标准的冒泡排序。
void bubblesort(vector<int> &num)
{
for(int i=0;i<num.size();i++)
for(int j=i+1;j<num.size();j++)
if(num[i]>num[j])
swap(num[i],num[j]);
}
标准的冒泡排序
从后面两两比较,不停的将最小的往上移,直到有序。
void bubbleSort(vector<int>&num)
{
for(int i=0;i<num.size();i++)
for(int j=num.size()-1;j>i;j--)
if(num[j]<num[j-1])
swap(num[j],num[j-1]);
}
改良版冒泡排序
因为如果在从后往前的一次内循环判断中,没有发生数字交换,那说明整体已经有序,就可以不用比较了,基于这种想法进行改良。
void bubbleSort(vector<int>&num)
{
bool flag=true;
for(int i=0;i<num.size() && flag;i++)
{
flag=false;
for(int j=num.size()-1;j>i;j--)
if(num[j]<num[j-1])
{
flag=true;
swap(num[j],num[j-1]);
}
}
}
3.2 选择排序(时间复杂度O(n2))
比较找出最小数的下标,将该数交换到最前列。
void selectSort(vector<int>&num)
{
for(int i=0;i<num.size();i++)
{
min=i;
for(int j=i+1;j<num.size();j++)
if(num[min]>num[j])
min=j;
if(min!=i)
swap(num[i],num[min]);
}
}
3.3 插入排序(时间复杂度O(n2))
插入排序的主要优势在于已经基本排好序,有几个乱序的需要排序时,效率较高。
void insertSort(vector<int> &num)
{
int i,j;
int tmp; // 存放要插入的数
for(i=1;i<num.size();i++) //每次循环默认num[0到i-1]是排好序的数
{
if(num[i]<num[i-1]) // 需要将num[i]插入
{
tmp=num[i];
for(j=i-1; num[j]>tmp && j>=0; j--)
num[j+1]=num[j];
num[j+1]=tmp;
}
}
}
3.4 希尔排序
希尔排序(Shell’s Sort)是直接插入排序算法的一种高效改进版本,又称“缩小增量排序”。希尔排序是非稳定排序算法。
排序的稳定性:不会改变相同元素的相对顺序的排序方法是稳定的。
其实该方法就是一种分组插入排序,一般的初次取序列的一半为增量,以后每次减半,直到增量为1,意思为刚开始将数组分成两半进行排序,后面分成4份,依次减半比较,最后变成一份已排好序的序列,但是这种增量的分组方式不是最好的,而且也没有最好的方式,比较公认的分组是dlta[k]=2t-k+1 -1,这个t是当时分组次数,k是总的分组次数。在这种分组下的时间复杂度为O(n3/2)。
以20个数为例,第一次增量=10,是将1和11,2和12…等等分成10组,这样下来一次循环各组都是有序的,然后增量=5,将[1, 6, 11, 16],[2, 7, 12, 17],[3,8,13,18],[4,9,14,19]等等5组循环下来也是有序的,且6和16的数值是以插入排序方式进入[1,6]分组的,增量=2,[1,3,5,7,9,11,13,15,17,19]和[2…20]插入排序,最后形成一组完整的有序列。
void shellSort(vector<int> &num)
{
int i,j,tmp;
int increment=num.size();
while(increment>1)
{
increment=increment/2+1; //分组方式可以变化
for(i=increment;i<num.size();i++)
if(num[i]<num[i-increment])
{
tmp=num[increment];
for(j=i-increment;j>=0 && tmp<num[j];j=j-increment)
num[j+increment]=num[j];
num[j+increment]=tmp;
}
}
}
3.5 堆排序(时间复杂度O(nlogn))
堆排序是选择排序的一种改良版,利用了堆这一数据结构,堆的数据结构是完全二叉树。
完全二叉树:
1)只允许最后一层有空缺结点且空缺在右边,即叶子结点只能在层次最大的两层上出现;
2)对任一结点,如果其右子树的深度为j,则其左子树的深度必为j或j+1。 即度为1的点
只有1个或0个
堆:每个节点的值都大于或者等于其左右孩子节点的值,称为大顶堆;
每个节点的值都小于或者等于其左右孩子节点的值,称为小顶堆。
完全二叉树的编号为i的节点,如果有子节点,一定是2i(左),2i+1(右)
void heapSort(vector<int> &num)
{
int n=num.size();
for(int i=n/2-1;i>=0;i--) //构建大顶堆
heapAdjust(num,i,n);
for(int i=n-1;i>0;i--) //将最大的数保存在num[i],剩余的数再构成大顶堆
{
swap(num[0],num[i]);
heapAdjust(num,0,i-1);
}
}
void heapAdjust(vector<int> &num, int st,int length)
{
int temp;
temp=num[st];
for(int i=2*st+1;i<length;i=i*2+1)
{
if(i<length-1 && num[i]<num[i+1])
i++;
if(temp>=num[i])
break;
num[st]=num[i];
st=i;
}
num[st]=temp;
}
3.6 归并排序 ( 时间复杂度(O(n)、(空间复杂度(O(n+logn) )
归并排序有两种实现方式,迭代法和递归法,递归和迭代的比较
//从l到r的排序
#include<climits>
void merge(vector<int> &num,int l,int mid,int r)
{
vector<int> lnum,rnum;
for(int i=0;i<mid-l+1;i++)
lnum.push_back(num[i+l]);
for(int i=0;i<r-mid;i++)
rnum.push_back(num[i+mid+1]);
rnum.push_back(INT_MAX);
lnum.push_back(INT_MAX);
int j=0,k=0;
for(int i=l;i<=r;i++)
{
if(lnum[j]<=rnum[k])
num[i]=lnum[j++];
else
num[i]=rnum[k++];
}
}
void Msort(vector<int> &num,int l,int r)
{
if(r==l)
return;
int mid=(r+l)/2;
Msort(num,l,mid);
Msort(num,mid+1,r);
merge(num,l,mid,r);
}
迭代法实现归并排序
#include<climits>
void Msort(vector<int>& num)
{
int k=1;
int length=num.size();
while(k<length)
{
mergePass(num,k,length);
k=2*k;
}
}
//将num中相邻长度为s的序列两两归并排序
void mergePass(vector<int> &num,int k,int n)
{
int i=0,j;
while(i<=n-2*k)//留够2k空间
{
merge(num,i,i+k-1,i+2*k-1);
i=i+2*k;
}
if(i<n-k)//如果不满足,说明末尾还有一个长度小于等于k的序列,保持不变就好
merge(num,i,i+k-1,n-1); //这里合并的是结尾时一个长度k的序列
//和一个长度不足k的序列
}
void merge(vector<int>& num,int l,int mid,int r)
{
vector<int> lnum,rnum;
for(int i=0;i<mid-l+1;i++) //对两部分已经排好序的数组进行合并
lnum.push_back(num[i+l]);
for(int i=0;i<r-mid;i++)
rnum.push_back(num[mid+i+1]);
int j=0,k=0;
rnum.push_back(INT_MAX);
lnum.push_back(INT_MAX);
for(int i=l;i<=r;i++)
{
if(lnum[j]<=rnum[k])
num[i]=lnum[j++];
else
num[i]=rnum[k++];
}
}
3.7 快速排序
快速排序是交换排序的一种。最坏情况时间复杂度为O(n2),但是平均性能很好,实际运用中是最好的。
void Qsort(vector<int> &num,int low,int high)
{
int pivot;
if(low<high)
{
pivot=partition(num,low,high);
Qsort(num,low,pivot-1);
Qsort(num,pivot+1,high);
}
}
int partition(vector<int> &num,int low,int high)
{
int pivotkey=num[low];
while(low<high)
{
while(low<high && num[high]>=pivotkey)
high--;
swap(num[low],num[high]);
while(low<high && num[low]<=pivotkey)
low++;
swap(num[low],num[high]);
}
return low;
}
如果从大到小,只要改>和<调换就好了。
这里的pivotkey的选择可以优化为三数取中,或者九数取中,交换过程也可以优化。
非比较排序:
3.8 桶排序
先扫一遍序列找出最大值max和最小值min,设桶的个数为k,然后将[min,max]区间均匀划分成k个区间,每个区间就是一个桶,将序列中的元素分配到各自的桶。然后对桶内的元素进行排序,最后将各个桶合并成有序序列。时间复杂度为
3.9计数排序
时间复杂度:O(n)
其实就是一种特殊桶排序,先扫描一遍序列查找最大最小值,开辟出max-min+1的空间数组,用哈希表统计每个元素的个数,然后输出顺序。
3.10基数排序
时间复杂度:O(n),稳定排序,非比较排序,特殊的桶排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。