问题描述
问题:有一个m*n的二维数组,从外到内按照顺时针填充,并将结果打印出来。
据个例子: 3 * 4 的数组,填充结果为
1 2 3 410 11 12 5
9 8 7 6
解题思路1
我们可以来的直接点,既然题目要求我们顺时针填充,那么我们就这样把这个数组填充满就好了。
遇到的问题是:如何找到下一个应该填充的数组的下标。
一般情况下,下标的递增都是线性单向的。例如x作为下标,循环的过程中就是x++之类的。
但是,这个问题无非是把一个线性的递增关系变成了一个区间函数,对于不同的情况,我们递增偏差不同。
那二维数组举例,对于从左到右,假设原始坐标是(x,y),那么下一个坐标是(x,y+1)。
依次来说:从上到下 (x,y) -> (x+1,y)
从右到左 (x,y) -> ( x, y - 1 )
从下到上 (x,y) -> (x-1,y)
所以,我们可以总结出这样的递增数据:
int directionMap[][2] = {
{ 0 , 1 }, // left -> right
{ 1 , 0 }, // top -> bottom
{ 0 , -1 }, // right -> left
{ -1 , 0 }, // bottom -> top
};
而且,我们可以发现,由于是顺时针填充,所以,填充的变换过程,永远按照directionMap从大到小的顺序变化。
于是,有如下的程序:
#include <iostream>
#include <iomanip>
#include <memory>
using namespace std;
namespace CyclePrint
{
// map the two dimensional array index to flat index
class IndexMap
{
public:
IndexMap( int Column )
:m_nColumn(Column) {}
inline int operator()( int X, int Y ) const
{
return X * m_nColumn + Y;
}
private:
int m_nColumn;
};
void DeleteArray( int* array )
{
if( array )
{
delete [] array;
}
}
void DoPrint( int Row , int Column )
{
if ( Row <= 0 || Column <= 0 )
return ;
// create a flat array to store the data
shared_ptr<int> matrixAutoRelease( new int[Row * Column] , DeleteArray );
int * matrix = matrixAutoRelease.get();
memset( matrix , 0 , sizeof(int) * Row * Column );
int directionMap[][2] = {
{ 0 , 1 }, // left -> right
{ 1 , 0 }, // top -> bottom
{ 0 , -1 }, // right -> left
{ -1 , 0 }, // bottom -> top
};
int currentNumber = 1;
int currentDirection = 0;
IndexMap im(Column);
int x(0) , y(0);
while( currentNumber <= Row * Column )
{
if( x >= Row || x < 0 || y >= Column || y < 0 || matrix[im(x,y)] != 0 )
{
// revert the wrong move
x -= directionMap[currentDirection][0];
y -= directionMap[currentDirection][1];
// modify direction
currentDirection = ( currentDirection + 1) % 4;
// step to the correct index
x += directionMap[currentDirection][0];
y += directionMap[currentDirection][1];
}
else
{
matrix[im(x,y)] = currentNumber++ ;
x += directionMap[currentDirection][0];
y += directionMap[currentDirection][1];
}
}
for ( int i = 0 ; i < Row ; ++i )
{
for ( int j = 0 ; j < Column ; ++j )
{
cout << setw(4) << matrix[im(i,j)];
}
cout << endl;
}
}
};
int main(int argc, char* argv[])
{
CyclePrint::DoPrint( 3 , 4 );
return 0;
}
解题思路2
通过一定的观察,我们发现:
当我们把大小为m*n,初始坐标点为(x,y)的一个二维矩阵的一圈填满之后,
剩下的任务是继续填满一个大小为(m-2)*(n-2),初始坐标为(x+1,y+1)
所以,这个问题也适用于递归的方式解决办法,我们只要精确的找对的递归出口的条件就好。
下面,考虑到递归的方式比较适用于FP思维,所以,用F#的方式给出递归的实现。
主要思路已经说明了,关于具体的解法,我这里首先根据输入参数 初始下标x,y和要填充的矩阵大小,映射成对应的填充顺序坐标。
然后直接递归子矩阵,知道找出所有的顺序坐标,随后填充到一个二维数组里面,最后输出。
MapToCoordinate为主要的映射函数。
// Learn more about F# at http://fsharp.net
module CyclePrint
type Direction =
| Left2Right
| Top2Bottom
| Right2Left
| Bottom2Top
let rec MapToCoordinate x y row column =
let MakeCoordinateFromDirection direction=
match direction with
| Left2Right when column > 0 && row > 0 ->
[ 0 .. column - 1 ] |> List.map ( fun offset -> ( x , y + offset) )
| Top2Bottom when column > 0 && row > 2 ->
[ 0 .. row - 2 - 1 ] |> List.map ( fun offset -> ( x + 1 + offset, y + column - 1 ) )
| Right2Left when column > 0 && row > 1 ->
[ 0 .. -1 .. -( column - 1 ) ] |> List.map ( fun offset -> ( x + row - 1 , y + column - 1 + offset) )
| Bottom2Top when column > 0 && row > 2 ->
[ 0 .. -1 .. - ( row - 2 - 1) ] |> List.map ( fun offset -> ( x + row - 1 - 1 + offset, y ) )
| _ ->
[]
let head = [ Left2Right ; Top2Bottom ; Right2Left ; Bottom2Top ]
|> List.map MakeCoordinateFromDirection
|> List.fold (fun acc elem -> (List.append acc elem) ) []
match head with
| [] -> []
| _ -> List.append head (MapToCoordinate (x+1) (y+1) (row-2) (column-2))
let DoPrint row column =
let matrix = Array2D.create row column 0
MapToCoordinate 0 0 row column
|> List.zip [ 1 .. (row*column) ]
|> List.iter (fun ( num , (x,y) ) -> matrix.[x,y] <- num )
for x in 0..(row-1) do
for y in 0..(column-1) do
printf "%4d" matrix.[x,y]
printfn ""
[<EntryPoint>]
let main ( args : string[] ) =
DoPrint 4 8
0