数据结构与算法(九)数组系列——构建乘积数组、顺时针打印矩阵leetcode执行时间击败100%用户

数组——二维数组

建立一定的抽象建模能力,将实际中的很多问题进行抽象

1 构建乘积数组

题目

给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。

思路

B[i]的值是A数组所有元素的乘积再除以A[i],但是题目中给定不能用除法,我们换一个思路,将B[i]的每个值列出来,如下图:

在这里插入图片描述

B[i]的值可以看作下图的矩阵中每行的乘积。

可以将B数组分为上下两个三角,先计算下三角,然后把上三角乘进去。

代码

function multiply(array) {
    const result = [];
    if (Array.isArray(array) && array.length > 0) {
        // 计算下三角  计算前i - 1个元素的乘积
        result[0] = 1;
        for (let i = 1; i < array.length; i++) {
            result[i] = result[i - 1] * array[i - 1];
        }
        // 乘上三角(越过i) 计算后N - i个元素的乘积并连接
        let temp = 1;
        for (let i = array.length - 2; i >= 0; i--) {
            temp = temp * array[i + 1];
            result[i] = result[i] * temp;
        }
    }
    return result;
}

2 顺时针打印矩阵

题目

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

例如,如果输入如下4 X 4矩阵:

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.

思路

解法1

在这里插入图片描述

借助图形思考,将复杂的矩阵拆解成若干个圈,循环打印矩阵,每次打印其中一个圈

设起点坐标为(start,start),矩阵的行数为rows,矩阵的列数为columns

循环结束条件为 rows>start*2 并且 columns>start*2

将打印一圈拆解为四部,

  • 第一步:从左到右打印一行
  • 第二步:从上到下打印一列
  • 第三步:从右到左打印一行
  • 第四步:从下到上打印一列

最后一圈很有可能出现几种异常情况,打印矩阵最里面一圈可能只需三步、两步、甚至一步

在这里插入图片描述

所以在每一行打印时要做好条件判断:

能走到最后一圈,从左到右必定会打印

结束行号大于开始行号,需要从上到下打印

结束列号大于开始列号,需要从右到左打印

结束行号大于开始行号+1,需要从下到上打印

解法2

在这里插入图片描述

​ 如果一条边从头遍历到底,则下一条边遍历的起点随之变化。如果不遍历到底,可以减小横向和竖向遍历之间的影响。我选择一次迭代遍历一个“圈”,然后 4 条边的两端同时收缩,一层层向内处理,按顺时针依次遍历:上、右、下、左层。

​ 当不再形成“环”了就结束遍历,剩下一行或一列,然后单独判断即可。

  • 上边界 top : 0

  • 下边界 bottom : matrix.length - 1

  • 左边界 left : 0

  • 右边界 right : matrix[0].length - 1

    矩阵不一定是方阵。top < bottom && left < right 是循环的条件。结束循环时,分 3 种情况:

  • top == bottom && left < right —— 剩一行。

  • top < bottom && left == right —— 剩一列。

  • top == bottom && left == right —— 剩一项(也是一行/列)。

    处理剩下的单行或单列。因为是按顺时针推入结果数组的,所以:

  • 剩下的一行,从左至右 依次推入结果数组。

  • 剩下的一列,从上至下 依次推入结果数组。

代码

解法1
// 顺时针打印
function printMatrix(matrix) {
    var start = 0;
    var rows = matrix.length;
    var coloums = matrix[0].length;
    var result = [];
    if (!rows || !coloums) {
        return false;
    }
    while (coloums > start * 2 && rows > start * 2) {
        //打印圈
        printCircle(matrix, start, coloums, rows, result);
        start++;
    }
    return result;
}

// 打印一圈
function printCircle(matrix, start, coloums, rows, result) {
    //结束列号
    var entX = coloums - start - 1;
    //结束行号
    var endY = rows - start - 1;
    //从左向右打印
    for (var i = start; i <= entX; i++) {
        result.push(matrix[start][i]);
    }
    //结束行号>开始行号,从上到下打印
    if (endY > start) {
        for (var i = start + 1; i <= endY; i++) {
            result.push(matrix[i][entX]);
        }
        //结束列号>开始列号,从右往左打印
        if (entX > start) {
            for (var i = entX - 1; i >= start; i--) {
                result.push(matrix[endY][i]);
            }
            //结束行号>开始列号+1 从下到上。
            if (endY > start + 1) {
                for (var i = endY - 1; i > start; i--) {
                    result.push(matrix[i][start]);
                }
            }
        }
    }
}

在这里插入图片描述

解法2

每个元素访问一次,时间复杂度 O(mn)O(m**n),m、n 分别是行数和列数。空间复杂度 O(mn)O(m**n)。

“不撞南墙不回头法”。

var spiralOrder = function(matrix) {
if (matrix.length == 0) return [];
  const res = [];
  //获得边界
  let top = 0;
  let bottom = matrix.length - 1;
  let left = 0;
  let right = matrix[0].length - 1;
  
  while (top < bottom && left < right) {
    //走到底
    for (let i = left; i < right; i++) res.push(matrix[top][i]);   // 上层
    for (let i = top; i < bottom; i++) res.push(matrix[i][right]); // 右层
    for (let i = right; i > left; i--) res.push(matrix[bottom][i]);// 下层
    for (let i = bottom; i > top; i--) res.push(matrix[i][left]);  // 左层
    right--;
    top++;
    bottom--;
    left++;
  }
  if (top == bottom) { // 剩下一行,从左到右依次添加
    for (let i = left; i <= right; i++) {
      res.push(matrix[top][i]);
    }
  } else if (left == right) { // 剩下一列,从上到下依次添加
    for (let i = top; i <= bottom; i++) {
      res.push(matrix[i][left]);
    }
  }
  return res;
};

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值