前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >二维数组的花式遍历技巧盘点

二维数组的花式遍历技巧盘点

作者头像
labuladong
发布于 2021-12-13 09:40:30
发布于 2021-12-13 09:40:30
1.1K00
代码可运行
举报
运行总次数:0
代码可运行

有不少读者说,看过很多公众号历史文章之后掌握了框架思维,可以解决大部分有套路框架可循的题目。

但是框架思维也不是万能的,有一些特定技巧呢,属于会者不难,难者不会的类型,只能通过多刷题进行总结和积累。

那么本文我分享一些巧妙的二维数组的花式操作,你只要有个印象,以后遇到类似题目就不会懵圈了。

顺/逆时针旋转矩阵

对二维数组进行旋转是常见的笔试题,力扣第 48 题「旋转图像」就是很经典的一道:

题目很好理解,就是让你将一个二维矩阵顺时针旋转 90 度,难点在于要「原地」修改,函数签名如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void rotate(int[][] matrix)

如何「原地」旋转二维矩阵?稍想一下,感觉操作起来非常复杂,可能要设置巧妙的算法机制来「一圈一圈」旋转矩阵:

但实际上,这道题不能走寻常路,在讲巧妙解法之前,我们先看另一道谷歌曾经考过的算法题热热身:

给你一个包含若干单词和空格的字符串s,请你写一个算法,原地反转所有单词的顺序。

比如说,给你输入这样一个字符串:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
s = "hello world labuladong"

你的算法需要原地反转这个字符串中的单词顺序:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
s = "labuladong world hello"

常规的方式是把s按空格split成若干单词,然后reverse这些单词的顺序,最后把这些单词join成句子。但这种方式使用了额外的空间,并不是「原地反转」单词。

正确的做法是,先将整个字符串s反转

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
s = "gnodalubal dlrow olleh"

然后将每个单词分别反转

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
s = "labuladong world hello"

这样,就实现了原地反转所有单词顺序的目的。

我讲这道题的目的是什么呢?

旨在说明,有时候咱们拍脑袋的常规思维,在计算机看来可能并不是最优雅的;但是计算机觉得最优雅的思维,对咱们来说却不那么直观。也许这就是算法的魅力所在吧。

回到之前说的顺时针旋转二维矩阵的问题,常规的思路就是去寻找原始坐标和旋转后坐标的映射规律,但我们是否可以让思维跳跃跳跃,尝试把矩阵进行反转、镜像对称等操作,可能会出现新的突破口。

我们可以先将n x n矩阵matrix按照左上到右下的对角线进行镜像对称

然后再对矩阵的每一行进行反转

发现结果就是matrix顺时针旋转 90 度的结果

将上述思路翻译成代码,即可解决本题:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 将二维矩阵原地顺时针旋转 90 度
public void rotate(int[][] matrix) {
    int n = matrix.length;
    // 先沿对角线镜像对称二维矩阵
    for (int i = 0; i < n; i++) {
        for (int j = i; j < n; j++) {
            // swap(matrix[i][j], matrix[j][i]);
            int temp = matrix[i][j];
            matrix[i][j] = matrix[j][i];
            matrix[j][i] = temp;
        }
    }
    // 然后反转二维矩阵的每一行
    for (int[] row : matrix) {
        reverse(row);
    }
}

// 反转一维数组
void reverse(int[] arr) {
    int i = 0, j = arr.length - 1;
    while (j > i) {
        // swap(arr[i], arr[j]);
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
        i++;
        j--;
    }
}

肯定有读者会问,如果没有做过这道题,怎么可能想到这种思路呢?

其实我觉得这个思路还是挺容易想出来的,如果学过线性代数,这道算法题的思路本质就是矩阵变换,肯定可以想出来。

即便没学过线性代数,旋转二维矩阵的难点在于将「行」变成「列」,将「列」变成「行」,而只有按照对角线的对称操作是可以轻松完成这一点的,对称操作之后就很容易发现规律了。

既然说道这里,我们可以发散一下,如何将矩阵逆时针旋转 90 度呢

思路是类似的,只要通过另一条对角线镜像对称矩阵,然后再反转每一行,就得到了逆时针旋转矩阵的结果:

翻译成代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 将二维矩阵原地逆时针旋转 90 度
void rotate2(int[][] matrix) {
    int n = matrix.length;
    // 沿左下到右上的对角线镜像对称二维矩阵
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n - i; j++) {
            // swap(matrix[i][j], matrix[n-j-1][n-i-1])
            int temp = matrix[i][j];
            matrix[i][j] = matrix[n - j - 1][n - i - 1];
            matrix[n - j - 1][n - i - 1] = temp;
        }
    }
    // 然后反转二维矩阵的每一行
    for (int[] row : matrix) {
        reverse(row);
    }
}

void reverse(int[] arr) { /* 见上文 */}

至此,旋转矩阵的问题就解决了。

矩阵的螺旋遍历

我的公众号 动态规划系列文章 经常需要遍历二维dp数组,但难点在于状态转移方程而不是数组的遍历,顶多就是倒序遍历。

今天我们讲一下力扣第 54 题「螺旋矩阵」,看一看二维矩阵可以如何花式遍历:

函数签名如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<Integer> spiralOrder(int[][] matrix)

解题的核心思路是按照右、下、左、上的顺序遍历数组,并使用四个变量圈定未遍历元素的边界

随着螺旋遍历,相应的边界会收缩,直到螺旋遍历完整个数组:

只要有了这个思路,翻译出代码就很容易了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<Integer> spiralOrder(int[][] matrix) {
    int m = matrix.length, n = matrix[0].length;
    int upper_bound = 0, lower_bound = m - 1;
    int left_bound = 0, right_bound = n - 1;
    List<Integer> res = new LinkedList<>();
    // res.size() == m * n 则遍历完整个数组
    while (res.size() < m * n) {
        if (upper_bound <= lower_bound) {
            // 在顶部从左向右遍历
            for (int j = left_bound; j <= right_bound; j++) {
                res.add(matrix[upper_bound][j]);
            }
            // 上边界下移
            upper_bound++;
        }

        if (left_bound <= right_bound) {
            // 在右侧从上向下遍历
            for (int i = upper_bound; i <= lower_bound; i++) {
                res.add(matrix[i][right_bound]);
            }
            // 右边界左移
            right_bound--;
        }

        if (upper_bound <= lower_bound) {
            // 在底部从右向左遍历
            for (int j = right_bound; j >= left_bound; j--) {
                res.add(matrix[lower_bound][j]);
            }
            // 下边界上移
            lower_bound--;
        }

        if (left_bound <= right_bound) {
            // 在左侧从下向上遍历
            for (int i = lower_bound; i >= upper_bound; i--) {
                res.add(matrix[i][left_bound]);
            }
            // 左边界右移
            left_bound++;
        }
    }
    return res;
}

力扣第 59 题「螺旋矩阵 II」也是类似的题目,只不过是反过来,让你按照螺旋的顺序生成矩阵:

函数签名如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int[][] generateMatrix(int n)

有了上面的铺垫,稍微改一下代码即可完成这道题:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int[][] generateMatrix(int n) {
    int[][] matrix = new int[n][n];
    int upper_bound = 0, lower_bound = n - 1;
    int left_bound = 0, right_bound = n - 1;
    // 需要填入矩阵的数字
    int num = 1;

    while (num <= n * n) {
        if (upper_bound <= lower_bound) {
            // 在顶部从左向右遍历
            for (int j = left_bound; j <= right_bound; j++) {
                matrix[upper_bound][j] = num++;
            }
            // 上边界下移
            upper_bound++;
        }

        if (left_bound <= right_bound) {
            // 在右侧从上向下遍历
            for (int i = upper_bound; i <= lower_bound; i++) {
                matrix[i][right_bound] = num++;
            }
            // 右边界左移
            right_bound--;
        }

        if (upper_bound <= lower_bound) {
            // 在底部从右向左遍历
            for (int j = right_bound; j >= left_bound; j--) {
                matrix[lower_bound][j] = num++;
            }
            // 下边界上移
            lower_bound--;
        }

        if (left_bound <= right_bound) {
            // 在左侧从下向上遍历
            for (int i = lower_bound; i >= upper_bound; i--) {
                matrix[i][left_bound] = num++;
            }
            // 左边界右移
            left_bound++;
        }
    }
    return matrix;
}

至此,两道螺旋矩阵的题目也解决了。

以上就是遍历二维数组的一些技巧,其他数组技巧可参见之前的文章 前缀和数组差分数组数组双指针算法集合;链表相关技巧可参见 单链表六大算法技巧汇总

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 labuladong 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
实现页面静态化,PHP是如何实现的,你又是如何实现的
随着网站的内容的增多和用户访问量的增多,无可避免的是网站加载会越来越慢,受限于带宽和服务器同一时间的请求次数的限制,我们往往需要在此时对我们的网站进行代码优化和服务器配置的优化。 一般情况下会从以下方面来做优化 1、动态页面静态化 2、优化数据库 3、使用负载均衡 4、使用缓存 5、使用CDN加速 现在很多网站在建设的时候都要进行静态化的处理,为什么网站要进行静态化处理呢?我们都知道纯静态网站是所有的网页都是独立的一个html页面,当我们访问的时候不需要经过数据的处理直接就能读取到文件,访问速度就可想而知了,而其对于搜索引擎而言也是非常友好的一个方式。 纯静态网站在网站中是怎么实现的? 纯静态的制作技术是需要先把网站的页面总结出来,分为多少个样式,然后把这些页面做成模板,生成的时候需要先读取源文件然后生成独立的以.html结尾的页面文件,所以说纯静态网站需要更大的空间,不过其实需要的空间也不会大多少的,尤其是对于中小型企业网站来说,从技术上来讲,大型网站想要全站实现纯静态化是比较困难的,生成的时间也太过于长了。不过中小型网站还是做成纯静态的比较,这样做的优点是很多的。 而动态网站又是怎么进行静态处理的? 页面静态化是指将动态页面变成html/htm静态页面。动态页面一般由asp,php,jsp,.net等程序语言编写而成,非常便于管理。但是访问网页时还需要程序先处理一遍,所以导致访问速度相对较慢。而静态页面访问速度快,却又不便于管理。那么动态页面静态化即可以将两种页面的好处集中到一起。 静态处理后又给网站带来了哪些好处? 1、静态页面相对于动态页面更容易被搜索引擎收录。 2、访问静态页面不需要经过程序处理,因此可以提高运行速度。 3、减轻服务器负担。 4、HTML页面不会受Asp相关漏洞的影响。 静态处理后的网站相对没有静态化处理的网站来讲还比较有安全性,因为静态网站是不会是黑客攻击的首选对象,因为黑客在不知道你后台系统的情况下,黑 客从前台的静态页面很难进行攻击。同时还具有一定的稳定性,比如数据库或者网站的程序出了问题,他不会干扰到静态处理后的页面,不会因为程序或数据影响而 打不开页面。 搜索引擎蜘蛛程序更喜欢这样的网址,也可以减轻蜘蛛程序的工作负担,虽然有的人会认为现在搜索引擎完全有能力去抓取和识别动态的网址,在这里还是建议大家能做成静态的尽量做成静态网址。 下面我们主要来讲一讲页面静态化这个概念,希望对你有所帮助! 什么是HTML静态化
友儿
2022/09/11
1.5K0
再聊缓存技术
对于现在的各种系统来说,缓存的应用无处不在。如果能合理的利用缓存,整个系统的性能将会得到大大的提高,Web开发尤其如此。一般高并发大访问量的应用,主要压力都在服务器端,所以服务器端的性能至关重要,缓存的使用,很多时候是有决定性影响的。
后端技术探索
2018/08/10
6650
老雷PHP全栈开发教程之PHP缓存的使用
缓存的作用就是减少对数据的处理,增加网站的性能。适用于非实时需求的数据。 课件内容: 一、页面缓存 新闻类的 很少会更新的内容 将整个页面缓存起来 html静态页 <?php ob_start()
老雷PHP全栈开发
2020/07/02
4850
Apc缓存Opcode
由于PHP是个解释型语言执行的时候先得把程序读进来,然后由Zend引擎编译成opcode。最后Zend虚拟机顺次执行这些opcode(指令)完成操作。因此我们可 以把这个Opcode缓存起来,下次就能避免重新编译了。 APC缓存作用如下:
黄规速
2022/04/14
6090
Apc缓存Opcode
企业级memcached缓存数据库结合php使用与web管理memcached
环境 [root@cache01 ~]# cat /etc/redhat-release CentOS Linux release 7.4.1708 (Core) [root@cache01 ~]# uname -a Linux cache01 3.10.0-693.el7.x86_64 #1 SMP Tue Aug 22 21:09:27 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux 前言:转载请注明出处。。。 memcached介绍    官方:http://mem
863987322
2018/01/24
1.4K0
企业级memcached缓存数据库结合php使用与web管理memcached
数据库专题(四) ——各类缓存技术
数据库专题(四) ——各类缓存技术 (原创内容,转载请注明来源,谢谢) 一、概述 缓存(Cache)技术原指高速数据,当CPU处理数据的时候,会先去缓存里面找,有的话就直接返回,不用再去RAM取数据。但是现在缓存已经不仅指cpu的操作了,而在程序中更多的是指内存和硬盘之间的缓存。凡是速度差距较大的两者,有介于中间的速度差异的结构,均可以称为用cache。速度排序,CPU>内存>硬盘,因此cpu到内存、内存到硬盘都有缓存。 1、优势 缓存利用相对高速的速度减少介质交互、低速操作等,例如减少网络I/O、减少
用户1327360
2018/03/07
1.2K0
你应该这个姿势学习php(3)
输出缓冲区的内容,如果你想获取缓冲区的内容要在这个函数之前使用ob_get_contents(),不然数据将会被清空
思梦php
2018/04/16
8909
你应该这个姿势学习php(3)
深入理解php的apc
apc定义:apc是一个开放自由的php opcode缓存。它的目标是提供一个自由、开放和健全的框架,用于缓存和优化php中间代码。apc常用函数:1.apc_clear_cache() 清楚apc缓存内容2.apc_define_constants(string key,array constants,[,bool case_sensitive]) 将数组constants以常量加入缓存3.apc_load_constants(string key) 取出常量缓存4.apc_store(strin
sunsky
2020/08/20
9030
openresty 页面静态化及多级缓存
这里是 redis 和 Ehcache的Java代码缓存方式:不细致讲解可以了解:点击
Java_慈祥
2024/08/06
2210
openresty 页面静态化及多级缓存
认识高性能Web缓存体系,你需要知道这些
前言 我们再看知识体系的时候,我们学一个东西的时候,每次我们都回过头去看一看,这就是所谓的不忘初心。这个说着容易做起来难,当一个人慢慢在成长,在进步的时候,是很难做到不忘初心的。 我们之前说了DNS缓存、浏览器缓存(维护了这么久的服务器,你真的认识 Web 缓存体系?),所以浏览器就是我们安排在千家万户缓存代理服务器,你把浏览器缓存用好,性能就不用说。 为什么这么说?如果遇到关于session或cookie的过期时间这样的问题,浏览器都不会向服务器发送连接请求。它直接用浏览器本地缓存就打开了,你说它快还是不
DevOps时代
2018/02/02
1.5K0
认识高性能Web缓存体系,你需要知道这些
深入探究Smarty模版
Smarty是一个使用PHP写出来的模板引擎,是目前业界最著名的PHP模板引擎之一。它分离了逻辑代码和外在的内容,提供了一种易于管理和使用的方法,用来将原本与HTML代码混杂在一起PHP代码逻辑分离
Java架构师必看
2021/03/22
6.5K0
深入探究Smarty模版
PHP实现智能的自动缓存
PHP实现自动化缓存的功能,这个感觉不错,挺好用的,只需要直接把这个php文件引入到需要缓存的页面即可实现get请求的页面缓存;用着感觉不错就分享出来了;
wordpress建站吧
2019/12/06
1.2K0
服务器高并发负载解决方案
web资源防盗链 盗链是什么? 为什么要防? 在自己页面上显示一些不是自己服务器的资源(图片、音频、视频、css、js等) 由于别人盗链你的资源会加重你的服务器负担,所以我们需要防止 可能会影响统计 防盗链是什么? 有哪几种方式? 防止别人通过一些技术手段绕过本站的资源展示页,盗用本站资源,让绕开本站资源展示页面的资源链接失效 大大减轻服务器压力 1、Referer (易伪造referer,安全性低) 2、加密签名 (安全性高) 防盗链的工作原理 通过Referer,服务器可以检测到访问目标资
黄啊码
2020/05/29
2.3K0
服务器高并发负载解决方案
部署LNMP动静分离并搭建memcache缓存服务器
一、MemCache简介 MemCache 是一个自由、源码开放、高性能、分布式的分布式内存对象缓存系统,用于动态Web应用以减轻数据库的负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高了网站访问的速度。 MemCaChe 是一个存储键值对的 HashMap,在内存中对任意的数据(比如字符串、对象等)所使用的 key-value 存储,数据可以来自数据库调用、API调用,或者页面渲染的结果。MemCache 设计理念就是小而强大,它简单的设计促进了快速部署、易于开发并解决面对大规模的数据缓存的许多难题,而所开放的 API 使得 MemCache用于 Java、C/C++/C#、Perl、Python、PHP、Ruby 等大部分流行的程序语言。 另外,说一下为什么会有 Memcache 和 memcached 两种名称?其实 Memcache 是这个项目的名称(也时它客户端的名称),而 memcached 是它服务器端的主程序文件名。
小手冰凉
2020/02/17
1K1
thinkphp缓存技术
如果没有缓存的网站是百万级或者千万级的访问量,会给数据库或者服务器造成很大的压力,通过缓存,大幅减少服务器和数据库的负荷。假如我们把读取数据的过程分为三个层,第一个是访问层,第一个是缓存层,第三个是数据库存取层。如果没有缓存层,访问层是直接从数据库存取层读取数据,而设置缓存后,访问层不再是直接在数据库存取层读取,而是从缓存层读取数据。
PM吃瓜
2019/08/13
1.4K0
apc和apcu
apc(alternative php cache) apc的功能分为两部分 1. opcode缓存 2. 数据缓存,可以存储k/v对,类似memcache
跑马溜溜的球
2020/12/07
9810
php最新面试题_面试问题汇总
你好,我大概的说下我们的业务流程,我们的业务流程是:用户在网站浏览酒店信息,可以根据地区检索出该地区的酒店信息。列表展示酒店的信息由:酒店的名称,酒店图片,酒店位置,评论人数,评论分数以及最低入住价格。用户选中要入住的酒店进入酒店详情页面,查看酒店的介绍以及酒店的房型列表,用户根据他要入住的时间和离店的时间,检索出这个时间段内的所有可选房型(房间数量–当天的订单–当天未离店订单=剩余房间数量)显示给用户。用户选择好房型后就可以进行下单,要求有订单的开始时间,结束时间,房间数量,住客姓名,抵店时间,联系方式,备注信息等等。
全栈程序员站长
2022/09/28
8960
memcached 缓存数据库应用实践
惨绿少年
2017/12/27
1.8K0
memcached 缓存数据库应用实践
2021年电商基础面试总结「建议收藏」
①技术更新较快:根据市场的需求,不断迭代更新. ②技术涉及面广:除了 PHP,还会用到 Python,GO 等其他的一些语言;数据库中 MySQL,nosql 是最频繁使用的(当然也有的公司会用 oracle,但是 PHP 一般都是以 MySQL 为主),服务器端使用 Linux(少部分公司会用到 Unix),还经常涉及到服务器安全、系统安全等安全方面的技术. ③分布式:从前的单一的机器上运行,现在是分散到不同机器上,最后将数据集中汇总。集中式向分布式进行发展是由需求来推动. ④高并发、集群(高可用集群)、负载均衡:由并发问题采用集群进行处理,其中,集群会涉及服务器的主从以及分布问题,使用负载均衡。(权重高低)高可用是对用户而言,用户的服务不中断(系统升级,服务不中断,公司电商系统的部分更新等)。 ⑤海量数据:每年商家的各类活动(双 11,双 12 等等)订单量、浏览数、商品量、活动相关数据都将会超级大超级多(一般随同高并发出现). ⑥业务复杂:电商业务并不简单:并不是商品展示出来后,简单的加入购物车后购买就完成了。除此以外后台业务逻辑是相当复杂,比如优惠(包邮、满减),秒杀,抢购等. ⑦系统安全:系统上线必须通过系统安全部门审核通过,安全性问题正逐步的被放到台面上,而且很多企业对这块相当重视.
全栈程序员站长
2022/07/19
2.8K0
2021年电商基础面试总结「建议收藏」
相关推荐
实现页面静态化,PHP是如何实现的,你又是如何实现的
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文