矩阵的螺旋遍历 - JavaScript 中的递归解决方案

Posted

技术标签:

【中文标题】矩阵的螺旋遍历 - JavaScript 中的递归解决方案【英文标题】:Spiral traversal of a matrix - recursive solution in JavaScript 【发布时间】:2015-09-03 13:27:54 【问题描述】:

我正在尝试提出一个采用这样的矩阵的解决方案:

[[1,2,3,4],
 [5,6,7,8],
 [9,10,11,12],
 [13,14,15,16]]

并返回一个以螺旋形式遍历数组的数组,因此在本例中: [1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10]

我无法让这个递归解决方案工作,其中结果数组采用第一个数组,其余数组的最终元素,倒序的底部数组,然后是中间的第一个元素数组,然后在没有外部“外壳”的情况下重新构造数组,以便可以递归地调用剩下的数组,直到中心有一个元素的数组或 2x2 矩阵(我的基本情况,尽管后者可能不是必需的。 ..)

我的解决方案不起作用,如下所示。关于如何完成这项工作的任何建议?

var spiralTraversal = function(matriks)
  var result = [];
    var goAround = function(matrix) 
        var len = matrix[0].length;
        if (len === 1) 
            result.concat(matrix[0]);
            return result;
        
        if (len === 2) 
            result.concat(matrix[0]);
            result.push(matrix[1][1], matrix[1][0]);
            return result;
        
        if (len > 2) 
            // right
            result.concat(matrix[0]);
            // down
            for (var j=1; j < matrix.length - 1; j++) 
                result.push(matrix[j][matrix.length -1]);
            
            // left
            for (var l=matrix.length - 2; l > 0; l--) 
                result.push(matrix[matrix.length - 1][l]);
            
            // up
            for (var k=matrix.length -2; k > 0; k--) 
                result.push(matrix[k][0]);
            
        
        // reset matrix for next loop
        var temp = matrix.slice();
        temp.shift();
        temp.pop();
        for (var i=0; i < temp.length - 1; i++) 
            temp[i] = temp[i].slice(1,-1);
        
        goAround(temp);
    ;
    goAround(matriks);  
;

【问题讨论】:

【参考方案1】:

您的代码非常接近,但它做的比它需要做的要多。在这里我简化并修复错误:

var input = [[1,  2,   3,  4],
             [5,  6,   7,  8],
             [9,  10, 11, 12],
             [13, 14, 15, 16]];

var spiralTraversal = function(matriks)
  var result = [];
    var goAround = function(matrix) 
        if (matrix.length == 0) 
            return;
        

        // right
        result = result.concat(matrix.shift());

        // down
        for (var j=1; j < matrix.length - 1; j++) 
            result.push(matrix[j].pop());
        

        // bottom
        result = result.concat(matrix.pop().reverse());

        // up
        for (var k=matrix.length -2; k > 0; k--) 
            result.push(matrix[k].shift());
        

        return goAround(matrix);
    ;

    goAround(matriks);

    return result;
;
var result = spiralTraversal(input);

console.log('result', result);

运行它输出:

result [1, 2, 3, 4, 12, 16, 15, 14, 13, 5, 6, 7, 8, 11, 10, 9]

JSFiddle:http://jsfiddle.net/eb34fu5z/

重要的事情:

concat on Array 返回结果——它不会改变调用者,所以你需要像这样保存concat 的结果:result = result.concat(otherArray) 检查递归数组顶部的终止条件 对于每次传递,执行预期的(上、右、下、左) 返回结果

我会这样做,但我会添加错误检查以验证数组具有相等数量的“行”和“列”。所以假设输入是有效的,我们开始:

var input = [[1,  2,   3,  4],
             [5,  6,   7,  8],
             [9,  10, 11, 12],
             [13, 14, 15, 16]];

function run(input, result) 
    if (input.length == 0) 
        return result;
    

    // add the first row to result
    result = result.concat(input.shift());

    // add the last element of each remaining row
    input.forEach(function(rightEnd) 
        result.push(rightEnd.pop());
    );

    // add the last row in reverse order
    result = result.concat(input.pop().reverse());

    // add the first element in each remaining row (going upwards)
    var tmp = [];
    input.forEach(function(leftEnd)     
        tmp.push(leftEnd.shift());
    );
    result = result.concat(tmp.reverse());

    return run(input, result);


var result = run(input, []);

console.log('result', result);

哪些输出:

result [1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10]

一般的想法是我们知道每次通过我们需要做这些事情:

    在输入中添加第一个数组 在输入中添加每个剩余数组的最后一项 在输入中添加最后一个数组 在输入中添加每个剩余数组中的第一项

因此,如果我们在每次通过时都进行递归,我们就可以完成螺旋。

JSFiddle:http://jsfiddle.net/2v6k5uhd/

【讨论】:

【参考方案2】:

你的算法看起来不错,只有一个错误有几件事,有些比其他的更难发现。

    concat method 不会改变数组(就像push 一样),但会返回一个新数组,其中包含原始数组中的所有元素和参数。 result 没有发生变异。

    要解决此问题,您可以

    使用result = result.concat(…); 使其成为一个显式循环,您可以在其中执行result.push(…)(如您已经编写的向下、向左和向上的循环)或 使用result.push@987654322@(result, …) 一次推送多个值 您的“左”或“上”循环确实漏掉了一个元素,即左下角的元素。要么向左走,你需要前进到第一个元素(在条件中使用&gt;= 0),或者向上走时,你需要从最后一行而不是倒数第二行开始(matrix.length-1)李> 在为下一次迭代收缩矩阵的循环中,您忘记了最后一行,它必须是for (var i=0; i &lt; temp.length; i++)(而不是temp.length-1)。否则你会得到非常不幸的结果。 您的基本情况应该是 0(和 1),而不是(1 和)2。这将简化您的脚本并避免错误(在极端情况下)。 您希望您的矩阵是正方形的,但它们可能是矩形的(甚至有不均匀长度的线)。您正在访问的 .length 可能不是您所期望的 - 最好仔细检查并抛出带有描述性消息的错误。 spiralTraversalgoAround 都缺少用于(递归)调用的 return 语句。他们只是填写result,但不返回任何东西。

【讨论】:

谢谢。我进行了第一个建议的更改,但仍然出现错误。 jsfiddle.net/vdn9ygae @zahabba:好的,我仔细看了看,发现了更多东西 :-)【参考方案3】:

虽然不是递归的,但至少输出正确答案:

result: [ 1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10 ]

我想说唯一奇怪的是必须在每个 while 循环之后“重置”变量 i,j。此外,可能还有更简洁的递归解决方案。

var array = [ 
  [1,  2,  3,   4],
  [5,  6,  7,   8],
  [9,  10, 11,  12],
  [13, 14, 15,  16]  
];

function spiralTraversal(array) 
  let discovered = new Set();
  let result = [];  
  let totalSpots = array.length * array[0].length;
  let direction = 'right';

  for (var i = 0; i < array.length; i ++) 
    for (var j = 0; j < array[i].length; j++)    

      while (totalSpots) 
        while (direction === 'right' && !!bounds(array, i, j) && !discovered.has(array[i][j]))   
          discovered.add(array[i][j]);                        
          result.push(array[i][j]);
          totalSpots--;                            
          j++;                         

        

        direction = 'down';  
        i++;
        j--;


        while (direction === 'down' && !!bounds(array,i, j) && !discovered.has(array[i][i]))       
          discovered.add(array[i][j]);                    
          result.push(array[i][j]);
          totalSpots--;          
          i++;                                           
        


        direction = 'left';  
        j--;
        i--;


        while (direction === 'left' && !!bounds(array, i, j) && !discovered.has(array[i][j]))   
          discovered.add(array[i][j]);                    
          result.push(array[i][j]);
          totalSpots--;       
          j--;                         
        


        direction = 'up';          
        i--;
        j++


        while (direction === 'up' && bounds(array, i, j) && !discovered.has(array[i][j])) 
          discovered.add(array[i][j]);          
          result.push(array[i][j]);
          totalSpots--;          
          i--;                                   
        

        direction = 'right';        
        j++;
        i++;

                
    
  
  return result;


function bounds(array, i, j)
  if (i < array.length && i >= 0 && j < array[0].length && j >= 0) 
    return true;
   else 
    return false;
  
;

【讨论】:

【参考方案4】:

递归解:

我没有四处走动,而是越过顶行和最右边的一列,然后递归调用“反转”矩阵上的函数。

var input = [
                [ 1, 2, 3, 4], 
                [ 5, 6, 7, 8], 
                [ 9,10,11,12], 
                [13,14,15,16]
            ];

let spiral = (mat) => 
    if(mat.length && mat[0].length) 
        mat[0].forEach(entry =>  console.log(entry))
        mat.shift();
        mat.forEach(item => 
            console.log(item.pop())
        );
        spiral(reverseMatrix(mat))
    
    return;


let reverseMatrix = (mat) =>  
    mat.forEach(item =>  
        item.reverse() 
    ); 
    mat.reverse(); 
    return mat; 


console.log("Clockwise Order is:")
spiral(input)

【讨论】:

【参考方案5】:

这是我的功能:

let  array_masalah = [
    [1,2,3,4],
    [5,6,7,8],
    [9, 10, 11, 12],
    [13, 14, 15,16],
];

let  array_masalah_2 = [
    [1, 2, 3, 4, 5],
    [6, 7, 8, 9, 10],
    [11, 12, 13, 14, 15],
    [16, 17, 18, 19, 20],
];


function polaSpiral(array_masalah) 
    function spiral(array) 
        if (array.length == 1) 
        return array[0];
      

        var firstRow    = array[0]
        , numRows     = array.length
        , nextMatrix  = []
        , newRow
        , rowIdx
        , colIdx      = array[1].length - 1

        for (colIdx; colIdx >= 0; colIdx--) 
        newRow = [];

        for (rowIdx = 1; rowIdx < numRows; rowIdx++) 
          newRow.push(array[rowIdx][colIdx]);
        

        nextMatrix.push(newRow);
      

        firstRow.push.apply(firstRow, spiral(nextMatrix));
        return firstRow
    

    console.log(spiral(array_masalah));



polaSpiral(array_masalah) // [ 1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10 ]
polaSpiral(array_masalah_2) // [ 1, 2, 3, 4, 5, 10, 15, 20, 19, 18, 17, 16, 11, 6, 7, 8, 9, 14, 13, 12 ]

【讨论】:

【参考方案6】:

我习惯了 C#:

    public static IList<int> spiralTraversal (int[,] matrix)
    
        IList<int> list = new List<int>();            

        // Get all bounds before looping.
        int bound0 = matrix.GetUpperBound(0);
        int bound1 = matrix.GetUpperBound(1);

        int totalElem = (bound0+1) * (bound1+1);

        int auxbound0 = 0;
        int auxbound1 = 0;

        string direction = "left";

        int leftCtrl = 0;
        int rightCtrl = 0;
        int upCtrl = 0;
        int downCtrl = 0;

        for (int i=0;i< totalElem;i++)
        
            if (direction == "down")
            
                list.Add(matrix[auxbound0, auxbound1]);
                if (auxbound0 == bound0 - downCtrl)
                
                    direction = "right";
                    auxbound1 -= 1;
                    downCtrl += 1;
                    continue;
                
                else
                
                    auxbound0 += 1;
                
            

            if (direction == "left")
            
                list.Add(matrix[auxbound0, auxbound1]);
                if (auxbound1 == bound1 - leftCtrl)
                
                    direction = "down";
                    auxbound0 += 1;
                    leftCtrl += 1;
                    continue;
                
                else
                
                    auxbound1 += 1;
                
            

            if (direction == "up")
            
                list.Add(matrix[auxbound0, auxbound1]);
                if (auxbound0 == 1 + upCtrl)
                
                    direction = "left";
                    auxbound1 += 1;
                    upCtrl += 1;
                    continue;
                
                else
                
                    auxbound0 -= 1;
                
            

            if (direction == "right")
            
                list.Add(matrix[auxbound0, auxbound1]);
                if (auxbound1 == rightCtrl)
                
                    direction = "up";
                    auxbound0 -= 1;
                    rightCtrl += 1;
                    continue;
                
                else
                
                    auxbound1 -= 1;
                
            
        

        return list;
    

【讨论】:

【参考方案7】:

此解决方案适用于任何类型的矩阵 (m * n),而不仅仅是正方形 (m * m)。下面的示例采用 5*4 矩阵并以螺旋格式打印。

var matrix =  [[1,2,3,4], [14,15,16,5], [13,20,17,6], [12,19,18,7], [11,10,9,8]];

var row = currentRow = matrix.length, column = currentColumn = matrix[0].length;

while(currentRow > row/2 )

  // traverse row forward
  for(var i = (column - currentColumn); i < currentColumn ; i++)  console.log(matrix[row - currentRow][i]); 

  // traverse column downward
  for(var i = (row - currentRow + 1); i < currentRow ; i++)  console.log(matrix[i][currentColumn - 1]) 

  // traverse row backward
  for(var i = currentColumn - 1; i > (column - currentColumn) ; i--)  console.log(matrix[currentRow - 1][i - 1]); 

  // traverse column upward
  for(var i = currentRow - 1; i > (row - currentRow + 1) ; i--)  console.log(matrix[i - 1][column - currentColumn]) 

  currentRow--;
  currentColumn--;

【讨论】:

【参考方案8】:

? 螺旋阵列 (ES6)

ES6 让我们保持简单:

function spiral(matrix) 
  const arr = [];
    
  while (matrix.length) 
    arr.push(
      ...matrix.shift(),
      ...matrix.map(a => a.pop()),
      ...(matrix.pop() || []).reverse(),
      ...matrix.map(a => a.shift()).reverse()
    );
  
  return arr;

【讨论】:

告诉我,是否有可能在 php 中实现? @Lior Elrom 代码简洁明了。从未想过传播运算符可以用于此任务。 例如,如果您在矩阵大小 N*M (5*4) 上尝试它,则会出现“未定义”异常。用 try 和 catch 包围循环正在解决这个问题。无论如何,这是一个很好的解决方案。 @MatanTubul 好点。不要使用 try catch,而是将 ...matrix.pop().reverse() 替换为 ...(matrix.pop() || []).reverse() 以解决非数组结果。 @LiorElrom ...matrix.pop()?.reverse?.() ?? []【参考方案9】:

以下是一个 javascript 解决方案。我已将 cmets 添加到代码中,以便您可以按照流程进行操作:)

var array = [ 
    [1,  2,  3,   4],
    [5,  6,  7,   8],
    [9,  10, 11,  12],
    [13, 14, 15,  16]  
];

var n = array.length;

//create empty 2d array

var startRow = 0;
var endRow = n - 1;
var startColumn = 0;
var endColumn = n - 1
var newArray = [];

// While loop is used to spiral into the 2d array.
while(startRow <= endRow && startColumn <= endColumn) 

    // Reading top row, from left to right
    for(var i = startColumn; i <= endColumn; i++) 
        newArray.push(array[startColumn][i]);
    
    startRow++; // Top row read.

    // Reading right column from top right to bottom right
    for(var i = startRow; i <= endRow; i++) 
        newArray.push(array[i][endColumn]);
    
    endColumn--; // Right column read

    // Reading bottom row, from bottom right to bottom left
    for(var i = endColumn; i >= startColumn; i--) 
        newArray.push(array[endRow][i]);
    
    endRow--; // Bottom row read

    // Reading left column, from bottom left to top left
    for(var i = endRow; i >= startRow; i--) 
        newArray.push(array[i][startColumn]);
    
    startColumn++; // left column now read.

 // While loop will now spiral in the matrix.

console.log(newArray);

:)

【讨论】:

【参考方案10】:

此解决方案采用螺旋阵列并将其转换为有序阵列

它以上、右、下、左的格式对螺旋矩阵进行排序。

const matrix = [
  [1, 2, 3, 4, 5],
  [16, 17, 18, 19, 6],
  [15, 24, 25, 20, 7],
  [14, 23, 22, 21, 8],
  [13, 12, 11, 10, 9],
];

function getOrderdMatrix(matrix, OrderdCorner) 
  // If the Matrix is 0 return the OrderdCorner
  if (matrix.length > 0) 

    //Pushes the top of the matrix to OrderdCorner array
    OrderdCorner.push(...matrix.shift());

    let left = [];
    /*Pushes right elements to the Orderdcorner array and
     Add the left elements to the left array */
    for (let i = 0; i < matrix.length; i++) 
      OrderdCorner.push(matrix[i][matrix[i].length - 1])
      matrix[i].pop(); //Remove Right element

      if (matrix[i].length > 0) 
        //Starts from the last element of the left corner 
        left.push(matrix[(matrix.length - 1) - i][0])
        matrix[(matrix.length - 1) - i].shift();
      
    

    /* If the array length is grater than 0 add the bottom
    to the OrderdCorner array */
    if (matrix.length > 0) 
      OrderdCorner.push(...matrix.pop().reverse());
    
    //Ads the left array to the OrderdCorner array
    OrderdCorner.push(...left);

    return getOrderdMatrix(matrix, OrderdCorner);
   else 
    return OrderdCorner
  


console.log(getOrderdMatrix(matrix,[]));

退货 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]

【讨论】:

【参考方案11】:

这是一个可配置的版本:

function spiral(n) 

// Create 2D array of size n*n
var matrix = new Array(n);
  for(var i=0; i < matrix.length; i++) 
     matrix[i] = new Array(n);
  

  for(var i=0; i < n;i++) 
    for(var j=0; j < n; j++) 
       matrix[i][j] = 0;
    
  

  var startNum = 0;
  var rowNum = 0;

  function spin(rowNum) 

   // right
   for(var j=rowNum; j < (n-rowNum); j++) 
      startNum++; 
      matrix[rowNum][j] = startNum;
   

   if(startNum === (n*n)) 
      return; // exit if number matches to the size of the matrix. ( 16 = 4*4 )
   

   // down
   for(var i=(rowNum+1); i < (n-(rowNum+1)); i++) 
     startNum++; 
     matrix[i][n-(rowNum+1)] = startNum;
   

   if(startNum === (n*n)) 
      return; // exit if number matches to the size of the matrix. ( 16 = 4*4 )
   

  // left
   for(var j=(n-(1+rowNum)); j >= rowNum; j--) 
     startNum++; 
     matrix[(n-(1+rowNum))][j] = startNum;
   


   if(startNum === (n*n)) 
      return; // exit if number matches to the size of the matrix. ( 16 = 4*4 )
   

   //top
   for(var i=(n-(2+rowNum)); i > rowNum; i--) 
      startNum++; 
      matrix[i][rowNum] = startNum;
   

  if(startNum === (n*n)) 
      return; // exit if number matches to the size of the matrix. ( 16 = 4*4 )
   

  spin(rowNum+1);


   

  spin(rowNum);

  console.log(matrix)


spiral(6);

例如:https://jsfiddle.net/dino_myte/276ou5kb/1/

【讨论】:

【参考方案12】:

const spiralOrder = matrix => 
  if (!matrix || matrix.length === 0) 
    return [];
  
  let startRow = 0;
  let startCol = 0;

  let ans = [];
  let endCol = matrix[0].length - 1;
  let endRow = matrix.length - 1;

  while (startRow <= endRow && startCol <= endCol) 
    for (let i = startCol; i <= endCol; i++) 
      ans.push(matrix[startRow][i]);
    

    startRow++;

    for (let i = startRow; i <= endRow; i++) 
      ans.push(matrix[i][endCol]);
    
    endCol--;

    if (startRow <= endRow) 
      for (let i = endCol; i >= startCol; i--) 
        ans.push(matrix[endRow][i]);
      
      endRow--;
    

    if (startCol <= endCol) 
      for (let i = endRow; i >= startRow; i--) 
        ans.push(matrix[i][startCol]);
      
      startCol++;
    
  
  return ans;
;

let input = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
//Output: [1, 2, 3, 6, 9, 8, 7, 4, 5];
spiralOrder(input);

【讨论】:

【参考方案13】:

关于这个漂亮的玩具问题,我已经写了一段时间的文章,我真的很喜欢。您可能需要查看我的解决方案。

你可以在 Medium 上关注我,也可以从here.查看我的文章

var spiralTraversal = function (matrix, result = []) 
  // TODO: Implement me!

  //  if the length of the matrix ==0 we will return the result
  if (matrix.length == 0) 
    return result;
  
  // we need to push the elements inside the first element of the array then delete this element
  while (matrix[0].length) 
    result.push(matrix[0].shift());
  
  //top right to bottom right
  matrix.forEach((row) => 
    result.push(row.pop());
  );
  //bottom right to bottom left
  while (matrix[matrix.length - 1].length) 
    result.push(matrix[matrix.length - 1].pop());
  
  //reverse again so we can retraverse on the next iteration
  matrix.reverse();
  //filter out any empty arrays
  matrix = matrix.filter((element) => element.length);

  //recursive case
  result = spiralTraversal(matrix, result);

  //return the result and filter any undefined elements
  return result.filter((element) => element);
;

【讨论】:

请注意我对类似问题的simpler answer 的看法。

以上是关于矩阵的螺旋遍历 - JavaScript 中的递归解决方案的主要内容,如果未能解决你的问题,请参考以下文章

某比赛小记5- 螺旋遍历矩阵

54. 螺旋矩阵

算法:螺旋矩阵

ruby 以螺旋顺序打印或遍历矩阵

LeetCode之螺旋矩阵

59. 螺旋矩阵 II-内环模拟遍历