首页 > 其他分享 >二维数组的旋转遍历:顺时针、逆时针和对角线旋转

二维数组的旋转遍历:顺时针、逆时针和对角线旋转

时间:2024-06-03 14:54:17浏览次数:8  
标签:遍历 return matrix int up 旋转 vector 对角线 left

在面试中,常常会遇到数组的各类旋转问题,例如以顺时针、逆时针或对角线的方式遍历二维数组。这类问题并不涉及算法,只是逻辑有点复杂,一个不小心就会导致访问越界,考验的是编程的基本功。如何优雅地解决此类问题呢?

1. 顺时针旋转

[LeetCode 54. 螺旋矩阵] 给你一个 mn 列的矩阵 matrix ,请按照顺时针螺旋顺序,返回矩阵中的所有元素。

示例 1:

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]

示例 2:

输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]

 

仔细观察顺时针旋转的过程,首先顺时针输出矩阵最外面的一圈,随后是第二圈……因此,可以定义函数rotate_one_circle,用于顺时针输出矩阵最外层的一圈,通过在二维数组中左、右、上、下的索引指定矩阵大小。因此,这个函数的原型如下:

// 顺时针旋转矩阵matrix[top:bottom][left:right]最外面的一圈,返回旋转后的结果
vector<int> rotate_one_circle(vector<vector<int>>& matrix, int left, int right, int top, int bottom);

利用这个函数,很容易顺时针旋转整个二维数组:

vector<int> rotate(vector<vector<int>>& matrix) {
	vector<int> result{};
	int m = matrix.size();  // 获取矩阵的第1维大小
	if (m == 0) {  // 处理输入为空矩阵的情况
		return result;
	}
	int n = matrix[0].size();  // 获取矩阵的第2维大小
	int left = 0, right = n-1;  // 矩阵的四个边界
	int top = 0, bottom = m-1;
	for(;left <= right && top <= bottom; left++, right--, top++, bottom--) {
        // 每次循环顺时针先遍历矩阵最外层,之后通过修改left,right,top和bottom将矩阵向内收缩一圈
		auto v = rotate_one_circle(matrix, left, right, top, bottom);
		result.insert(result.end(), v.begin(), v.end());
	}
	return result;
}

 现在考虑如何实现旋转一圈的功能:这个很简单,按顺时针方向遍历一圈即可:

vector<int> rotate_one_circle(vector<vector<int>>& matrix, int left, int right, int top, int bottom) {
	vector<int> result{};
	if (left <= right and top <= bottom) {
		for (int j = left; j <= right; j++) {
			result.push_back(matrix[top][j]);
		}
		for (int i = top+1; i <= bottom; i++) {
			result.push_back(matrix[i][right]);
		}
		for (int j = right-1; j >= left; j--) {
			result.push_back(matrix[bottom][j]);
		}
		for (int i = bottom-1; i > top; i--) {
			result.push_back(matrix[i][left]);
		}
	}
	return result;
}

以上代码对于一般情况是成立的,但对于特殊情况呢?例如left==righttop==bottom的情况?不难发现,对于这样的情况,以上代码存在问题。例如,当left==right时,矩阵退化为一个笔直的长条,如下图所示。图中的1,2,3,4依次表示以上代码中的4个循环,可以看到,第4个循环会再次遍历第2个循环已经遍历过的、长条的中间部分。

要想解决这个问题,可以直接在代码中分类处理这些特殊情况,也可以直接在上述代码添加控制条件,避免不必要的访问。这里采用后者,修改后的代码如下:

vector<int> rotate_one_circle(vector<vector<int>>& matrix, int left, int right, int top, int bottom) {
	vector<int> result{};
	if (left <= right and top <= bottom) {
		for (int j = left; j <= right; j++) {
			result.push_back(matrix[top][j]);
		}
		for (int i = top+1; i <= bottom; i++) {
			result.push_back(matrix[i][right]);
		}
		if (top < bottom) { 
			for (int j = right-1; j >= left; j--) {
				result.push_back(matrix[bottom][j]);
			}
		}
		if (left < right) {
			for (int i = bottom-1; i > top; i--) {
				result.push_back(matrix[i][left]);
			}
		}
	}
	return result;
}

注意,当left==righttop==bottom同时成立时,以上代码也能给出正确结果。

 

类似的问题还有向二维数组中沿顺时针方向填数,例如59. 螺旋矩阵 II, 2326. 螺旋矩阵 IV. 这类问题本质上还是二维数组的顺时针遍历问题,只需要将以上代码中访问数组元素部分修改为写入数组元素即可。

2. 逆时针旋转

逆时针旋转与顺时针旋转差不多,只不过每次访问最外层时,按照逆时针顺序访问,在此不再赘述。

3. 对角线旋转

[LeetCode 498. 对角线遍历] 给你一个大小为 m x n 的矩阵 mat ,请以对角线遍历的顺序,用一个数组返回这个矩阵中的所有元素。

示例 1:

输入:mat = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,4,7,5,3,6,8,9]

示例 2:

输入:mat = [[1,2],[3,4]]
输出:[1,2,3,4]

 

先考虑右边界和下边界不存在的情况,如下图所示:

在这种情况下,只有向上运动才会碰到上边界、向下运动才会碰到下边界,并因此改变方向。假设函数next_position计算当前坐标和移动方向下,下一个坐标的位置,此函数的实现如下:

// is_up表示运动的方向,为true时表示向上运动,否则表示向下运动
// 这里传入is_up的引用,因为函数中可能会修改运动方向
vector<int> next_position(int i, int j, bool& is_up) {
	if (is_up) {
		if (i == 0) {  // 向上运动时,碰撞到上边界
			is_up = false;  // 改变运动方向为向下
			return {i, j+1};
		}
		else {
			return {i-1, j+1};
		}
	}
	else {
		if (j == 0) {  // 向下运动时,碰撞到左边界
			is_up = true;  // 改变运动方向为向上
			return {i+1, j};
		}
		eles {
			return {i+1, j-1};
		}
	}
}

现在,在右侧和下方添加两个边界。不难发现,当向上运动时,在碰到上边界前,可能会先碰到右边界,因此,向上运动时,首先检查是否碰到右边界;向下运动也是同样的道理。

如果mn分别表示下边界和右边界的位置,函数next_postion的实现如下:

vector<int> next_position(int i, int j, int m, int n, bool& is_up) {
	if (is_up) {
		if (j == n) {  // 向上运动时,碰撞到右边界
			is_up = false;
			return {i+1, j};
		}
		else if (i == 0) {  // 向上运动时,碰撞到上边界
			is_up = false;  // 改变运动方向为向下
			return {i, j+1};
		}
		else {
			return {i-1, j+1};
		}
	}
	else {
		if (i == m) {  // 向下运动时,碰撞到下边界
			is_up = true;
			return {i, j+1};
		}
		else if (j == 0) {  // 向下运动时,碰撞到左边界
			is_up = true;  // 改变运动方向为向上
			return {i+1, j};
		}
		else {
			return {i+1, j-1};
		}
	}
}

有了这个函数,很容易解决上述问题了:

vector<int> visit_diagonal(vector<vector<int>>& mat) {
	int i = 0, j = 0;
	int m = mat.size(), n = mat[0].size();

	bool is_up = true;
	vector<int> result {};
	for(int k = 1; k <= m*n; k++) {  // 访问mxn的二维数组,因此恰好循环mxn次
		result.push_back(mat[i][j]);
		auto v = next_position(i, j, m-1, n-1, is_up);  // 注意,这里右边界和下边界分别为m-1和n-1
		i = v[0], j = v[1];  // 更新下一个位置
	}
	return result;
}

 

标签:遍历,return,matrix,int,up,旋转,vector,对角线,left
From: https://www.cnblogs.com/overxus/p/18228369

相关文章

  • 百度文库最新AI旋转验证码识别
    上个月发现百度文库最新出了一个验证码,是AI生成的。内容每次可能都不一样,所以给识别造成了很大困难。传统的比对放松完全失效。一、介绍这个是最近才出的最新验证码,内容主要以工厂、建筑、山峰、机器人、汽车、盆栽植物等为主。如下图所示优点:解决了图片种类有限的问题,AI......
  • Object 对象实现for of 迭代遍历
    实现代码Object.prototype[Symbol.iterator]=function(){letkeys=Object.keys(this);letindex=0;return{next:()=>{return{value:this[keys[index++]],done:index>=keys.lengt......
  • 【JAVA】快速遍历map集合
    1.使用entrySet()方法【推荐】2.直接使用values()方法获取所有value值组成的集合3.使用keySet()方法和getValue方法4.使用迭代器iterator5.使用增强for的Lambda表达式......
  • 二叉树链式结构的前序、中序、后序、层序遍历
    文章目录一、二叉树创建二、前序遍历概念以及解释代码三、中序遍历概念及解释代码四、后序遍历概念及解释代码五、层序遍历概念及解释代码一、二叉树创建&mesp; 实现二叉树的遍历,我们要先手搓出一个二叉树,在次基础上实现二叉树的前序、中序、后序、层序遍历。......
  • Shell阶段08 数组(普通数组, 关联数组), 示例(数组的遍历与循环)
    数组基本概述#shell的数组用的比较少,一般都用awk。因为shell的数组比awk慢很多什么是数组简单来说:数组其实就是变量的一种,变量只能存储一个值,而数组可以存储多个值数组的分类分为两类普通数组关联数组普通数组:只能使用正整数作为数组索引关联数组:可以使用......
  • 罗德里格斯旋转公式证明
    罗德里格斯旋转公式证明。设旋转向量为\((n,\theta)\),设其对应的旋转矩阵为\(R\),如何证明?\[R=cos\thetaI+n^{\wedge}sin\theta+(1-cos\theta)nn^{T}\]证明过程如下:如图所示,设旋转向量为\(\hat{A}\),记为\(n\),设三维中的点\(r\)绕\(n\)旋转\(\theta\)后得到\(r^{'}\),其中\(......
  • 《旋转的快速傅里叶变换》——2024.5.31
    $$\aleph$$——发疯记录(无题,不知道起什么好,用前几天看书看到的符号阿列夫表示了)我很久没发过阶段性总结类的博文了,对比去年来是少之又少。一是因为我觉得现在的日子比去年枯燥多了;二是其实我平时会写记录,但没有总的;三是上了高中以后几次语文考试我的作文成绩都很差,老师说我写的......
  • MyBatis的XML配置:如何判断List为空并遍历拼接
    哈喽,大家好,我是木头左!大家好,欢迎来到我的博客!今天要聊一聊关于MyBatis的XML配置,如何在查询数据表时判断List是否为空,并进行遍历拼接。相信这个问题对于很多使用MyBatis的朋友来说都非常实用,所以请大家认真阅读哦!一、为什么需要判断List是否为空?在的日常开发中,经常会遇到需要......
  • SAP ABAP 对工作区列遍历或按条件访问
    需要对字段数量多的工作区或动态工作区进行数据处理时,列遍历可使代码更加的简洁高效。(๑¯ω¯๑)重新发一遍,丢合集里示例代码:点击查看代码TYPES:BEGINOFtyp_kna1,kunnrTYPEkna1-kunnr,"客户编号name1TYPEkna1-name1,"送达方名......
  • Leetcode 力扣106. 从中序与后序遍历序列构造二叉树 (抖音号:708231408)
    给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。示例1:输入:inorder=[9,3,15,20,7],postorder=[9,15,7,20,3]输出:[3,9,20,null,null,15,7]示例2:输入:inorder=[......