旋转图像[中等]

news2024/12/25 16:57:06

优质博文:IT-BLOG-CN

一、题目

给定一个n × n的二维矩阵matrix表示一个图像。请你将图像顺时针旋转90度。你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

示例 1:

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

示例 2:

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

n == matrix.length == matrix[i].length
1 <= n <= 20
-1000 <= matrix[i][j] <= 1000

二、代码

【1】我们先不进行原地旋转,而是使用辅助数组进行,我们通过案例寻找规律:

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

我们将图像旋转90度之后,我们看下第一行,旋转后他出现在倒数第一列的位置:可以发现第一行的x元素落到了倒数第一列的第x个元素。

[5  1  9  11]                 [o  o  o  5] 
[o  o  o  o ]  ----> 旋转后   [o  o  o  1] 
[o  o  o  o ]                 [o  o  o  9] 
[o  o  o  o ]                 [o  o  o 11] 

对于矩阵中的第二行,我们旋转后看下:对于矩阵中的第三行和第四行同理。这样我们可以得到规律:**对于矩阵中第i行的第j个元素,在旋转后,它出现在倒数第i列的第j个位置。**我们将其翻译成代码。由于矩阵中的行列从0开始计数,因此对于矩阵中的元素matrix[row][col],在旋转后,它的新位置为matrixnew[col][n−1−row]

[o  o  o  o ]                 [o  o  2  o] 
[2  4  5  10]  ----> 旋转后   [o  o  3  o] 
[o  o  o  o ]                 [o  o  4  o] 
[o  o  o  o ]                 [o  o  10 o] 

我们使用一个与matrix大小相同的辅助数组matrixnew​,临时存储旋转后的结果。我们遍历matrix中的每一个元素,根据上述规则将该元素存放到matrixnew 中对应的位置。在遍历完成之后,再将matrixnew中的结果复制到原数组中即可。

class Solution {
    public void rotate(int[][] matrix) {
        // 先创建相同的二位数据进行优化
        int n = matrix.length;
        int[][] matrixNew = new int[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                matrixNew[j][n-1-i] = matrix[i][j];
            }
        }
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                matrix[i][j] = matrixNew[i][j];
            }
        }
    }
}

时间复杂度: O(N2)其中Nmatrix的边长。
空间复杂度: O(N2)我们需要使用一个和matrix大小相同的辅助数组。

【2】去除额外空间,使用原地旋转:我们尝试在不使用额外内存空间的情况下进行矩阵的旋转,那么如何在方法一的基础上完成原地旋转呢?

我们观察方法一中的关键等式:matrixnew​[col][n−row−1]=matrix[row][col] 它阻止了我们进行原地旋转,这是因为如果我们直接将matrix[row][col]放到原矩阵中的目标位置matrix[col][n−row−1],原矩阵中的matrix[col][n−row−1]就被覆盖了!这并不是我们想要的结果。因此我们可以考虑用一个临时变量temp暂存matrix[col][n−row−1]的值,这样虽然matrix[col][n−row−1]被覆盖了,我们还是可以通过temp获取它原来的值:

temp                 = matrix[col][n−row−1]
matrix[col][n−row−1] = matrix[row][col]

那么matrix[col][n−row−1]经过旋转操作之后会到哪个位置呢?我们还是使用方法一中的关键等式,不过这次,我们需要将

row ​= col
col ​= n−row−1​

带入关键等式,就可以得到:

matrix[n−row−1][n−col−1] = matrix[col][n−row−1]

同样地,直接赋值会覆盖掉matrix[n−row−1][n−col−1]原来的值,因此我们还是需要使用一个临时变量进行存储,不过这次,我们可以直接使用之前的临时变量temp

temp                     ​= matrix[n−row−1][n−col−1]
matrix[n−row−1][n−col−1] = matrix[col][n−row−1]
matrix[col][n−row−1]= matrix[row][col]

我们再重复一次之前的操作matrix[n−row−1][n−col−1]经过旋转操作之后会到哪个位置呢?

row ​= n−row−1
col ​= n−col−1

带入关键等式,就可以得到:matrix[n−col−1][row]=matrix[n−row−1][n−col−1]写进去:

temp                      ​= matrix[n−col−1][row]
matrix[n−col−1][row]      = matrix[n−row−1][n−col−1]
matrix[n−row−1][n−col−1]  = matrix[col][n−row−1]
matrix[col][n−row−1]      = matrix[row][col]

再来一次matrix[n−col−1][row]经过旋转操作之后回到哪个位置呢?

row ​= n−col−1
col ​= row

带入关键等式,就可以得到:matrix[row][col]=matrix[n−col−1][row]我们回到了最初的起点matrix[row][col],也就是说:

​matrix[row][col]
matrix[col][n−row−1]
matrix[n−row−1][n−col−1]
matrix[n−col−1][row]

这四项处于一个循环中,并且每一项旋转后的位置就是下一项所在的位置!因此我们可以使用一个临时变量temp完成这四项的原地交换:

temp                     ​= matrix[row][col]
matrix[row][col]         ​​= matrix[n−col−1][row]
matrix[n−col−1][row]     = matrix[n−row−1][n−col−1]
matrix[n−row−1][n−col−1] = matrix[col][n−row−1]
matrix[col][n−row−1]     = temp​

当我们知道了如何原地旋转矩阵之后,还有一个重要的问题在于:我们应该枚举哪些位置(row,col)进行上述的原地交换操作呢?由于每一次原地交换四个位置,因此:
【1】当n为偶数时,我们需要枚举n^2/4=(n/2)×(n/2)个位置,可以将该图形分为四块,以4×4的矩阵为例:保证了不重复、不遗漏;

【2】当n为奇数时,由于中心的位置经过旋转后位置不变,我们需要枚举(n^2−1)/4=((n−1)/2)×((n+1)/2)个位置,需要换一种划分的方式,以5×5的矩阵为例:同样保证了不重复、不遗漏,矩阵正中央的点无需旋转。

class Solution {
    public void rotate(int[][] matrix) {
        int n = matrix.length;
        for (int i = 0; i < n / 2; ++i) {
            for (int j = 0; j < (n + 1) / 2; ++j) {
                int temp = matrix[i][j];
                matrix[i][j] = matrix[n - j - 1][i];
                matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1];
                matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1];
                matrix[j][n - i - 1] = temp;
            }
        }
    }
}

时间复杂度: O(N^2)其中Nmatrix的边长。我们需要枚举的子矩阵大小为O(⌊n/2⌋×⌊(n+1)/2⌋)=O(N^2)
空间复杂度: O(1)为原地旋转。

【3】用翻转代替旋转: 我们还可以另辟蹊径,用翻转操作代替旋转操作。我们还是以题目中的示例二

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

作为例子,先将其通过水平轴翻转得到:

[5  1  9  11]                [15 14 12 16]
[2  4  8  10]    -->反转后    [13 3  6  7] 
[13 3  6  7 ]                [2  4  8  10] 
[15 14 12 16]                [5  1  9  11] 

再根据主对角线翻转得到:

[5  1  9  11]                [15 13 2  5]
[2  4  8  10]    -->反转后    [14 3  4  1] 
[13 3  6  7 ]                [12 6  8  9] 
[15 14 12 16]                [16 7 10  11] 

就得到了答案。这是为什么呢?对于水平轴翻转而言,我们只需要枚举矩阵上半部分的元素,和下半部分的元素进行交换,即matrix[row][col]水平轴翻转​matrix[n−row−1][col]对于主对角线翻转而言,我们只需要枚举对角线左侧的元素,和右侧的元素进行交换,即matrix[row][col]主对角线翻转​matrix[col][row]将它们联立即可得到:

matrix[row][col]  ​水平轴翻转     ​matrix[n−row−1][col]
                  主对角线翻转   ​matrix[col][n−row−1]

和方法一、方法二中的关键等式:matrixnew​[col][n−row−1]=matrix[row][col]是一致的。

class Solution {
    public void rotate(int[][] matrix) {
        // 思想:翻转代替旋转,先上下翻转在对角线翻转
        int n = matrix.length;
        // 上下翻转
        for (int i = 0; i < n/2; i++) {
            for (int j = 0; j < n; j++) {
                int temp = matrix[n-1-i][j];
                matrix[n-1-i][j] = matrix[i][j];
                matrix[i][j] = temp;
            }
        }

        // 对角线翻转
        for (int i = 0; i < n; i++) {
            for (int j = 0; j <= i; j++) {
                int temp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = temp;
            }
        }
    }
}

时间复杂度:O(N^2),其中Nmatrix的边长。对于每一次翻转操作,我们都需要枚举矩阵中一半的元素。
空间复杂度: O(1)。为原地翻转得到的原地旋转。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1171394.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

perl列表创建、追加、删除

简介 perl 列表追加元素 主要是通过push和unshift函数来实现。其中&#xff0c;push是追加到列表尾&#xff0c;unshift是追加到列表头。 perl列表删除元素 主要是通过pop和shift函数来实现。其中&#xff0c;pop是从列表尾删除一个元素&#xff0c; shift是从列表头删除一…

以配置的方式开关axios拦截器功能

前景提要&#xff1a; ts 简易封装 axios&#xff0c;统一 API 背景 axios 很多额外功能都是基于拦截器实现。有些功能想要全局使用&#xff0c;因此将拦截器注册在全局。比如重复请求过滤。但也有一小部分请求不希望进行过滤&#xff0c;比如并发上传文件。 因此希望可以在…

贰[2],OpenCV函数解析

1&#xff0c;imread&#xff1a;图片读取 CV_EXPORTS_W Mat imread( const String& filename, int flags IMREAD_COLOR );//参数1(filename)&#xff1a;文件地址 //参数2(flags):读取标志 注:ImreadModes&#xff0c;参数2(flags)枚举定义 enum ImreadModes { IMREAD…

SOEM源码解析——ec_init(初始化单网卡主站)

0 工具准备 1.SOEM-master-1.4.0源码1 ec_init总览 /** Initialise lib in single NIC mode:初始化库在单网卡模式* param[in] ifname Dev name, f.e. "eth0" 设备名* return >0 if OK* see ecx_init*/ int ec_init(const char * ifname) {return ecx_init(&…

构建高性能嵌入式系统的技术要求

目录 关键的技术要求 硬件设计 嵌入式操作系统&#xff08;Embedded Operating System&#xff09; 内存管理 电源管理 通信接口 实时性能 软件优化 安全性 关键的技术要求 硬件设计&#xff1a; 处理器选择&#xff1a; 选择适当的处理器架构&#xff0c;考虑性能、功…

点大商城V2版 2.5.2.1 全开源独立版 多小程序端+unipp安装教程

点大商城V2是一款采用全新界面设计支持多端覆盖的小程序应用&#xff0c;支持H5、微信公众号、微信小程序、头条小程序、支付宝小程序、百度小程序&#xff0c;本程序是点大商城V2独立版&#xff0c;包含全部插件&#xff0c;代码全开源&#xff0c;并且有VUE全端代码。分销&am…

MMDetection 系列(一): 初步入门配置文件

参考文档 不得不知的 MMDetection 学习路线(个人经验版) 轻松掌握 MMDetection 整体构建流程(一) 学习配置文件 MaskRCNN 配置文件的逻辑 整体来说&#xff0c;配置文件包含以下几个部分&#xff1a; 数据集和评测器配置模型配置训练和测试的配置优化相关配置钩子配置(TODO…

Web测试与APP测试详解

前言 单纯从功能测试的层面上来讲的话&#xff0c;App 测试、Web 测试在流程和功能测试上是没有区别的&#xff0c;但由于系统结构方面存在差异&#xff08;web 项目&#xff0c;b/s 架构&#xff1b;app 项目&#xff0c;c/s 结构&#xff09;在测试中还是有不同的侧重点内容…

使用opencv和dlib库(C++代码)实现人脸活体检测(眨眼、张嘴、摇头检测)

前言 本文章使用opencv和dlib库,使用C++代码实现了人脸活体检测,包括眨眼检测、张嘴检测以及摇头检测,可以对静态图片和活体进行有效区分。效果展示 Dlib库介绍 dlib是一个开源的C++机器学习库,它提供了一系列用于图像处理、人脸检测、人脸识别、物体检测、图像标注等功能的…

stable diffusion公司发布4款LLM大语言模型,为何大家都喜爱LLM?

stable diffusion模型是Stability AI开源的一个text-to-image的扩散模型&#xff0c;其模型在速度与质量上面有了质的突破&#xff0c;玩家们可以在自己消费级GPU上面来运行此模型&#xff0c;本模型基于CompVis 和 Runway 团队的Latent Diffusion Models。本期我们不介绍stabl…

口才不好可以做管理岗吗?

口才不好也可以选择管理岗位 口才是很重要的职场技能之一&#xff0c;但是并不代表没有口才就不能从事管理工作。管理岗位需要的不仅是口才&#xff0c;还包括组织能力、决策能力、问题解决能力等多种素质。下面我从以下几个方面来分析一下&#xff0c;口才不好的人也有可能选…

MySQL(8):聚合函数

聚合函数介绍 聚合函数&#xff1a; 对一组数据进行汇总的函数&#xff0c;输入的是一组数据的集合&#xff0c;输出的是单个值。 聚合函数类型&#xff1a;AVG(),SUM(),MAX(),MIN(),COUNT() AVG / SUM 只适用于数值类型的字段&#xff08;或变量&#xff09; SELECT AVG(…

C++prime之输入输出文件

作为一种优秀的语言&#xff0c;C必然是能操作文件的&#xff0c;但是我们要知道&#xff0c;C是不直接处理输入输出的&#xff0c;而是通过一族定义在标准库中的类型来处理IO的。 ‘流’和‘缓冲区’ ‘流’和‘缓冲区’ C程序把输入输出看作字节流&#xff0c;并且其只检查…

【数据集处理】基于Python处理EAR5数据

基于Python处理EAR5数据 1 EAR5数据简介2 数据集处理准备工作&#xff1a;xarray库安装2.1 数据预处理-剔除异常值 参考 1 EAR5数据简介 ERA5是ECMWF&#xff08;欧洲中期天气预报中心&#xff09;对1950年1月至今全球气候的第五代大气再分析数据集。 包含了四个基本变量(日…

[SSD综述 1.4] SSD固态硬盘的结构和原理导论

依公知及经验整理,原创保护,禁止转载。 专栏 《SSD入门到精通系列》 <<<< 返回总目录 <<<< ​ 前言 机械硬盘的存储系统由于内部结构, 其IO访问性能无法进一步提高,CPU与存储器之间的性能差距逐渐扩大。以Nand Flash为存储介质的固态硬盘技术的发展,…

【Python实例】netCDF数据介绍及处理

netCDF数据处理 netCDF数据介绍1 netCDF数据结构1.1 维度(Dimensions)1.2 变量&#xff08;Variables&#xff09;1.3 坐标变量&#xff08;Coordinate Variables)1.4 属性&#xff08;Attributes&#xff09; 2 netCDF数据处理方法1&#xff1a;利用netCDF4打开nc格式数据2.1.…

Python基础入门例程33-NP33 乘法与幂运算(运算符)

最近的博文&#xff1a; Python基础入门例程32-NP32 牛牛的加减器&#xff08;运算符&#xff09;-CSDN博客 Python基础入门例程31-NP31 团队分组&#xff08;列表&#xff09;-CSDN博客 Python基础入门例程30-NP30 用列表实现队列&#xff08;列表&#xff09;-CSDN博客 目…

水经微图Web版从入门到精通

我们在《47GB水经微图从入门到精通视频教程》和《163M水经微图从入门到精通文档教程》中&#xff0c;为大家分享了水经微图PC版的教程。 这里&#xff0c;我们再为大家分享水经微图Web版的文档教程。 水经微图Web版教程 水经微图Web版的教程&#xff0c;主要包括基础名词、…

【日常记录】关于LinkedHashMap中key为数字字符串根据compareTo排序的问题

今天在修复Bug的时候&#xff0c;在项目中发现有这样一段代码&#xff08;为了方便&#xff0c;下面用一个例子进行演示&#xff09;。 项目中使用了LinkedHashMap key为Long类型&#xff0c;value为String类型。 并且&#xff0c;LinkedHashMap 使用Stream流 对key进行升序的排…

Java web(六):FilterListenerAJAX

文章目录 一、Filter1.1 基本介绍1.2 过滤器的执行流程1.3 拦截路径配置1.4 过滤器链1.5 案例 二、Listener三、AJAX3.1 快速入门3.2 Axios异步框架 四、 JSON4.1 JSON基础语法4.2 Fastjson 五、 案例JSONAxiosServlet Java web的三大组件&#xff1a;Servlet、Filter、Listene…