对于一个n*n的矩阵,其中只包含有0,1两种元素且,所有的0都在1之前,请找出矩阵中0最多的一行。(Given an N-by-N matrix of 0s and 1s such that in each row no 0 comes before a 1, find the row with the most 0s in O(N) time.)
初看这题,想到的算法就是每一行都设置一个计数器,记录每行的0的个数,然后找出最大值即可(暴力解法)。
算法实现:
int* find_the_longest_row_force(int **a, int n)
{
int* res = new int[2];
res[0] = res[1] = -1;
for(int i=0; i<n; i++)
{
int max = 0;
for(int j=0;j<n;j++)
{
if(a[i][j] == 0)
max++;
}
if(max > res[1] && max!=0)
{
res[1] = max;
res[0] = i+1;
}
}
if(res[0] == -1)
res[1] = -1;
return res;
}
算法中有两层循环,因此算法的时间复杂度为o(n^2)。这样的算法是否还能改进呢?呵呵,当然可以。
首先由于矩阵中只含有0和1两个元素,且0在1之前,因此每一行都是排好序的,为此每行都使用二分查找,找到每行的最后一个0所在的位置,这个位置即为每行0的长度,如此循环查找n行,即可得到最长的一行的位置以及相应的0的长度。
算法实现
int* find_the_longest_row_search(int** a, int n)
{
int* res = new int[2];
res[0] = res[1] = -1;
for(int i=0; i<n; i++)
{
int low = 0;
int high = n-1;
int temp_loc = -1;
while(low <= high)
{
int mid = (low + high)/2;
if(a[i][mid] > 0)
high = mid -1;
else
{
if(mid > temp_loc)
temp_loc = mid;
low = mid + 1;
}
}
if(temp_loc > res[1])
{
res[1] = temp_loc;
res[0] = i+1;
}
}
if(res[0] != -1)
res[1] = res[1]+1;
return res;
}
上述算法实现的过程中,有一个循环,循环里面运行的是二分查找,时间复杂度为O(logn),总共循环n次,因此总的算法的时间复杂度为O(nlogn)。与暴力算法相比,有了一定的提高。那这是不是最优的算法呢?
其实是有的,仔细想想,我们对每一行都进行二分查找似乎是多余的,比如说第一行的0的长度为10,那么我只要看第二行在10的位置是否为0,如果不为0,则这一行的0的个数就小于第一个行,如果大于0,那就继续在这行往后查找0。由此可得一个新的算法。
首先从左上角的第一个元素开始查看,如果为0,则向右查看,否则向下查看,如此循环,直到到达矩阵的边界为止。
算法实现:
int* find_the_longest_row_0(int** a, int n)
{
int* res = new int[2];
res[0]=-1;
res[1] =-1;
int i=0,j=0;
while (i<n && j<n)
{
if(a[i][j] == 0)
{
j++;
res[0] = i+1;
}
else
{
i++;
}
}
if(res[0] != -1)
res[1] = j;
return res;
}
上述算法实现的过程中,从矩阵的左上角已知查询到矩阵的右下角,中途只能往右或者往下查询,因此最多查询2n次即可找到含有0元素最多的行。为此算法的时间复杂度为O(n)。
测试代码:
#include <iostream>
#include<stdlib.h>
#include <time.h>
#include <string>
using namespace std;
int* find_the_longest_row_force(int **a, int n);
int* find_the_longest_row_search(int** a, int n);
int** Create_matrix(const int n);
void print(int** a, const int n);
int* find_the_longest_row_0(int** a, int n);
void main()
{
int n;
cout<<"Input the n: (exit 0)";
cin>>n;
while(n>0)
{
int** a = Create_matrix(n);
print(a,n);
const string name[3] = {"Force","Search","Best"};
int* (*(p[3]))(int**,int) = {find_the_longest_row_force,find_the_longest_row_search,find_the_longest_row_0};
int* res;
for(int i=0; i<3; i++)
{
res = p[i](a,n);
cout<<name[i]<<": "<<endl;
cout<<"The longest row is :"<<res[0]<<endl
<<"The length is :"<<res[1]<<endl;
}
delete[] res;
for(int i=0; i<n; i++)
delete[] a[i];
cout<<"Input the n: (exit 0)";
cin>>n;
}
}
辅助函数
int** Create_matrix(const int n)
{
int** a = new int*[n];
for(int i=0; i<n; i++)
{
a[i] = new int[n];
srand(i);
int index = rand()%n;
for(int j=0; j<n; j++)
{
if(j<index)
a[i][j] = 0;
else
a[i][j] = 1;
}
}
return a;
}
void print(int** a, const int n)
{
for(int i=0; i<n; i++)
{
for(int j=0; j<n; j++)
cout<<a[i][j]<<" ";
cout<<endl;
}
}
上述算法优化的过程中发现,在设计一个问题的解题算法时,可以先设计出一个暴力算法,然后再暴力算法的基础上看是否存在可以改进的地方(本题主要的改进就是0这个元素的位置查询方式)。对本题而言,主要耗时就在0的位置的查询,第一中方法是挨个挨行的查询,其中存在较多的冗余,第二种方法减少了没有必要的查询,使用了二分查找,虽说在每行查询的时间减少了,但是由于没有充分利用上一次查询的信息,造成了一定的冗余查询。最后一种方法充分利用了上一行查询的信息,使得查询的效率大幅度提高。