阅读视图

发现新文章,点击刷新页面。

每日一题-矩阵中的幻方🟡

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

给定一个由整数组成的row x col 的 grid,其中有多少个 3 × 3 的 “幻方” 子矩阵?

注意:虽然幻方只能包含 1 到 9 的数字,但 grid 可以包含最多15的数字。

 

示例 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 <= 10
  • 0 <= grid[i][j] <= 15

无需计算第三行列的和,以及对角线的和,数学证明(Python/Java/C++/Go)

幻方正中心一定是 5

$3\times 3$ 的幻方必须包含 $1$ 到 $9$ 所有数字,这意味着幻方元素和为 $1+2+\cdots + 9 = 45$。

由于每行的和都要相等,所以每行的和为 $\dfrac{45}{3} = 15$。这也是每列的和,对角线的和。

有 $4$ 条经过幻方正中心的线:第二行,第二列,两条对角线。

这 $4$ 条线:

  • 覆盖了 $3\times 3$ 幻方的每个数,正中心的数覆盖了 $4$ 次(多覆盖了 $3$ 次),其余数恰好覆盖一次。
  • 总和为 $15\cdot 4 = 60$。

设正中心的数为 $x$ ,则有

$$
1+2+\cdots + 9 + 3x = 60
$$

解得

$$
x = 5
$$

换言之(逆否命题),如果 $3\times 3$ 矩阵正中心的数不是 $5$,那么该矩阵必然不是幻方。

无需计算第三行的和,第三列的和

如果 $3\times 3$ 矩阵包含 $1$ 到 $9$ 所有整数,且前两行的和都是 $15$,那么第三行的和为

$$
1+2+\cdots+9-15-15 = 15
$$

同理,如果前两列的和都是 $15$,那么第三列的和必然是 $15$。

无需计算对角线的和

如果 $3\times 3$ 矩阵:

  • 正中心的数是 $5$。
  • 包含 $1$ 到 $9$ 所有整数。
  • 前两行的和都是 $15$。
  • 前两列的和都是 $15$。

下面证明:矩阵对角线的和一定都是 $15$。

设矩阵为

$$
\begin{bmatrix}
a & b & c \
d & 5 & f \
g & h & i \
\end{bmatrix}
$$

由于正中心的数是 $5$,只需证明对角两数之和等于 $10$,即 $a+i=c+g=10$。

在 $1$ 到 $9$ 中选两个不同整数相加等于 $10$,只有如下 $4$ 种情况:

  • $1+9=10$。
  • $2+8=10$。
  • $3+7=10$。
  • $4+6=10$。

由于已知 $b+h=d+f=10$,消耗了两种情况,剩余的四个数必然在 $a,c,g,i$ 中。比如 $b+h=1+9$,$d+f=3+7$,那么剩余的 $2,4,6,8$ 必然在 $a,c,g,i$ 中。

比如左上角的 $a=4$,那么 $6$ 在哪?

  • 在右上角?此时 $a+c=10$,由于已知 $a+b+c=15$,得到 $b=5$,与正中心的数相同,矛盾。
  • 在左下角?此时 $a+g=10$,由于已知 $a+d+g=15$,得到 $d=5$,与正中心的数相同,矛盾。
  • 所以 $6$ 只能在右下角,即 $a+i=10$ 成立。
  • 剩余两个数 $2$ 和 $8$ 在另一对角,所以 $c+g=10$ 成立。

用同样的方法可以证明,$a$ 等于其他数的时候,对角的数 $i$ 一定等于 $10-a$。剩余的另一对和为 $10$ 的数在另一对角。

综上,由已知条件可得,两条对角线的和都是 $15$。

如何快速判断矩阵包含 $1$ 到 $9$ 所有数?可以把数字压缩到一个二进制数 $\textit{mask}$ 中,$\textit{mask}$ 从低到高的 $i$ 位是 $1$ 表示 $i$ 在矩阵中。矩阵包含 $1$ 到 $9$ 所有数相当于 $\textit{mask} = 1111111110_{(2)} = 2^{10}-2=1022$。

###py

class Solution:
    def numMagicSquaresInside(self, grid: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])
        ans = 0

        for i in range(m - 2):
            for j in range(n - 2):
                if grid[i + 1][j + 1] != 5:
                    continue

                mask = 0
                r_sum = [0] * 3
                c_sum = [0] * 3
                for r in range(3):
                    for c in range(3):
                        x = grid[i + r][j + c]
                        mask |= 1 << x
                        r_sum[r] += x
                        c_sum[c] += x

                if mask == 1022 and r_sum[0] == r_sum[1] == c_sum[0] == c_sum[1] == 15:
                    ans += 1

        return ans

###java

class Solution {
    public int numMagicSquaresInside(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int ans = 0;

        for (int i = 0; i < m - 2; i++) {
            for (int j = 0; j < n - 2; j++) {
                if (grid[i + 1][j + 1] != 5) {
                    continue;
                }

                int mask = 0;
                int[] rSum = new int[3];
                int[] cSum = new int[3];
                for (int r = 0; r < 3; r++) {
                    for (int c = 0; c < 3; c++) {
                        int x = grid[i + r][j + c];
                        mask |= 1 << x;
                        rSum[r] += x;
                        cSum[c] += x;
                    }
                }

                if (mask == (1 << 10) - 2 &&
                    rSum[0] == 15 && rSum[1] == 15 &&
                    cSum[0] == 15 && cSum[1] == 15) {
                    ans++;
                }
            }
        }

        return ans;
    }
}

###cpp

class Solution {
public:
    int numMagicSquaresInside(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        int ans = 0;

        for (int i = 0; i < m - 2; i++) {
            for (int j = 0; j < n - 2; j++) {
                if (grid[i + 1][j + 1] != 5) {
                    continue;
                }

                int mask = 0;
                int r_sum[3]{};
                int c_sum[3]{};
                for (int r = 0; r < 3; r++) {
                    for (int c = 0; c < 3; c++) {
                        int x = grid[i + r][j + c];
                        mask |= 1 << x;
                        r_sum[r] += x;
                        c_sum[c] += x;
                    }
                }

                if (mask == (1 << 10) - 2 &&
                    r_sum[0] == 15 && r_sum[1] == 15 &&
                    c_sum[0] == 15 && c_sum[1] == 15) {
                    ans++;
                }
            }
        }

        return ans;
    }
};

###c

int numMagicSquaresInside(int** grid, int gridSize, int* gridColSize) {
    int m = gridSize, n = gridColSize[0];
    int ans = 0;

    for (int i = 0; i < m - 2; i++) {
        for (int j = 0; j < n - 2; j++) {
            if (grid[i + 1][j + 1] != 5) {
                continue;
            }

            int mask = 0;
            int r_sum[3] = {};
            int c_sum[3] = {};
            for (int r = 0; r < 3; r++) {
                for (int c = 0; c < 3; c++) {
                    int x = grid[i + r][j + c];
                    mask |= 1 << x;
                    r_sum[r] += x;
                    c_sum[c] += x;
                }
            }

            if (mask == (1 << 10) - 2 &&
                r_sum[0] == 15 && r_sum[1] == 15 &&
                c_sum[0] == 15 && c_sum[1] == 15) {
                ans++;
            }
        }
    }

    return ans;
}

###go

func numMagicSquaresInside(grid [][]int) (ans int) {
m, n := len(grid), len(grid[0])
for i := range m - 2 {
for j := range n - 2 {
if grid[i+1][j+1] != 5 {
continue
}

mask := 0
var rSum, cSum [3]int
for r, row := range grid[i : i+3] {
for c, x := range row[j : j+3] {
mask |= 1 << x
rSum[r] += x
cSum[c] += x
}
}

if mask == 1<<10-2 &&
rSum[0] == 15 && rSum[1] == 15 &&
cSum[0] == 15 && cSum[1] == 15 {
ans++
}
}
}
return
}

###js

var numMagicSquaresInside = function (grid) {
    const m = grid.length;
    const n = grid[0].length;
    let ans = 0;

    for (let i = 0; i < m - 2; i++) {
        for (let j = 0; j < n - 2; j++) {
            if (grid[i + 1][j + 1] !== 5) {
                continue;
            }

            let mask = 0;
            const rSum = [0, 0, 0];
            const cSum = [0, 0, 0];
            for (let r = 0; r < 3; r++) {
                for (let c = 0; c < 3; c++) {
                    const x = grid[i + r][j + c];
                    mask |= 1 << x;
                    rSum[r] += x;
                    cSum[c] += x;
                }
            }

            if (mask === (1 << 10) - 2 &&
                rSum[0] === 15 && rSum[1] === 15 &&
                cSum[0] === 15 && cSum[1] === 15) {
                ans++;
            }
        }
    }

    return ans;
};

###rust

impl Solution {
    pub fn num_magic_squares_inside(grid: Vec<Vec<i32>>) -> i32 {
        let m = grid.len();
        let n = grid[0].len();
        let mut ans = 0;

        for i in 0..m.saturating_sub(2) {
            for j in 0..n.saturating_sub(2) {
                if grid[i + 1][j + 1] != 5 {
                    continue;
                }

                let mut mask = 0;
                let mut r_sum = [0; 3];
                let mut c_sum = [0; 3];
                for (r, row) in grid[i..i + 3].iter().enumerate() {
                    for (c, &x) in row[j..j + 3].iter().enumerate() {
                        mask |= 1 << x;
                        r_sum[r] += x;
                        c_sum[c] += x;
                    }
                }

                if mask == (1 << 10) - 2 &&
                    r_sum[0] == 15 && r_sum[1] == 15 &&
                    c_sum[0] == 15 && c_sum[1] == 15 {
                    ans += 1;
                }
            }
        }

        ans
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(mn)$,其中 $m$ 和 $n$ 分别是 $\textit{grid}$ 的行数和列数。
  • 空间复杂度:$\mathcal{O}(1)$。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

非暴力,努力写出优雅的代码,双百

解题思路

首先谈谈三阶幻方,固然是可以通过定义去判断,但写起来很麻烦,效率也不高。实际上,三阶幻方的解是固定的,有以下八种。
image.png
自然是不可能逐个判断是不是这八个之一,大家仔细观察,相信很容易找出这八个解的共同点,首先中间的元素都是五,角上元素都是偶数,中点都是奇数。同一行的解可以通过旋转得到,第一行镜像,可以得到第二行。也就是说,三阶幻方本质只有一种解,其余都是旋转镜像的体现
我们可以依据这一点,写出优雅简洁的代码。

  1. 遍历中间的部分,只有是五的时候,才判断以他为中心的三阶方阵是否是幻方。
  2. 五已经比对过了,只需要比对其他八个元素,由于解的旋转不变特性,这里将八个元素按顺序存放,方便后面比较,顺序如下图。
  3. 解的编码,如何表示旋转镜像,参考旋转数组这题的思想,将前面部分的放在后面就达到了旋转的效果。镜像只需要反向迭代就好了。
    image.png
    4.第二步输入的数组首位元素和8 6 2 4逐个比较决定可能的解,从编码的数组中取出正向镜像两个解比较是否其一,就可确定是否是幻方。

代码

###cpp

class Solution {
public:
    vector<int> m={8,1,6,7,2,9,4,3,8,1,6,7,2,9,4,3};
    int numMagicSquaresInside(vector<vector<int>>& grid) {
        int di[8]={-1,-1,-1,0,1,1,1,0};
        int dj[8]={-1,0,1,1,1,0,-1,-1};
        int count=0;
        for(int i=1;i<grid.size()-1;i++)
            for(int j=1;j<grid[0].size()-1;j++)
                if(grid[i][j]==5){
                    vector<int> around;
                    for(int k=0;k<8;k++)
                        around.push_back(grid[i+di[k]][j+dj[k]]);
                    count+=IsMagic(around);
                }
        return count;
    }
    bool IsMagic(vector<int>& v){
        for(int i=0;i<8;i+=2)
            if(m[i]==v[0])
            return v==vector<int>(m.begin()+i,m.begin()+i+8)
            ||v==vector<int>(m.rbegin()+7-i,m.rbegin()+15-i);
        return false;//奇数元素
    }
};

使用抽屉算法解题?

解题思路

满足幻方的条件必定是中间的数字为5,周围的数字为满足 1->6->7->2->9->4->3->8->1
的这种圆环结构,5的上下左右必定是 1、9、3、7(这四个数字都只能组成两组 15)
并且5的周围的数字其实都可以跳过,这里没作实现
1.利用数组的索引语义记录索引对应的下一个数字
2.若5的下一个值满足条件,且该值的下一行对应列满足条件,顺时针
若5的下一个值满足条件,且该值的上一行对应列满足条件,逆时针
3.总共判断6次即可(前两次已经判断过了)

代码

###java

class Solution {
    public int numMagicSquaresInside(int[][] grid) {
        //if(grid == null || grid.length < 3 || grid[1].length < 3)return 0;
        //由于是1~9的不同数字,那么必定是5在中间,并且其他数字满足顺时针或逆时针
        int[] check = {0,6,9,8,3,0,7,2,1,4};
        int result = 0;
        for(int i=1;i<grid.length-1;i++){
            for(int j=1;j<grid[0].length-1;j++){
                if(grid[i][j] == 5){
                    int next = grid[i][j+1];
                    switch(next){
                        case 1:
                        case 3:
                        case 7:
                        case 9:
                            if(check[next] == grid[i+1][j+1]){
                                if(each(grid,check,i+1,j+1,1))
                                    result++;
                            }else if(check[next] == grid[i-1][j+1]){
                                if(each(grid,check,i-1,j+1,-1))
                                    result++;
                            }
                    }
                    //5的后一位必定不用算    
                    j++;
                }
            }
        }
        return result;
    }

    public boolean each(int[][] grid,int[] check,int x,int y,int ward){
        int cur = grid[x][y];
        int next = check[cur];
        for(int i=y-2;y!=i;){
            if(grid[x][--y] != next)
                return false;
            cur = next;
            next = check[cur];
        }
        for(int i=x-2*ward;x!=i;){
            if(grid[x-=ward][y] != next)
                return false;
            cur = next;
            next = check[cur];
        }
        for(int i=y+2;y!=i;){
            if(grid[x][++y] != next)
                return false;
            cur = next;
            next = check[cur];
        }
        return true;       
    }
}

HarmonyOS应用开发:多重筛选

本示例主要介绍多重筛选场景,利用数组方法过滤满足条件的数据,利用LazyForEach实现列表信息的渲染以及刷新。 效果图预览 使用说明 等待列表数据全部加载完成后,点击筛选类型,展开筛选数据。

胜通能源:股票短期内价格涨幅较大,明起停牌核查

36氪获悉,胜通能源公告,公司股票自2025年12月12日至29日期间价格涨幅为213.97%,已严重背离公司基本面。为维护投资者利益,公司将就股票交易波动情况进行停牌核查。公司提醒广大投资者注意二级市场交易风险,近期公司股票价格显著偏离大盘指数和行业指数,短期波动幅度较大,已明显偏离市场走势,存在较高的炒作风险。公司主营业务未发生重大变化,股价短期内连续上涨,可能存在市场情绪过热、非理性炒作风险。公司不存在应披露而未披露的重大信息。公司股票自2025年12月30日起停牌,预计停牌时间不超过3个交易日。

紫光国微:拟购买瑞能半导控股权或全部股权,股票停牌

36氪获悉,紫光国微公告,公司正在筹划以发行股份及支付现金的方式,购买南昌建恩半导体产业投资中心(有限合伙)、北京广盟半导体产业投资中心(有限合伙)、天津瑞芯半导体产业投资中心(有限合伙)等交易对方持有的瑞能半导体科技股份有限公司控股权或全部股权,并募集配套资金。本次交易预计不构成重大资产重组,构成关联交易。公司股票及可转债自2025年12月30日起开始停牌,预计在不超过10个交易日的时间内披露本次交易方案。

天普股份:实际控制人已变更为杨龚轶凡,提前开展董事会换届选举工作

36氪获悉,天普股份公告,公司于2025年12月22日收到控股股东浙江天普控股有限公司通知,中昊芯英(杭州)科技有限公司、海南芯繁企业管理合伙企业(有限合伙)和方东晖对天普控股的增资款已足额支付,并完成工商变更登记,公司实际控制人变更为杨龚轶凡。为保障公司治理结构稳定性,公司提前开展董事会换届选举工作,提名杨龚轶凡、李琛龄和康啸为第四届董事会非独立董事候选人,马莹、沈百鑫为独立董事候选人。

外汇局:要促进跨境贸易投融资便利化,防范跨境资金流动风险

36氪获悉,国家外汇管理局近日组织2025年新任职司处级领导干部召开集体谈话会议。外汇局局长朱鹤新强调,要履职尽责敢担当,围绕防风险、强监管、促高质量发展工作主线,强化责任意识,提升履职能力,真抓实干,持续深化外汇领域改革开放,促进跨境贸易投融资便利化,防范跨境资金流动风险,保障外汇储备资产安全、流动和保值增值,切实维护外汇市场稳定和国家经济金融安全。
❌