我的PAT-ADVANCED代码仓:https://github.com/617076674/PAT-ADVANCED
我的Data Structures and Algorithms代码仓:https://github.com/617076674/Data-Structures-and-Algorithms
原题链接:
PAT-ADVANCED1067:https://pintia.cn/problem-sets/994805342720868352/problems/994805403651522560
Data Structures and Algorithms7-16:https://pintia.cn/problem-sets/16/problems/678
题目描述:
PAT-ADVANCED1067、Data Structures and Algorithms7-16:
题目翻译:
1067 用Swap(0, i)进行排序
给出任意一个由数字{0, 1, 2, ..., N - 1}组成的序列,很容易把它们排列成増序。但是如果你只能使用操作Swap(0, *)呢?距骨例子,为了排列{4, 0, 2, 1, 3},我们可以进行以下交换操作:
Swap(0, 1) => {4, 1, 2, 0, 3}
Swap(0, 3) => {4, 1, 2, 3, 0}
Swap(0, 4) => {0, 1, 2, 3, 4}
对于给定的N个非负整数,你需要计算出最少的交换次数。
输入格式:
每个输入文件包含一个测试用例,每个测试用例给出一个正整数N(<= 10 ^ 5),接下来跟着一串由{0, 1, 2, ..., N - 1}组成的序列。一行中的所有数字由一个空格分隔。
输出格式:
对每个测试用例,对于给定序列,你需要输出最少的交换次数。
输入样例:
10
3 5 7 2 6 4 9 0 8 1
输出样例:
9
知识点:贪心算法
思路一:递归实现(测试点1和测试点2会超时)
首先明确一个说法,元素在其本位,意思是0在0索引位置,1在1索引位置,2在2索引位置……
递归终止条件:
如果当前序列已经有序,直接返回0,我们不需要进行任何交换。
递归过程:
(1)如果当前序列的首元素是0,我们将首元素与第一个不在本位的元素进行交换后,交换次数加1,再递归调用该函数。
(2)如果当前序列的首元素不是0,我们交换两个元素的位置,一个元素是0,另一个元素是0所在索引值,交换次数加1,再递归调用该函数。
时间复杂度是O(N ^ 2)。空间复杂度是O(N)。
C++代码:
#include<iostream>
#include<algorithm>
using namespace std;
int countSwap(int* array, int N);
int main(){
int N;
scanf("%d", &N);
int array[N];
for(int i = 0; i < N; i++){
scanf("%d", &array[i]);
}
printf("%d\n", countSwap(array, N));
return 0;
}
int countSwap(int* array, int N){
bool flag = true;
for(int i = 0; i < N; i++){
if(*(array + i) != i){
flag = false;
break;
}
}
//flag = true说明array数组已经已经排好序了
if(flag){
return 0;
}
//如果array数组的第一个元素是0
if(*array == 0){
for(int i = 1; i < N; i++){
if(*(array + i) != i){
swap(*array, *(array + i));
break;
}
}
return 1 + countSwap(array, N);
}else{
for(int i = 0; i < N; i++){
if(*(array + i) == 0){
int another = -1;
for(int j = 0; j < N; j++){
if(i == *(array + j)){
another = j;
break;
}
}
swap(*(array + i), *(array + another));
break;
}
}
return 1 + countSwap(array, N);
}
}
C++解题报告:
思路二:寻找待排序序列中有多少个blocks
首先明确这里我的blocks的定义。
对于序列{4, 0, 2, 1, 3}而言,只有2在其本位。
对于0号索引,其值是4。
对于4号索引,其值是3。
对于3号索引,其值是1。
对于1号索引,其值是0。
我们说0、4、3、1号索引构成一个blocks。而2号索引在其本位,所以该序列只有1个blocks。
对于序列{3, 5, 7, 2, 6, 4, 9, 0, 8, 1}而言,只有8在其本位。
对于0号索引,其值是3。
对于3号索引,其值是2。
对于2号索引,其值是7。
对于7号索引,其值是0。
我们说0、3、2、7号索引构成一个blocks。
对于1号索引,其值是5。
对于5号索引,其值是4。
对于4号索引,其值是6。
对于6号索引,其值是9。
对于9号索引,其值是1。
我们说1、5、4、6、9号索引构成一个blocks。而8号索引在其本位,且该序列中所有的索引都归类进了一个blocks,因此该序列共有2个blocks。
(1)我们首先处理0所在的blocks,其需要交换的次数是size - 1,这里size指的是该blocks中的元素个数。在这个操作后,0在其本位。
(2)如果还有其他blocks,其交换的时候必然要把0给包含进来,也就是说需要先把0与该blocks中的任意元素进行交换,这个时操作包含了一次交换,再进行对包含了0的新blocks进行操作,这时新blocks中的元素要比原blocks中的元素多一个0,因此需要交换的次数是size + 1 - 1,因此需要进行的交换总次数就是1 + size + 1 - 1 = size + 1。
而如果一开始0就在本位,那么参照(2)中的情形进行处理即可。
因此如果0不在其本位,我们需要交换的总次数就是不在本位的元素个数再加上blocks的个数,再减2。而如果0在其本位,我们需要多额外2次交换操作。
时间复杂度是O(N)。空间复杂度是O(1)。
C++代码:
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
int N;
scanf("%d", &N);
int array[N];
int tempArray[N];
for(int i = 0; i < N; i++){
scanf("%d", &array[i]);
tempArray[i] = i;
}
bool visited[N];
fill(visited, visited + N, false);
int blocks = 0;
int result = 0;
for(int i = 0; i < N; i++){
if(array[i] == tempArray[i]){
visited[i] = true;
}else{
result++;
}
if(!visited[i]){
visited[i] = true;
int temp = i;
while(true){
int j = array[temp];
if(j == i){
break;
}
visited[j] = true;
temp = j;
}
blocks++;
}
}
result += blocks - 2;
if(array[0] == 0){
result += 2;
}
printf("%d\n", result);
return 0;
}
C++解题报告:
思路三:对思路一的改进
在思路一中,我们每次递归都需要在循环中寻找一个不在本位上的数,每次都是从头开始枚举序列中的数,判断其是否在其本位上。更合适的做法是利用每个移回本位的数在后续操作中不再移动的特点,从整体上定义一个变量k,用来保存目前序列中不在本位上的最小数(初始为1),当交换过程中出现0回归本位的情况时,总是从当前的k开始继续增大寻找不在本位上的数,这样就能保证时间复杂度是O(N)。
空间复杂度是O(1)。
C++代码:
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
int N;
scanf("%d", &N);
int array[N]; //存放各数字当前所处的位置编号
int unOrdered = 0; //unOrdered记录除0以外不在本位上的数的个数
int num;
for(int i = 0; i < N; i++){
scanf("%d", &num);
array[num] = i;
if(array[i] != i && i != 0){
unOrdered++;
}
}
int k = 1; //k存放除0以为当前不在本位上的最小数
int result = 0;
while(unOrdered > 0){
//如果0在本位上,则寻找一个当前不在本位上的数与0交换
if(array[0] == 0){
while(k < N){
if(array[k] != k){
swap(array[k], array[0]);
result++;
break;
}
k++;
}
}
//只要0不在本位,就将0所在位置的数的当前所处位置与0的位置交换
while(array[0] != 0){
swap(array[0], array[array[0]]); //将0与array[0]交换
result++; //交换次数加1
unOrdered--; //不在本位上的数的个数减1
}
}
printf("%d\n", result);
return 0;
}
C++解题报告: