Leecode初级算法C++题解(排序和搜索)

5 篇文章 1 订阅
1 篇文章 0 订阅

排序和搜索

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),稳定排序,非比较排序,特殊的桶排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值