PAT (Advanced Level) Practice 1067 Sort with Swap(0, i) 贪心算法以及条件转换

一、概述

只交换0和其他数,将数列变为有序。

本题的难点在于当数据量较大时如何保证不超时。

我就是个弱智,想的太直了,我的算法优化到最后也还有一个测试点超时。

先说明自己的算法,再说明AC的算法。

二、分析

由于只允许交换0和其他数,因此基础算法就是交换0和0所在的数组元素的下标。但是当0在0的时候,问题就出现了,下一步不知如何交换。这时应找到最小的不在正常位置的元素,然后将0与它交换,接着继续交换,直到所有元素都在正确位置。

我从第一步开始就选错了方向。

1、我的方法

开一个数组,按输入顺序保存数列。然后遍历数列计算目前在正常位置的数字的个数sum。

如果sum等于数字数量N,那么直接输出0,不用交换;

如果sum不等于N,这有一点要注意,如果0的位置是0,那么sum要减一,因为会把在正确位置的0交换出去,如果0的位置不是0,那么就不用交换。

设置一个最小的不在正常位置的值minwrong,初始值为1。

因为当0不在0位置时,我们要寻找0所在位置的元素下标,如果从0开始遍历太浪费时间了,从minwrong开始遍历能好一点;

同时当0在0位置时,我们要寻找最小的不在正常位置的值,也就是minwrong,这样也避免了从头开始遍历,

如下:

if(sum==N)
		printf("0");
	else
	{
	if(Number[0]==0)
	sum--;
	while(sum!=N-1)
	{
		if(Number[0]!=0)
		{
			int j=minwrong;
			if(Number[0]==zero)
				j=0;
			else 
			{
				while(Number[j]!=zero)
				j++;
			}
			if(j==minwrong)
			minwrong++;
			swap(&Number[j],&Number[zero]);	
			zero=j;
			swapnum++;
			sum++;	
		}
		else
		{
			int j=minwrong;
			while(Number[j]==j)
			j++;
			minwrong=j;
			
			swap(&Number[0],&Number[j]);
			zero=j;
			swapnum++;
		}	
	}

同时注意bug,当0不在0位置时,首先要判断0位置的元素是不是要找的,然后再从minwrong开始遍历,否则会超时。

这样就计算出了总的使用次数。

代码如下:

#include<stdio.h>
#include<cstdio>
#include<string>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int Number[100010]={0};
void swap(int *a,int *b)
{
	int c=*b;
	*b=*a;
	*a=c;
}
int main()
{
	int N;
	int i;
	scanf("%d",&N);
	for(i=0;i<N;i++)
	{
		scanf("%d",&Number[i]);
	}
	int zero=0;
	while(Number[zero]!=0)
		zero++;//zero存储目前0的下标 
	int sum=0;
	for(i=0;i<N;i++)
		if(Number[i]==i)
			sum++;
	int swapnum=0;
	int minwrong=1;//最小的错误的的下标 
	if(sum==N)
		printf("0");
	else
	{
	if(Number[0]==0)
	sum--;
	while(sum!=N-1)
	{
		if(Number[0]!=0)
		{
			int j=minwrong;
			if(Number[0]==zero)
				j=0;
			else 
			{
				while(Number[j]!=zero)
				j++;
			}
			if(j==minwrong)
			minwrong++;
			swap(&Number[j],&Number[zero]);	
			zero=j;
			swapnum++;
			sum++;	
		}
		else
		{
			int j=minwrong;
			while(Number[j]==j)
			j++;
			minwrong=j;
			
			swap(&Number[0],&Number[j]);
			zero=j;
			swapnum++;
		}	
	}
	printf("%d",swapnum);	
	}
}

测试点2运行超时。

2、AC方法

首先,要对输入的数列进行处理,数组中存储的不是直观的输入顺序的元素,而是元素的位置。

举例如下:

输入的序列为3 5 7 2 6 4 9 0 8 1

我的算法的数组从头到尾的顺序为3 5 7 2 6 4 9 0 8 1

而AC方法的数组的顺序为7 9 3 0 5 1 4 2 8 6

如下:

int N;
	int i;
	scanf("%d",&N);
	for(i=0;i<N;i++)
	{
		int num;
		scanf("%d",&num);
		Number[num]=i;//设下标为m,值为n,则意义为数字m现在的位置为n 
	}

这样一来,首先减少了一大块时间,即寻找目前0的下标所在的数组的下标,直接用Number[Number[0]]表示即可。很高的提升了效率。交换的框架类似:当0不在0位置时,交换,当0在0位置时,从最小的不在原位置的元素开始找,一直找到真正的不在原位置的元素,交换,这样0又不在原位置了。这个算法的精妙之处就在于,最后最极端,搜索不在原位置的元素的次数也不超过N,而不是我的算法一样的N^2。一些注意点倒是和我的一样。

如下:

if(sum==N)
		printf("0");
	else
	{
	while(sum!=N-1)
	{
		if(Number[0]==0)
			sum--;
		while(Number[0]!=0)
		{
			//if(sum==N-1)
				//break;
			//if(minwrong==Number[0])
				//minwrong++;
			swap(&Number[0],&Number[Number[0]]);//有点难理解,eg.现在0的位置是3,3的位置是5,那么swap之后,0的位置是5,3的位置是3
			swapnum++; 
			sum++;
		}
		if(Number[0]==0)
		{
			if(sum==N-1)
				break;
			while(minwrong<N)
			{
			  if(Number[minwrong]!=minwrong)
			  {
			    swap(&Number[0],&Number[minwrong]);
			    swapnum++;
			    break;
			  }
			  minwrong++;
			}
		}
	}

之后输出即可。

三、总结

我从一开始的想法就偏了,想的太简单,应该换位思考才是。实际上我自己八成也想不到AC代码= =

PS:代码如下:

#include<stdio.h>
#include<cstdio>
#include<string>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int Number[100010]={0};
void swap(int *a,int *b)
{
	int c=*b;
	*b=*a;
	*a=c;
}
int main()
{
	int N;
	int i;
	scanf("%d",&N);
	for(i=0;i<N;i++)
	{
		int num;
		scanf("%d",&num);
		Number[num]=i;//设下标为m,值为n,则意义为数字m现在的位置为n 
	}
	int sum=0;
	for(i=0;i<N;i++)
		if(Number[i]==i)
			sum++;
	int swapnum=0;
	int minwrong=1;//最小的错误的的下标 
	if(sum==N)
		printf("0");
	else
	{
	while(sum!=N-1)
	{
		if(Number[0]==0)
			sum--;
		while(Number[0]!=0)
		{
			//if(sum==N-1)
				//break;
			//if(minwrong==Number[0])
				//minwrong++;
			swap(&Number[0],&Number[Number[0]]);//有点难理解,eg.现在0的位置是3,3的位置是5,那么swap之后,0的位置是5,3的位置是3
			swapnum++; 
			sum++;
		}
		if(Number[0]==0)
		{
			if(sum==N-1)
				break;
			while(minwrong<N)
			{
			  if(Number[minwrong]!=minwrong)
			  {
			    swap(&Number[0],&Number[minwrong]);
			    swapnum++;
			    break;
			  }
			  minwrong++;
			}
		}
	}
	printf("%d",swapnum);	
	}
	/*int N;
	int i;
	scanf("%d",&N);
	for(i=0;i<N;i++)
	{
		scanf("%d",&Number[i]);
	}
	int zero=0;
	while(Number[zero]!=0)
		zero++;//zero存储目前0的下标 
	int sum=0;
	for(i=0;i<N;i++)
		if(Number[i]==i)
			sum++;
	int swapnum=0;
	int minwrong=1;//最小的错误的的下标 
	if(sum==N)
		printf("0");
	else
	{
	if(Number[0]==0)
	sum--;
	while(sum!=N-1)
	{
		if(Number[0]!=0)
		{
			int j=minwrong;
			if(Number[0]==zero)
				j=0;
			else 
			{
				while(Number[j]!=zero)
				j++;
			}
			if(j==minwrong)
			minwrong++;
			swap(&Number[j],&Number[zero]);	
			zero=j;
			swapnum++;
			sum++;	
		}
		else
		{
			int j=minwrong;
			while(Number[j]==j)
			j++;
			minwrong=j;
			
			swap(&Number[0],&Number[j]);
			zero=j;
			swapnum++;
		}	
	}
	printf("%d",swapnum);	
	}*/
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值