AcWing 756. 蛇形矩阵

news2024/10/5 19:18:47

目录

  • 问题描述
  • 思路与代码
    • 1. 个人解法
    • 2. 官方题解

问题描述

原题链接🔗:756. 蛇形矩阵

输入两个整数 n n n m m m,输出一个 n n n m m m 列的矩阵,将数字 1 1 1 n × m n×m n×m 按照回字蛇形填充至矩阵中。

具体矩阵形式可参考样例。

输入格式
输入共一行,包含两个整数 n n n m m m

输出格式
输出满足要求的矩阵。

矩阵占 n n n 行,每行包含 m m m 个空格隔开的整数。

数据范围
1 ≤ n , m ≤ 100 1≤n,m≤100 1n,m100

输入样例:

3 3

输出样例:

1 2 3
8 9 4
7 6 5

思路与代码

1. 个人解法

观察题目不难发现,填充方向为顺时针并且是一圈一圈往内填的,如下图所示:

填完红圈之后,开始填蓝圈,最后填绿圈,当发现填完的数字等于 n ⋅ m n\cdot m nm 后,结束填充。

无论是哪种颜色的圈,当位于上边界的时候,移动方向总是向右的;当位于右边界的时候,移动方向总是向下的;当位于下边界的时候,移动方向总是向左的;当位于左边界的时候,移动方向总是向上的。

以最外圈(红圈)为例,我们可以将其划分成四个区域:

区域1上的点满足 i = 0 , j < m − 1 i=0,j<m-1 i=0,j<m1,且在该区域上向右移动;区域2上的点满足 i < n − 1 , j = m − 1 i<n-1,j=m-1 i<n1,j=m1,且在该区域上向下移动;区域3上的点满足 i = n − 1 , j > 0 i=n-1,j>0 i=n1,j>0,且在该区域上向左移动;区域4上的点满足 i > 0 , j = 0 i>0,j=0 i>0,j=0,且在该区域上向上移动。

当填充完外圈后,我们如何进入内圈呢?很简单,只需要将数组进行全0初始化,如果一圈还没有填充完,则下一个填充位置必然为0,如果刚好填完了一圈,则下一个填充位置必不为0。以红圈为例,当填充完该圈后,下一个填充位置为区域1的第一个方格处,即 i = 0 , j = 0 i=0,j=0 i=0,j=0,此时我们需要对其矫正,即执行 i++, j++(对应上图中的红色箭头),这样一来,下一个填充位置就变成内圈的起始处( i = 1 , j = 1 i=1,j=1 i=1,j=1)。

在内圈上移动时,我们需要缩减边界,可以理解为“缩圈”,此时四个区域上的点满足:
region 1 :    i = 1 ,    j < m − 2 region 2 :    i < n − 2 ,    j = m − 2 region 3 :    i = n − 2 ,    j > 1 region 4 :    i > 1 ,    j = 1 \begin{aligned} &\text{region}_1:\;i=1,\;j<m-2 \\ &\text{region}_2:\;i<n-2,\;j=m-2 \\ &\text{region}_3:\;i=n-2,\;j>1 \\ &\text{region}_4:\;i>1,\;j=1 \\ \end{aligned} region1:i=1,j<m2region2:i<n2,j=m2region3:i=n2,j>1region4:i>1,j=1

填充结束当且仅当刚刚填充的数字为 n ⋅ m n\cdot m nm

AC代码:

#include <iostream>

using namespace std;

int a[100][100];  // 全局变量自动初始化为0

int main() {
    int n, m;
    cin >> n >> m;
    int bound[] = {0, n - 1, 0, m - 1};  // 分别对应上、下、左、右边界

    for (int i = 0, j = 0, k = 1; k <= n * m; k++) {
        a[i][j] = k;
        if (k == n * m) break;  // 填充结束

        // 否则,移动到下一个填充位置
        if (i == bound[0] && j < bound[3]) j++;
        else if (i < bound[1] && j == bound[3]) i++;
        else if (i == bound[1] && j > bound[2]) j--;
        else if (i > bound[0] && j == bound[2]) i--;

        // 如果下一个填充位置已经填过了,则说明此时已经填完一圈了,此时应当缩小边界
        if (a[i][j]) {
            i++, j++;  // 矫正新的一圈的起始位置
            bound[0]++, bound[1]--, bound[2]++, bound[3]--;  // 缩圈
        }
    }

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++)
            cout << a[i][j] << " ";
        cout << endl;
    }

    return 0;
}

2. 官方题解

考虑使用方向数组。

因为在填充过程中移动方向只有四种:上、下、左、右,所以方向数组的大小为 4 4 4。设某一方格的坐标(准确来讲应当是索引)为 ( x , y ) (x,y) (x,y),则其上、下、左、右四个方格的坐标分别为 ( x − 1 , y ) ,    ( x + 1 , y ) ,    ( x , y − 1 ) ,    ( x , y + 1 ) (x-1,y),\;(x+1,y),\;(x,y-1),\;(x,y+1) (x1,y),(x+1,y),(x,y1),(x,y+1),由此可得到上、下、左、右这四个方向分别为 ( − 1 , 0 ) ,    ( 1 , 0 ) ,    ( 0 , − 1 ) ,    ( 0 , 1 ) (-1, 0),\;(1,0),\;(0,-1),\;(0,1) (1,0),(1,0),(0,1),(0,1)

例如当前坐标为 ( x , y ) (x,y) (x,y),向左移动一步后得到 ( x ′ , y ′ ) (x',y') (x,y),则坐标更新公式为

( x ′ , y ′ ) = ( x , y ) + ( 0 , − 1 ) = ( x , y − 1 ) (x',y')=(x,y)+(0,-1)=(x,y-1) (x,y)=(x,y)+(0,1)=(x,y1)

dx = [-1, 0, 1, 0]dy = [0, 1, 0, -1],则 (dx[d], dy[d]) 则代表某一方向。其中 dxdy 分别是偏移量数组,简便起见,称索引 d 为方向。不难看出,在这种定义下, d = 0 d=0 d=0 代表向上, d = 1 d=1 d=1 代表向右, d = 2 d=2 d=2 代表向下, d = 3 d=3 d=3 代表向左。

在这种定义下,坐标的更新公式为

( x ′ , y ′ ) = ( x , y ) + ( d x [ d ] , d y [ d ] ) , d ∈ { 0 , 1 , 2 , 3 } (x',y')=(x,y)+(dx[d],dy[d]),\quad d\in\{0,1,2,3\} (x,y)=(x,y)+(dx[d],dy[d]),d{0,1,2,3}

在填充的过程中,方向的变化为(初始时向右)

1 → 2 → 3 → 0 → 1 → 2 → ⋯ 1\to2\to3\to0\to 1\to2\to\cdots 123012

由此可得方向的更新公式为 d = (d + 1) % 4。那什么时候更新方向呢?无外乎两种情况:

  • 下一个要填充的位置越界(只有在填充最外圈的时候才会遇到);
  • 下一个要填充的位置已经被填充过。
#include <iostream>

using namespace std;

int q[100][100];

int main() {
    int n, m;
    cin >> n >> m;
    int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
    int x = 0, y = 0, d = 1;  // 初始位于(0, 0),方向向右

    for (int i = 1; i <= n * m; i++) {
        q[x][y] = i;
        int a = x + dx[d], b = y + dy[d];  // 获取下一个要填充的位置
        if (a < 0 || a >= n || b < 0 || b >= m || q[a][b]) {
            d = (d + 1) % 4;
            a = x + dx[d], b = y + dy[d];  // 矫正下一个要填充的位置
        }
        x = a, y = b;
    }

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++)
            cout << q[i][j] << ' ';
        cout << endl;
    }

    return 0;
}

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

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

相关文章

synchronized底层原理

synchronized是JVM内置锁&#xff0c;基于monitor机制实现&#xff0c;依赖底层操作系统的互斥源语Mutex&#xff08;互斥量&#xff09;&#xff0c;它是一个重量级锁&#xff0c;性能较低。 当然&#xff0c;JVM内置锁在1.5之后版本做了重大优化&#xff0c;如锁粗化&#xf…

[红明谷CTF 2021]JavaWeb

0x01 好久没打过ctf了&#xff0c;最近也在学Java 就看下java的题吧 WP 进入环境就提示访问 /login &#xff0c;访问之后 提示的 /json ,试着访问一下 给了 jessid&#xff0c;继续访问 /json 的话 又会跳转至 /login 应该是要传点username password 果然是了&#…

web制作网页

HTML(人的骨架): 标签 1.超文本标记性语言 2.当前最新版HTML5 3.URL统一资源定位器&#xff08;网址&#xff09; 4.网站首页名称index.html&#xff0c;default.htm&#xff0c;main.heml 基本结构&#xff1a; 注意&#xff1a;1.标签都是小写 2.标签成对写 3.代码要层次缩进…

JavaScript内存管理

JavaScript 是一个弱类型的、动态语言&#xff0c;在执行一段 JS 代码时&#xff0c;需要经历编译、执行、内存回收阶段。 一、编译阶段 JS 代码执行时&#xff0c;会首先创建全局执行上下文、以及函数执行上下文&#xff0c;上下文的执行顺序按照栈的方式进行调用&#xff0c…

面试题: JVM内存结构

目录目的资源概览JVM内存溢出分类类型1: OutOfMemoryError类型2: StackOverflowError方法区与永久代、元空间之间的关系目的 学习记录, 面试准备 资源 B站的一个讲高频面试题的一个学习视频 概览 线程私有 ① 程序计数器 ② 虚拟机栈线程共享 ① 堆 ② 方法区 JVM内存溢…

RSA密钥协商过程

目录 TSL握手过程 RSA密钥协商握手过程 TLS第一次握手 TLS第二次握手 客户端验证证书 TLS第三次握手 TLS 第四次握手 RSA 算法的缺陷 TSL握手过程 HTTP 由于是明文传输&#xff0c;所谓的明文&#xff0c;就是说客户端与服务端通信的信息都是肉眼可见的&#xff0c;随…

linux常用指令讲解

文章目录 前言一、指令详解总结前言 Linux是一种自由和开放源代码的类UNIX操作系统&#xff0c;该操作系统的内核由林纳斯托瓦兹在1991年首次发布&#xff0c;之后&#xff0c;在加上用户空间的应用程序之后&#xff0c;就成为了Linux操作系统。严格来讲&#xff0c;Linux只是操…

分布式锁的实现

目录分布式锁分布式锁的引出单体锁存在的问题分布式锁的引出分布式锁的设计思路分布式锁的常见应用场景分布式锁方案分布式锁 分布式锁的引出 单体锁存在的问题 在单体应用中&#xff0c;如果我们对共享数据不进行加锁操作&#xff0c;多线程操作共享数据时会出现数据一致性问…

【Kuangbin简单DP】平整数组

4562. 平整数组 - AcWing题库 题意&#xff1a; 思路&#xff1a; 一开始想的是 设DP状态是&#xff1a;dp[i][0/1]表示阶段到 i &#xff0c;然后前面是递增or递减的最小sum的集合 然后我是怎么转移的呢&#xff0c;现在看来感觉挺好笑的&#xff1a; #include <bits/…

Golang 函数使用的注意事项和细节

来自&#xff1a;尚硅谷-韩老师 尚硅谷 1&#xff09;函数的形参列表可以是多个&#xff0c;返回值列表也可以是多个 2&#xff09;形参列表和返回值列表的数据类型可以是值类型和引用类型 3&#xff09;函数的命名遵循标识符命名规范&#xff0c;首字母不能是数字&#xf…

京东购物成功订单已开具个人发票不能报销怎么申请更换重新开具企业发票用于报销?

原文来源&#xff1a;https://www.caochai.com/article-4109.html 京东购物订单的发票开成个人了可以更换成企业发票吗&#xff1f; 可以&#xff0c;通过京东购物完成的订单默认开个人发票&#xff0c;如果对发票有要求需要开企业发票的可以申请更换重新开企业发票&#xff1…

一文读懂JVM虚拟机:JVM虚拟机的内存管理(万字详解)

JVM虚拟机的内存管理 文章目录JVM虚拟机的内存管理JVM与操作系统Java虚拟机规范和 Java 语言规范的关系java虚拟机的内存管理JVM整体架构一、PC 程序计数器二、虚拟机栈三、本地方法栈四、堆Java 堆概念年轻代和老年代对象分配过程堆GC元空间为什么要废弃永久代&#xff0c;引入…

动手学习深度学习-《矩阵运算》

标量导数 常用求导&#xff1a; yyyaaaxnx^nxnexp(x)exp(x)exp(x)log(x)log(x)log(x)sin(x)sin(x)sin(x)dydx\frac{dy}{dx}dxdy​000nxn−1nx^{n-1}nxn−1exp(x)1x\frac{1}{x}x1​cos(x)cos(x)cos(x) 求导公式&#xff1a; yyyuvuvuvuvuvuvyf(u),ug(x)yf(u),ug(x)yf(u),ug(x)d…

植物大战僵尸:学会使用人造指针

通过向游戏中注入一段特殊的汇编代码&#xff0c;实现自动获取动态地址&#xff0c;省略找基址的麻烦。该方法适用于游戏基址层数过多无法直接获取到基址&#xff0c;游戏根本无法找到基址。 1.打开CE工具并附加游戏进程&#xff0c;首先通过遍历的方式找到阳光的动态地址&…

zookeeper之master选举代码实现

master选举的基本概念 &#xff08;1&#xff09;假设有一个系统A,它向外提供了一个服务&#xff0c;叫做服务B。并且这个服务需要24小时持续不断的向外提供。也就是提供服务的机器不能够有单点故障。于是我们考虑使用集群。 &#xff08;2&#xff09;我们采用的是master-sla…

傻白入门芯片设计,如何做文献笔记(十九)

Article: 文献出处&#xff08;方便再次搜索&#xff09; 作者文献题目文献时间Data: 文献数据&#xff08;总结归纳&#xff0c;方便理解&#xff09; 这篇文章的目的结论背景介绍结果方法&#xff08;可选&#xff09;Comments: 对文献的想法 &#xff08;强迫自己思考&#…

Java人脸识别相册分类按时间分类相册按城市分类相册app源码

简介 后台Java&#xff0c;前台mui开发的android app&#xff1b;主要是按拍摄时间&#xff0c;人脸&#xff0c;城市进行相册照片的分类。 演示视频 https://www.bilibili.com/video/BV1XP4y187rA/?share_sourcecopy_web&vd_sourceed0f04fbb713154db5cc611225d92156 技…

MySQL调优-Explain详解和索引最佳实践

目录 Explain工具介绍 Explain分析示例 explain 两个变种 explain中的列 1.id列 2.select_type列 3. table列 4.type列 5. possible_keys列 6. key列 7. key_len列 8. ref列 9. rows列 10.Extra列 索引最佳实践 1.全值匹配 2.最左前缀法则 3.不在索引列上做任何操…

2D 平台动作冒险游戏

本文实现比较流畅的跑和跳跃 跑的动画需要从idle经历到walk再到run的过程&#xff0c;这个过程可以用融合树实现 也可以让玩家在按下按键时先固定播放完一个walk的动画&#xff0c;然后再自动切换到run的状态。 只不过在任何状态时&#xff0c;只要玩家松开了按键&#xff0c;…

钧瓷产业数字化,将促使禹州走向更高级的社会形态——钧共体

讲好钧瓷产业的故事 有深度的故事,有温度的内容 有态度的文字,有立场的思考 版权声明:钧瓷内参独家发布,侵权必究 第334期 钧瓷内参 2023年1月1日 这里的上市指沪深的主板,创业板和科创板,区域的挂牌不算。 这个数据是根据禹州钧瓷产业2022…