刷爆力扣之矩阵中的幻方

简介: 刷爆力扣之矩阵中的幻方

一 🏠 题目描述

840. 矩阵中的幻方


3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。


给定一个由整数组成的row x col 的 grid,其中有多少个 3 × 3 的 “幻方” 子矩阵?(每个子矩阵都是连续的)。


示例 1:



输入: grid = [[4,3,8,4],[9,5,1,9],[2,7,6,2]
输出: 1解释: 
下面的子矩阵是一个 3 x 3 的幻方:
而这一个不是:
总的来说,在本示例所给定的矩阵中只有一个 3 x 3 的幻方子矩阵。


示例 2:

输出: grid = [[8]]
输入: 0

提示:


row == grid.length
col == grid[i].length
1 <= row, col <=100 <= grid[i][j] <=15


二 🏠破题思路

2.1 🚀 关键信息

解决问题第一步,当然先提取题目字面上的关键信息 😎😎😎


3 x 3 幻方是一个 从 1 到 9 不同数字的 3 x 3 矩阵,每行每列及两条对角线上各数之和都相等。给一个矩阵,求其中有多少个 3 × 3 幻方子矩阵



从题干易知,3 × 3 幻方必须是 1 到 9不同数字( 第一遍解错以为大于 9 也可,审题一定要细致 😭😭😭 )


对 3 × 3 幻方而言,其中心元素必为 5 ,因此在遍历输入矩阵时只需遍历中心元素即可,故循环条件是 [ 1 , len - 1 )



提取完题目中的关键信息后,直接进入第二阶段,思路整理 😃😃😃



2.2 🚀 思路整理

暴力法:分别检查每 3x3 网格


对于每个网格,所有数字必不同,且在 1 到 9 之间,且每一个行,列,对角线的和必须相同 🌷🌷🌷



小细节:


① 在遍历输入矩阵时只需遍历中心元素即可


② 在判决数字必不同,且在 1 到 9 之间时,只需最值处于 [ 1 , 9 ] 即可



旋转法:三阶幻方解是固定的,有以下八种



八个解共同点,首先中间元素为五,角上元素都是偶数,中点都是奇数。同一行的解可以通过旋转得到,第一行镜像,可以得到第二行。也就是说,三阶幻方本质只有一种解,其余都是旋转镜像的体现


实现逻辑


1、依据上述,只有中心元素为五时,才判断以它是否为幻方


2、此时只需对比其余八个元素,由于解的旋转不变特性,将八个元素按顺序存放,方便后面比较,顺序如下图


3、如何旋转镜像,参考这道 189. 轮转数组 的思想,将前面部分放到后面即达到了旋转效果,镜像只需反向迭代即可



4、第二步输入的数组首位元素和 8 6 2 4 逐个比较,从目标数组中取出正向镜像两个解比较是否其一,即可确定是否为幻方 🐌🐌🐌


注:旋转法出处 ,有部分删减



整理完解题思路后,直接进入第三阶段,代码实现 😃😃😃



三 🏠 代码详解

3.1 🚀 代码实现

按照我们刚才的破题思路,直接代码走起来 👇👇👇👇


bool isMagic(std::vector<int>& tmpVec) {
for (int targetIndex =0; targetIndex < 8; targetIndex +=2) { //遍历临时数组
if (tmpVec[0] == targetVec[targetIndex]) { //输入的数组首位元素和 8624 逐个比较
    //从目标数组中取出正向镜像两个解比较是否其一
            return tmpVec == std::vector<int>(targetVec.begin() + targetIndex, targetVec.begin() + targetIndex +8) || tmpVec == std::vector<int>(targetVec.rbegin() +7- targetIndex, targetVec.rbegin() +15- targetIndex);
  }
  }
  return false;
}
//初始化目标数组
std::vector<int> targetVec = { 8, 1, 6, 7, 2, 9, 4, 3, 8, 1, 6, 7, 2, 9, 4, 3 };
int numMagicSquaresInside(std::vector<std::vector<int>>& grid) {
  //初始化矩阵 x 轴, y 轴对应偏移
  std::vector<std::pair<int, int>> offsetVals = { { -1, -1 }, { -1, 0 }, 
            { -1, 1 }, { 0, 1 }, { 1, 1 }, { 1, 0 }, { 1, -1 }, { 0, -1 } };
    //初始化返回值, 行列长度
  int count =0, rows = grid.size() -1, columns = grid[0].size() -1;
    //初始化临时数组
  std::vector<int> tmpVec(8);
for (int i =1; i < rows; ++i) { //遍历行
for (int j =1; j < columns; ++j) { //遍历列
if (grid[i][j] ==5) {  //若中心元素为 5for (int index =0; index < 8; ++index) { 
      tmpVec[index] = grid[i + offsetVals[index].first][j + offsetVals[index].second];  //将以i,j为中心的八个元素置于 tmpVec 
    }
    count += isMagic(tmpVec);  //并判断其是否为幻方
    }
  }
  }
  return count;  //返回结果
}



3.2 🚀 细节解析

看完 👀👀👀 全注释版的代码实现后,相信看官大大对整体逻辑已经是大写的 OK 了 😃😃😃


那么我们挖掘上述实现的晦涩细节 😖😖😖 进行解析,直接开干,走起来 👇👇👇👇


targetIndex +=2

输入的数组(tmpVec)首位元素和 8 6 2 4 逐个比较,因此索引移动两位 🐳🐳🐳



tmpVec == std::vector<int>(targetVec.begin() + i , targetVec.begin() + i +8);

旋转逻辑 :找到目标位置后,从当前位置至当前位置移动八位(正向)


tmpVec == std::vector<int>(targetVec.rbegin() +7- i, targetVec.rbegin() +15- i);

镜像逻辑 :找到目标位置后,从反向迭代 7 - i 至反向迭代 15 - i(反向) 🌼🌼🌼


//例如找到第一个2,对镜像而言, 它是 [ 2, 7, 6, 1, 8, 3, 4, 9 ]
//即是反向迭代的 3, 4, 9, 2 这段距离 7-4=3 (targetVec.rbegin() +7- i) 
[ 8, 1, 6, 7, 2, 9, 4, 3, 8, 1, 6, 7, 2, 9, 4, 3 ]


四 🏠 心路历程

为方便各位看官大大了解博主真实刷题过程,我把当时状态纯纯真实还原,记录在心路历程这一小节,不感兴趣的小伙伴可以直接跳过哈


博主在第一阶段提取 🚀 关键信息没有问题,在第二阶段 🚀 思路整理没有问题


代码实现时使用了暴力解(官网题解同暴力,旋转法确实难想),简洁性差 😭😭😭 ,代码如下 👇👇👇👇


int numMagicSquaresInside(vector<vector<int>>& grid) {
        int count =0;
     int width = grid.size(), high = grid[0].size();
for (int i =1; i < width -1; ++i) { //遍历二维输入的宽
for (int j =1; j < high -1; ++j) {
if (grid[i][j] !=5) continue;
if (grid[i-1][j] == grid[i][j]) continue;  //各个数值不相同
                //横向加和
if (grid[i-1][j-1] + grid[i][j-1] + grid[i+1][j-1] !=15) continue;
if (grid[i-1][j] + grid[i][j] + grid[i +1][j] !=15) continue;
if (grid[i-1][j+1] + grid[i][j+1] + grid[i+1][j+1] !=15) continue;
                //竖向加和
if (grid[i-1][j-1] + grid[i-1][j] + grid[i-1][j+1] !=15) continue;
if (grid[i][j-1] + grid[i][j] + grid[i][j+1] !=15) continue;
if (grid[i+1][j-1] + grid[i+1][j] + grid[i+1][j+1] !=15) continue;
                //对角加和
if (grid[i-1][j-1] + grid[i][j] + grid[i+1][j+1] !=15) continue;
if (grid[i-1][j+1] + grid[i][j] + grid[i+1][j-1] !=15) continue;
                //求最大值和最小值 legalScope
                int mem_max = std::max({grid[i-1][j-1], grid[i][j-1], grid[i+1][j-1],
                                grid[i-1][j], grid[i][j], grid[i+1][j],
                                grid[i-1][j+1], grid[i][j+1], grid[i+1][j+1]});
                int mem_min = std::min({grid[i-1][j-1], grid[i][j-1], grid[i+1][j-1],
                    grid[i-1][j], grid[i][j], grid[i+1][j],
                    grid[i-1][j+1], grid[i][j+1], grid[i+1][j+1]});
if (mem_max <=9 && mem_min >=1) ++count;
            }
        }
     return count;
    }



相关文章
|
6月前
|
算法
力扣240 搜索二维矩阵II
力扣240 搜索二维矩阵II
|
6月前
|
算法 测试技术 C#
【二分查找】LeetCode1970:你能穿过矩阵的最后一天
【二分查找】LeetCode1970:你能穿过矩阵的最后一天
|
6月前
leetcode-329:矩阵中的最长递增路径
leetcode-329:矩阵中的最长递增路径
49 0
|
3月前
|
存储 算法 NoSQL
LeetCode第73题矩阵置零
文章介绍了LeetCode第73题"矩阵置零"的解法,通过使用矩阵的第一行和第一列作为标记来记录哪些行或列需要置零,从而在不增加额外空间的情况下解决问题。
LeetCode第73题矩阵置零
|
1月前
|
算法 C++
Leetcode第59题(螺旋矩阵2)
这篇文章介绍了解决LeetCode第59题“螺旋矩阵II”的算法,通过C++编程实现按顺时针顺序填充一个n x n的正方形矩阵。
15 0
|
3月前
|
算法 Java
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
50 6
|
3月前
|
算法 JavaScript Python
【Leetcode刷题Python】79. 单词搜索和剑指 Offer 12. 矩阵中的路径
Leetcode第79题"单词搜索"的Python解决方案,使用回溯算法在给定的二维字符网格中搜索单词,判断单词是否存在于网格中。
41 4
|
3月前
|
算法 Python
【Leetcode刷题Python】73. 矩阵置零
本文介绍了LeetCode第73题的解法,题目要求在给定矩阵中将所有值为0的元素所在的行和列全部置为0,并提供了一种原地算法的Python实现。
32 0
【Leetcode刷题Python】73. 矩阵置零
|
5月前
|
算法
力扣经典150题第三十七题:矩阵置零
力扣经典150题第三十七题:矩阵置零
31 2
|
5月前
|
存储 数据采集 算法
LeetCode题目73:矩阵置零
LeetCode题目73:矩阵置零