Java&C++题解与拓展——leetcode427.建立四叉树【二维vector初始化学习与使用】

236 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

每日一题做题记录,参考官方和三叶的题解

题目要求

image.png image.png

理解

哦题目好长……

  • 把矩阵递归四分,直到每个小块内的值相同(为叶子节点);
  • 叶子节点isLeaf=1isLeaf=1valval设为小块内的值(块内每个格子值相等);
  • 非叶子节点isLeaf=0isLeaf=0valval随便。

思路一:递归

  • 递归判断当前矩阵范围内值是否都相等
    • 相等则为叶子,返回(值, true)
    • 不相等则从中间划分为四个,分别再判断
  • 可以直接用左上角点(tlx,tly)(tlx, tly)和右下角点(brx,bry)(brx, bry)的坐标来划定矩阵范围
    • 划分时分别砍半横着tlx+brx2\frac{tlx+brx}{2}、竖着tly+bry2\frac{tly+bry}{2}

Java

class Solution {
    int[][] g;
    public Node construct(int[][] grid) {
        g = grid;
        int n = g.length;
        return DFS(0, 0, n, n);
    }

    Node DFS(int tlx, int tly, int brx, int bry) {
        boolean equ = true;
        int t = g[tlx][tly];
        // 块内所有值相等
        for(int i = tlx; i < brx && equ; ++i)
            for(int j = tly; j < bry && equ; ++j)
                if(g[i][j] != t)
                    equ = false;
        if(equ)
            return new Node(t == 1, true); // 叶子

        int nx = (brx + tlx) / 2, ny = (bry + tly) / 2; // 分割线
        Node root = new Node(
            true,
            false,
            DFS(tlx, tly, nx, ny),
            DFS(tlx, ny, nx, bry),
            DFS(nx, tly, brx, ny),
            DFS(nx, ny, brx, bry)
        );
              
        return root;
    }
}
  • 时间复杂度:O(n2+n2×logn)O(n^2+n^2\times\log n),单次递归调用4个子递归,其规模为n2×n2\frac{n}{2}\times\frac{n}{2},复杂度共4O(n24)=O(n2)4O(\frac{n^2}{4})=O(n^2);判断块内所有值相等复杂度为O(n2)O(n^2),最多拆分logn\log n次(也是需判断次数),所以递归内复杂度为O(n2×logn)O(n^2\times\log n)
  • 空间复杂度:O(1)O(1),忽略递归的额外空间开销

C++

class Solution {
    vector<vector<int>> g;
public:
    Node* construct(vector<vector<int>>& grid) {
        g = grid;
        int n = g.size();
        return DFS(0, 0, n, n);
    }

    Node* DFS(int tlx, int tly, int brx, int bry) {
        bool equ = true;
        int t = g[tlx][tly];
        // 块内所有值相等
        for(int i = tlx; i < brx && equ; ++i)
            for(int j = tly; j < bry && equ; ++j)
                if(g[i][j] != t)
                    equ = false;
        if(equ)
            return new Node(t == 1, true); // 叶子

        int nx = (brx + tlx) / 2, ny = (bry + tly) / 2; // 分割线
        Node* root = new Node(
            true,
            false,
            DFS(tlx, tly, nx, ny),
            DFS(tlx, ny, nx, bry),
            DFS(nx, tly, brx, ny),
            DFS(nx, ny, brx, bry)
        );
              
        return root;
    }
};
  • 时间复杂度:O(n2+n2×logn)O(n^2+n^2\times\log n),单次递归调用4个子递归,其规模为n2×n2\frac{n}{2}\times\frac{n}{2},复杂度共O(n2)O(n^2);判断块内所有值相等复杂度为O(n2)O(n^2),最多拆分logn\log n次(也是需判断次数),所以递归内复杂度为O(n2×logn)O(n^2\times\log n)
  • 空间复杂度:O(1)O(1),忽略递归的额外空间开销

思路二:递归+前缀和

  • 前面判断块内所有值相等的暴力方法可以采用前缀和优化
  • 记录内容其实为每一部分的面积,为00或为当前块内格子数当前块内格子数则相同(为叶子),否则划分判断

Java

class Solution {
    int[][] g;
    int[][] pre = new int[70][70];
    public Node construct(int[][] grid) {
        g = grid;
        int n = g.length;
        // 前缀和
        for(int i = 1; i <= n; ++i) 
            for(int j = 1; j <= n; ++j)
                pre[i][j] = pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1] + grid[i - 1][j - 1];

        return DFS(0, 0, n, n);
    }

    Node DFS(int tlx, int tly, int brx, int bry) {
        int tot = pre[brx][bry] - pre[brx][tly] - pre[tlx][bry] + pre[tlx][tly];
        // 叶子
        if(tot == 0)
            return new Node(false, true);
        else if(tot == (brx - tlx) * (bry - tly))
            return new Node(true, true);
       
        int nx = (tlx + brx) / 2, ny = (tly + bry) / 2; // 分割线
        Node root = new Node(
            true,
            false,
            DFS(tlx, tly, nx, ny),
            DFS(tlx, ny, nx, bry),
            DFS(nx, tly, brx, ny),
            DFS(nx, ny, brx, bry)
        );
        return root;
    }
}
  • 时间复杂度:O(n2+logn)O(n^2+\log n),递归复杂度和上面一样为O(n2)O(n^2),二维前缀和预处理复杂度也为O(n2)O(n^2)递归内判断复杂度O(1),拆分O(log n)次【这部分是基于上一方法思路的个人胡乱分析,其实小于前面部分可以拿掉写成O(n2)O(n^2)
  • 空间复杂度:O(n2)O(n^2),二维前缀和所需

C++

class Solution {
private:
    vector<vector<int>> g;
    vector<vector<int>> pre = vector<vector<int>> (70, vector<int>(70, 0));
public:
    Node* construct(vector<vector<int>>& grid) {
        g = grid;
        int n = g.size();
        // 前缀和
        for(int i = 1; i <= n; ++i) 
            for(int j = 1; j <= n; ++j)
                pre[i][j] = pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1] + grid[i - 1][j - 1];

        return DFS(0, 0, n, n);
    }

    Node* DFS(int tlx, int tly, int brx, int bry) {
        int tot = pre[brx][bry] - pre[brx][tly] - pre[tlx][bry] + pre[tlx][tly];
        // 叶子
        if(tot == 0)
            return new Node(false, true);
        else if(tot == (brx - tlx) * (bry - tly))
            return new Node(true, true);

        int nx = (brx + tlx) / 2, ny = (bry + tly) / 2; // 分割线
        Node* root = new Node(
            true,
            false,
            DFS(tlx, tly, nx, ny),
            DFS(tlx, ny, nx, bry),
            DFS(nx, tly, brx, ny),
            DFS(nx, ny, brx, bry)
        );
              
        return root;
    }
};
  • 时间复杂度:O(n2+logn)O(n^2+\log n),递归复杂度和上面一样为O(n2)O(n^2),二维前缀和预处理复杂度也为O(n2)O(n^2)递归内判断复杂度为O(1),拆分O(\log n)$次【这部分是基于上一方法思路的个人胡乱分析,其实小于前面部分可以拿掉写成O(n2)O(n^2)
  • 空间复杂度:O(n2)O(n^2),二维前缀和所需

二维vector初始化

  • 学习参考链接
  • 格式为vector<vector<int>> vec(n, vector<int>(m, 0));nn为容器大小,mm为容器内每个值大小。
  • 但是直接初始化全局变量会报错:

    expected parameter declarator

    • 意为编译器无法区分该语句是在声明变量还是函数,所以可以类比java的初始化方法,即
    vector<vector<int>> vec = vector<vector<int>> (n, vector<int>(m, 0));
    
    • 其他解决方案参考这里

总结

递归应用题目,结束条件的+1、-1卡了一会,要理清思路看好是长度还是坐标。


欢迎指正与讨论!