一题多解 -- 循环填充二维数组

本文介绍两种实现二维数组顺时针填充的方法:一种通过定义方向映射并迭代填充;另一种采用递归方式,逐步缩小填充范围。

问题描述

问题:有一个m*n的二维数组,从外到内按照顺时针填充,并将结果打印出来。

据个例子: 3 * 4 的数组,填充结果为

    1   2   3   4
  10  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



<think>我们有一个一维数组,想要填充到一个二维空数组中。首先需要明确的是,二维空数组的维度必须与一维数组的长度相匹配,否则无法直接填充。假设我们有一个一维数组arr1d,形状为(n,),我们想把它填充到一个二维数组arr2d中,该二维数组形状为(m,n)或(n,m)?这里需要根据具体需求确定填充方式。常见的情况可能是:1.将一维数组作为二维数组的一行(或行)填充。2.将一维数组作为二维数组的一列(或列)填充。但是,如果二维数组是“空”的(例如用np.empty创建,或者用np.zeros创建但还没有填充),那么我们需要确保一维数组的长度与二维数组的某一维度长度一致。例如:-如果我们要将一维数组填充二维数组的一行,那么二维数组的列数必须等于一维数组的长度,并且我们指定要填充的行索引。-同样,如果填充为一列,则二维数组的行数必须等于一维数组的长度,并指定列索引。另外,如果二维数组是空的,我们也可以直接将整个二维数组个一维数组填充(例如逐行或逐列填充),但这里我们只讨论一个一维数组的情况。参考引用[1]中提到如何将一维数组转换成二维数组(添加新轴),这实际上改变了数组的维度,但这里我们是要填充到一个已经存在的二维数组中。根据问题,我们可能需要使用类似以下的方法:情况1:填充为一行假设我们有一个形状为(3,4)的二维空数组arr2d,我们有一个长度为4的一维数组arr1d,我们想将arr1d放到arr2d的第0行:arr2d[0,:]=arr1d情况2:填充为一列同样,arr2d形状为(4,3),arr1d长度为4,我们想将arr1d放到第0列:arr2d[:,0]=arr1d注意:赋值操作要求形状匹配。例如,在情况1中,arr2d[0,:]的形状是(4,),而arr1d的形状也必须是(4,)。如果arr1d的长度与目标行或列的长度不匹配,将会出错。如果我们要将同一个一维数组填充二维数组的每一行(或每一列),我们可以使用广播,但需要注意,赋值操作不支持自动广播到行(除非我们显式复制数组)。例如,将一维数组填充二维数组的每一行:arr2d[:,:]=arr1d#这样不行,因为形状不匹配,除非arr2d是(n,)和arr1d是(n,),但arr2d是二维的,所以这里要求arr1d能够广播到arr2d的形状。实际上,我们可以这样:arr2d[:,:]=arr1d[np.newaxis,:]#但这样要求arr2d的形状为(1,n)?不,这样只能广播到每一行?实际上,这样赋值要求arr1d[np.newaxis,:]的形状(1,n)能够广播到arr2d的形状(m,n)。但是赋值操作要求右边能够广播到左边的形状,所以如果arr2d是(m,n),那么我们可以:arr2d[:]=arr1d.reshape(1,-1)#这样会尝试将一维数组广播成(1,n)然后赋值给arr2d,但是要求arr2d是(1,n)?不对。更准确地说,如果我们想将一维数组(长度为n)赋值给一个二维数组(形状为m行n列)的每一行,我们可以:arr2d[:,:]=arr1d#直接这样赋值会出错,因为左边是(m,n),右边是(n,)#正确做法:将arr1d变成(1,n),然后利用广播(但赋值操作不支持广播到行?实际上,numpy的赋值操作支持一定程度的广播,但要求右边数组的形状与左边被赋值的部分的形状兼容)#我们可以这样做:arr2d[:]=arr1d.reshape(1,-1)#这样会尝试将右边的数组广播成(m,n),但reshape(1,-1)的形状是(1,n),而arr2d是(m,n),所以广播规则是:在维度大小为1的维度上扩展。因此,这会将arr1d复制m次,填充到每一行。验证:importnumpyasnparr1d=np.array([1,2,3,4])arr2d=np.empty((3,4))arr2d[:]=arr1d.reshape(1,-1)#将arr1d变成行向量,然后赋值,广播到3行同样,如果我们想将一维数组填充到每一列:arr2d[:]=arr1d.reshape(-1,1)#将arr1d变成列向量,然后广播到每一列注意:使用np.newaxis也可以,arr1d[np.newaxis,:]等同于reshape(1,-1),arr1d[:,np.newaxis]等同于reshape(-1,1)。因此,根据需求,我们可以选择不同的填充方式。总结:-填充到指定行:arr2d[i,:]=arr1d(要求arr1d的长度等于arr2d的列数)-填充到指定列:arr2d[:,j]=arr1d(要求arr1d的长度等于arr2d的行数)-填充到所有行:arr2d[:,:]=arr1d.reshape(1,-1)(要求arr2d的列数等于arr1d的长度,并且广播到所有行)-填充到所有列:arr2d[:,:]=arr1d.reshape(-1,1)(要求arr2d的行数等于arr1d的长度,并且广播到所有列)如果二维数组是空的,我们也可以先创建具有所需形状的数组,然后用上述方法填充。参考引用[3]中展示了如何通过改变数组形状(shape)来将一维数组变为二维数组,但这里我们是要填充到已存在的二维数组。所以,具体方法取决于你的二维数组的维度和你想要填充的方式。下面给出一个示例代码:示例1:将一维数组填充二维数组的指定行```pythonimportnumpyasnp#创建一个3行4列的空数组(用0初始化)arr2d=np.zeros((3,4))arr1d=np.array([1,2,3,4])#填充到第1行(索引0)arr2d[0]=arr1d#或者arr2d[0,:]=arr1dprint(arr2d)```输出:[[1.2.3.4.][0.0.0.0.][0.0.0.0.]]示例2:将一维数组填充二维数组的每一行```pythonarr2d=np.zeros((3,4))arr1d=np.array([1,2,3,4])#将arr1d变成行向量(1行4列),然后广播到3行arr2d[:,:]=arr1d.reshape(1,-1)#或者arr1d[np.newaxis,:]print(arr2d)```输出:[[1.2.3.4.][1.2.3.4.][1.2.3.4.]]示例3:将一维数组填充二维数组的每一列```pythonarr2d=np.zeros((4,3))#4行3列arr1d=np.array([1,2,3,4])#将arr1d变成列向量(4行1列),然后广播到3列arr2d[:,:]=arr1d.reshape(-1,1)#或者arr1d[:,np.newaxis]print(arr2d)```输出:[[1.1.1.][2.2.2.][3.3.3.][4.4.4.]]注意:在示例3中,我们要求arr2d的行数等于arr1d的长度(4),列数任意(这里是3),然后将arr1d广播到每一列。因此,请根据你的二维数组的维度和一维数组的长度,选择适当的填充方式。如果二维数组的维度与一维数组长度不匹配,则需要调整,比如截断或填充一维数组,或者调整二维数组的维度。但这已经超出本问题的范围。希望以上答对你有帮助。</think>### 将一维NumPy数组填充到二维空数组 在NumPy中,将一维数组填充到二维空数组的核心是**维度匹配**和**广播机制**。以下是具体方法及示例: --- #### **方法1:直接赋值(行/列填充)** 当二维数组的**行数或列数**与一维数组长度匹配时: ```python import numpy as np # 创建3&times;4的空二维数组 arr2d = np.empty((3, 4)) # 一维数组(长度=4) arr1d = np.array([1, 2, 3, 4]) # 填充到第0行 arr2d[0] = arr1d # 等价于 arr2d[0, :] = arr1d # 填充到第1列(需行数匹配) # arr2d[:, 1] = arr1d # 要求arr1d长度=3 ``` * **关键点**:一维数组长度必须与目标行/列长度严格一致[^3]。 --- #### **方法2:广播填充(全行/全列)** 通过`reshape`添加新轴,利用广播机制填充所有行或列: ```python # 填充所有行(每行相同) arr2d[:, :] = arr1d.reshape(1, -1) # 添加行轴 # 填充所有列(每列相同) arr2d[:, :] = arr1d.reshape(-1, 1) # 添加列轴 ``` * **原理**:`reshape(1, -1)`将一维数组转为行向量(1&times;N),广播到所有行;`reshape(-1, 1)`转为列向量(N&times;1),广播到所有列[^1][^5]。 --- #### **方法3:动态扩展数组** 若二维数组尚未创建,可直接从一维数组构建: ```python # 直接转为单行二维数组 arr2d_single_row = arr1d[np.newaxis, :] # 或 arr1d.reshape(1, -1) # 直接转为单列二维数组 arr2d_single_col = arr1d[:, np.newaxis] # 或 arr1d.reshape(-1, 1) ``` --- #### **注意事项** 1. **维度匹配**:赋值操作要求右值形状与左值切片形状兼容,例如: - `arr2d[i] = arr1d` 需 `arr1d.shape == (arr2d.shape[1],)` - `arr2d[:, j] = arr1d` 需 `arr1d.shape == (arr2d.shape[0],)` 2. **空数组初始化**:`np.empty`不初始化值,`np.zeros`会用0填充[^3]。 3. **高效操作**:优先使用`reshape`/`newaxis`而非循环,避免性能损失[^1]。 > **示例验证**: > ```python > arr2d = np.zeros((2, 3)) > arr1d = np.array([10, 20, 30]) > arr2d[1] = arr1d # 成功:第二行变为[10,20,30] > # arr2d[:, 0] = arr1d # 报错:Shape (3,) 无法匹配 (2,) > ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值