剑指 Offer(第2版)面试题 29:顺时针打印矩阵

剑指 Offer(第2版)面试题 29:顺时针打印矩阵

  • 剑指 Offer(第2版)面试题 29:顺时针打印矩阵
    • 解法1:按层模拟
    • 解法2:模拟

剑指 Offer(第2版)面试题 29:顺时针打印矩阵

题目来源:40. 顺时针打印矩阵

解法1:按层模拟

可以将矩阵看成若干层,首先输出最外层的元素,其次输出次外层的元素,直到输出最内层的元素。

定义矩阵的第 k 层是到最近边界距离为 k 的所有顶点。

设矩阵的行数和列数分别是 m 和 n,初始化边界标记:left = 0,right = n - 1,top = 0,buttom = m - 1。

对于每层,从左上方开始以顺时针的顺序遍历所有元素。假设当前层的左上角位于 (top, left),右下角位于 (bottom, right),按照如下顺序遍历当前层的元素:

  1. 从左到右遍历上侧元素:范围是 (top, left) 到 (top, right),之后上边界下移,即 top++;
  2. 从上到下遍历右侧元素:范围是 (top, right) 到 (buttom, right),之后右边界左移,即 right–;
  3. 从右到左遍历下侧元素:范围是 (buttom, right) 到 (buttom, left),之后下边界上移,即 buttom++;
  4. 从下到上遍历左侧元素:范围是 (buttom, left) 到 (top, left),之后左边界右移,即 left++。

进入下一层继续遍历,直到遍历完所有元素为止。

代码:

class Solution
{
public:
	vector<int> printMatrix(vector<vector<int>> matrix)
	{
		// 特判
		if (matrix.empty() || matrix[0].empty())
			return {};
		int m = matrix.size(), n = m ? matrix[0].size() : 0;
		vector<int> nums(m * n, 0);
		int index = 0; // 数组下标
		// 边界标记
		int left = 0, right = n - 1, top = 0, buttom = m - 1;
		while (index < m * n)
		{
			// 向右
			for (int j = left; j <= right && index < m * n; j++)
				nums[index++] = matrix[top][j];
			top++; // 上边界下移
			// 向下
			for (int i = top; i <= buttom && index < m * n; i++)
				nums[index++] = matrix[i][right];
			right--; // 右边界左移
			// 向左
			for (int j = right; j >= left && index < m * n; j--)
				nums[index++] = matrix[buttom][j];
			buttom--; // 下边界上移
			// 向上
			for (int i = buttom; i >= top && index < m * n; i--)
				nums[index++] = matrix[i][left];
			left++; // 左边界右移
		}
		return nums;
	}
};

复杂度分析:

时间复杂度:O(m*n),其中 m 和 n 分别是矩阵的行数和列数。每个矩阵元素都要遍历一次。

空间复杂度:O(1)。仅用到若干变量,忽略答案数组的空间开销。

解法2:模拟

可以模拟螺旋矩阵的路径。初始位置是矩阵的左上角,初始方向是向右,当路径超出界限或者进入之前访问过的位置时,顺时针旋转,进入下一个方向。

判断路径是否进入之前访问过的位置需要使用一个与输入矩阵大小相同的辅助矩阵 visited,其中的每个元素表示该位置是否被访问过。当一个元素被访问时,将 visited 中的对应位置的元素设为已访问。

如何判断路径是否结束?

由于矩阵中的每个元素都被访问一次,因此路径的长度即为矩阵中的元素数量,当路径的长度达到矩阵中的元素数量时即为完整路径,将该路径返回。

代码:

class Solution
{
private:
	// 方向数组,按顺序分别是:向右、向下、向左、向上
	static constexpr int directions[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

public:
	vector<int> printMatrix(vector<vector<int>> matrix)
	{
		if (matrix.empty() || matrix[0].empty())
			return {};
		int m = matrix.size(), n = m ? matrix[0].size() : 0;
		vector<int> nums(m * n, 0);
		vector<vector<bool>> visited(m, vector<bool>(n, false));
		int r = 0, c = 0;
		int directionIndex = 0;
		for (int index = 0; index < m * n; index++)
		{
			nums[index] = matrix[r][c];
			visited[r][c] = true;
			int next_r = r + directions[directionIndex][0], next_c = c + directions[directionIndex][1];
			// 遇到边界,转向
			if (next_r < 0 || next_r >= m || next_c < 0 || next_c >= n || visited[next_r][next_c])
				directionIndex = (directionIndex + 1) % 4;
			r += directions[directionIndex][0];
			c += directions[directionIndex][1];
		}
		return nums;
	}
};

复杂度分析:

时间复杂度:O(m*n),其中 m 和 n 分别是矩阵的行数和列数。矩阵中的每个元素都要被访问一次。

空间复杂度:O(m*n),其中 m 和 n 分别是矩阵的行数和列数。需要创建一个大小为 m*n 的矩阵 visited 记录每个位置是否被访问过。

你可能感兴趣的:(剑指,Offer,矩阵,C++,数据结构与算法,剑指Offer,模拟)