问题描述
算法思想与步骤
- 如果可以实现初始矩阵转化为目标矩阵,那么初始矩阵中一定存在一列是目标矩阵中的第一列通过 列变换+行翻转 而来的;为了能够得到最小交换次数,则需要把所有列依次作为第一列进行遍历,然后进行下一步操作
- 第一列已经归位,所以目前需要做的是:让其他所有列都能变为目标矩阵中的对应列。则从目标矩阵第二列开始遍历,设遍历的为第i列。
- 每次遍历过程中都需要遍历初始矩阵的列(从目标矩阵当前的i列开始遍历,之前列的已经与目标矩阵一致)。如果没有找到与第i列相同的列,则没有找到,跳出该步,返回第一步。如果找到,设该列为第k列,则需要进行交换操作(因为可能存在不止一列相同的情况,所以交换时要考虑以下情况)。
- 如果k==i,可以直接遍历下一列
- 如果k!=i,考虑交换的后续影响
- 第i列初始矩阵与目标矩阵第k列一致,且初始矩阵第k列与目标矩阵第i列相等
- 第i列初始矩阵与目标矩阵第k列一致,但初始矩阵第k列与目标矩阵第i列不相等
- 显然我们为了得到最少交换次数,我们应该选择第1种情况,如果其存在,直接交换然后跳出本次列遍历即可,如果第一种情况不存在,再选择第二种情况。(贪心算法,用两个标志实现,代码中的flag与found)
- 用一个数据来存储当前最小次数,如果执行完了第二步,需要把结果与当前最小次数进行比较,更新最小值。
复杂度分析
- 时间复杂度
分析一个金币阵列,有m行n列。
第1步:提供了一个大循环,需要n次,需要时间O(n)。翻转所有的行需要时间O(m)。
第2步:需要遍历后序每一列,需要时间O(n)
第3步:需要找到匹配的每一列,每次判断需要O(m),共n次判断,并且有n列,总复杂度O(mn²)
第5步:为O(1)
总时间复杂度为O(n(m+(mn²)))=O(mn+mn³) = O(mn³) - 空间复杂度
存在中间矩阵,复杂空间复杂度为O(m*n)
疑问剖析
1、这个算法是否遍历了所有交换情况?
通过算法步骤可以发现,我们只是把可能无序的变换(且不会有循环的变换),转化为了有序的变换。由于题目最小次数的要求,这样求得到的一定是最小的。
2、如果不考虑交换后两列是否对等,会出现哪种情况
按照以下样例进行就会发现错误
初始矩阵:1 1 1 1 0 0 0 0
目的矩阵:0 1 0 1 0 1 0 1
明显交换第6列与第一列时,不用行变换,变为
0 1 1 1 0 1 0 0
0 1 0 1 0 1 0 1
进行第2、3步,到第三列时,肉眼可看,与第8列交换即可得到目标矩阵,如果不考虑交换后的结果,1会直接与第5列交换,则会多交换一次,不满足最小情况。
所以需要考虑交换的情况(即第三步的第二层分析)
代码
//复杂度O(m*n^3)
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#define size 200
int num; //输入几组数据
int row; //行数
int column; //列数
int count; //交换次数
int min;
int a[size][size]; //目标矩阵
int b[size][size]; //初始矩阵
int c[size][size]; //临时存放矩阵
int found; //初始到最终是否有交换
void trans_row(int x) // 第x行取反
{
int i;
for (i = 0;i<column; i++)
b[x][i] = b[x][i]^1; // 异或取反
count++;
}
void trans_column(int x, int y) // 交换x和y列
{
int i;
int temp;
for(i = 0; i < row; i++)
{
temp=b[i][x];
b[i][x]=b[i][y];
b[i][y]=temp;
}
if (x != y)
count++;
}
int is_same(int x, int y) //比较x和y列是否相同
{
int i;
for(i = 0; i <row; i++)
if (a[i][x] != b[i][y])
return 0;
return 1;
}
void copy(int a[size][size], int b[size][size]) // 拷贝数组
{
int i,j;
for (i = 0; i <row; i++)
for (j = 0; j < column; j++)
a[i][j] = b[i][j];
}
int main()
{
int i,j,k,p;
int exchgmin[size];
scanf("%d",&num);
for(i=0;i<num;i++)
{
scanf("%d",&row);
scanf("%d",&column);
for(j=0;j<row;j++)
for(k=0;k<column;k++)
scanf("%d",&a[j][k]);
for(j=0;j<row;j++)
for(k=0;k<column;k++)
scanf("%d",&b[j][k]);
copy(c,b); //保护初始矩阵b
min=row+column+1;
for(j=0;j<column;j++)
{
copy(b,c); //恢复初始数组b
count=0; //交换次数清零
trans_column(0,j); //把每一列都假设成为第一列的目标状态,穷举这column中情况
for(k=0;k<row;k++)
{ //如果行不同,则将行转换
if(a[k][0]!=b[k][0])
trans_row(k);
}
int lo[200],flag=0,tot=0;
for(k=1;k<column;k++)
{//穷举其他所有列,如果相同则交换,说明目标状态统一,否则找不到与该列相同,说明不可行
found=0;//记录是否找到相同列
tot=0;flag=0;//flag记录是否有交换后都相同的列
for(p=k;p<column;p++)
{
if(is_same(k,p))
{
found=1;
if(p==k)
{break;}
lo[tot++]=p;
if(is_same(p,k))//此步实现优先选择列一致的
{flag=1;trans_column(k,p);break;}
}
}
if(!found)
break;
else if(!flag && tot)//没有交换后两列都符合的,则与第一次发现的列交换
trans_column(k,lo[0]);
}
if(found&&count<min) //如果可行,找出最小值
min=count;
}
if(min<row+column+1) //如果交换次数比初始值小,将其保存为当前组的最小交换次数,否则不可实现交换
exchgmin[i]=min;
else exchgmin[i]=-1;
}
for(i=0;i<num;i++)
printf("%d\n",exchgmin[i]);
return 0;
}
/*
1
4 3
1 0 1
0 0 0
1 1 0
1 0 1
1 0 1
1 1 1
0 1 1
1 0 1
*/