首页 > 编程语言 >每日一题.设计相邻元素求和服务;暴力算法与哈希表的运用

每日一题.设计相邻元素求和服务;暴力算法与哈希表的运用

时间:2024-11-09 23:45:29浏览次数:7  
标签:diagonalSum 求和 adjacentSum 网格 value int grid 哈希 一题

本题出自LeetCode每日一题3242,可以说比昨天的那道“每日抑题”简单不少呀,就是代码长一点,同时本题目使用了两种不同的方法,并对每一种用法进行了深度解析。本文全长5000字。


题目 

给你一个 n x n 的二维数组 grid,它包含范围 [0, n2 - 1] 内的不重复元素。

实现 neighborSum 类:

  • neighborSum(int [][]grid) 初始化对象。
  • int adjacentSum(int value) 返回在 grid 中与 value 相邻的元素之,相邻指的是与 value 在上、左、右或下的元素。
  • int diagonalSum(int value) 返回在 grid 中与 value 对角线相邻的元素之,对角线相邻指的是与 value 在左上、右上、左下或右下的元素。

示例 1:

输入:

["neighborSum", "adjacentSum", "adjacentSum", "diagonalSum", "diagonalSum"]

[[[[0, 1, 2], [3, 4, 5], [6, 7, 8]]], [1], [4], [4], [8]]

输出: [null, 6, 16, 16, 4]

解释:

  • 1 的相邻元素是 0、2 和 4。
  • 4 的相邻元素是 1、3、5 和 7。
  • 4 的对角线相邻元素是 0、2、6 和 8。
  • 8 的对角线相邻元素是 4。

示例 2:

输入:

["neighborSum", "adjacentSum", "diagonalSum"]

[[[[1, 2, 0, 3], [4, 7, 15, 6], [8, 9, 10, 11], [12, 13, 14, 5]]], [15], [9]]

输出: [null, 23, 45]

解释:

  • 15 的相邻元素是 0、10、7 和 6。
  • 9 的对角线相邻元素是 4、12、14 和 15。

提示:

  • 3 <= n == grid.length == grid[0].length <= 10
  • 0 <= grid[i][j] <= n2 - 1
  • 所有 grid[i][j] 值均不重复。
  • adjacentSum 和 diagonalSum 中的 value 均在范围 [0, n2 - 1] 内。
  • 最多会调用 adjacentSum 和 diagonalSum 总共 2 * n2 次。

解题思路

在交完代码看官方题解的时候,我发现LeetCode官方使用了哈希表,我个人认为使用暴力算法可能要“粗鲁而简单”一点,想了想索性两种思路都分享一下吧


题解 

暴力算法

#include <vector>
using namespace std;

class NeighborSum {
private:
    vector<int> diagonalSums;
    vector<int> adjacentSums;

public:
    NeighborSum(vector<vector<int>>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        int maxVal = 0;

        // 找出网格中的最大值,以确定 sums 向量的大小
        for (const auto& row : grid) {
            for (int val : row) {
                maxVal = max(maxVal, val);
            }
        }

        diagonalSums = adjacentSums = vector<int>(maxVal + 1, 0);

        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                for (int di = -1; di <= 1; ++di) {
                    for (int dj = -1; dj <= 1; ++dj) {
                        if (di == 0 && dj == 0) continue; // 跳过自身

                        int ni = i + di, nj = j + dj;
                        if (ni >= 0 && ni < n && nj >= 0 && nj < m) {
                            if (di != 0 && dj != 0) { // 对角线邻居
                                diagonalSums[grid[i][j]] += grid[ni][nj];
                            } else { // 上下左右邻居
                                adjacentSums[grid[i][j]] += grid[ni][nj];
                            }
                        }
                    }
                }
            }
        }
    }

    int adjacentSum(int value) const {
        return adjacentSums[value];
    }

    int diagonalSum(int value) const {
        return diagonalSums[value];
    }
};

/**
 * Your NeighborSum object will be instantiated and called as such:
 * NeighborSum* obj = new NeighborSum(grid);
 * int param_1 = obj->adjacentSum(value);
 * int param_2 = obj->diagonalSum(value);
 */

代码分析

public:
    NeighborSum(vector<vector<int>>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        int maxVal = 0;
  • public 关键字表示接下来的成员函数可以在类外部访问。
  • 构造函数 NeighborSum 接受一个二维整数数组 grid 作为参数。
  • n 和 m 分别表示网格的行数和列数。
  • maxVal 用于记录网格中的最大值,以便确定 diagonalSums 和 adjacentSums 向量的大小。
        // 找出网格中的最大值,以确定 sums 向量的大小
        for (const auto& row : grid) {
            for (int val : row) {
                maxVal = max(maxVal, val);
            }
        }
  • 使用嵌套的 for 循环遍历整个网格,找到其中的最大值 maxVal。这是为了确定 diagonalSums 和 adjacentSums 向量的大小,确保能够容纳网格中所有可能的值。
        diagonalSums = adjacentSums = vector<int>(maxVal + 1, 0);
  • 初始化 diagonalSums 和 adjacentSums 向量,大小为 maxVal + 1,并用 0 填充。这是因为向量的索引从 0 开始,所以需要 maxVal + 1 个元素。
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                for (int di = -1; di <= 1; ++di) {
                    for (int dj = -1; dj <= 1; ++dj) {
                        if (di == 0 && dj == 0) continue; // 跳过自身
  • 使用三层嵌套的 for 循环遍历网格中的每一个元素。外层两个循环 i 和 j 遍历网格的行和列,内层两个循环 di 和 dj 遍历当前元素周围的 8 个方向(包括对角线方向)。
  • if (di == 0 && dj == 0) continue; 跳过当前元素本身,只处理周围的邻居。
                        int ni = i + di, nj = j + dj;
                        if (ni >= 0 && ni < n && nj >= 0 && nj < m) {
                            if (di != 0 && dj != 0) { // 对角线邻居
                                diagonalSums[grid[i][j]] += grid[ni][nj];
                            } else { // 上下左右邻居
                                adjacentSums[grid[i][j]] += grid[ni][nj];
                            }
                        }
                    }
                }
            }
        }
  • 计算邻居的坐标 ni 和 nj
  • 检查邻居坐标是否在网格范围内,如果在范围内,则根据 di 和 dj 的值判断是上下左右邻居还是对角线邻居,并相应地更新 diagonalSums 或 adjacentSums
    int adjacentSum(int value) const {
        return adjacentSums[value];
    }

    int diagonalSum(int value) const {
        return diagonalSums[value];
    }
};
  • 定义了两个公共成员函数 adjacentSum 和 diagonalSum,分别返回指定值的上下左右邻居和对角线邻居和。
  • const 关键字表示这些函数不会修改类的成员变量。

哈希表存储

由于询问时给定的是元素值而不是其在二维数组中的位置,因此在初始化时,我们可以使用一个哈希表 pos 来存储每个元素所在的位置。哈希表 pos 中的每个键表示一个元素,对应的值是一个二元组,表示该元素在二维数组中的位置。

同时,在初始化时,我们存储给定的二维数组 grid 的一份拷贝。这样一来,在查询操作 adjacentSum(value)diagonalSum(value) 中,我们首先通过 pos 获取 value 的位置,然后根据查询的类型,返回其四个相邻元素的和或四个对角线元素的和

#include <vector>
#include <unordered_map>
using namespace std;

class NeighborSum {
public:
    NeighborSum(vector<vector<int>>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        
        // 记录每个值在网格中的位置
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                pos[grid[i][j]] = {i, j};
            }
        }
        
        // 移动语义,避免深拷贝
        this->grid = move(grid);
    }
    
    int adjacentSum(int value) {
        return getSum(value, 0);
    }
    
    int diagonalSum(int value) {
        return getSum(value, 1);
    }

private:
    int getSum(int value, int idx) {
        auto [x, y] = pos[value];
        int ans = 0;
        
        // 遍历邻居方向
        for (const auto& dir : dirs[idx]) {
            int nx = x + dir[0];
            int ny = y + dir[1];
            
            // 检查邻居是否在网格范围内
            if (0 <= nx && nx < grid.size() && 0 <= ny && ny < grid[0].size()) {
                ans += grid[nx][ny];
            }
        }
        
        return ans;
    }

    vector<vector<int>> grid;
    unordered_map<int, pair<int, int>> pos;
    static constexpr int dirs[2][4][2] = {
        {{-1, 0}, {1, 0}, {0, -1}, {0, 1}},  // 上下左右
        {{-1, -1}, {-1, 1}, {1, -1}, {1, 1}} // 对角线
    };
};

/**
 * Your NeighborSum object will be instantiated and called as such:
 * NeighborSum* obj = new NeighborSum(grid);
 * int param_1 = obj->adjacentSum(value);
 * int param_2 = obj->diagonalSum(value);
 */

这个方法的时间和空间复杂度简化了不少 

  1. 构造函数 NeighborSum

    • 记录每个值在网格中的位置,使用 unordered_map 存储。
    • 使用 move 语义将传入的 grid 移动到类的成员变量中,避免深拷贝。
  2. 成员函数 adjacentSumdiagonalSum

    • 这两个函数分别调用 getSum 函数,传入不同的索引值来区分上下左右邻居和对角线邻居。
  3. 私有成员函数 getSum

    • 从 pos 中获取指定值的位置 (x, y)
    • 遍历 dirs 中的方向数组,计算邻居的坐标 (nx, ny)
    • 检查邻居坐标是否在网格范围内,如果在范围内则累加邻居的值。
  4. 成员变量

    • grid:存储网格数据。
    • pos:存储每个值在网格中的位置。
    • dirs:静态常量数组,定义了上下左右和对角线的方向。

优化点

  • 使用 unordered_map:记录每个值的位置,避免了额外的空间开销。
  • 移动语义:使用 move 语义将传入的 grid 移动到类的成员变量中,避免了深拷贝。
  • 方向数组:使用 dirs 数组来定义邻居方向,使代码更加简洁和易读

制作不易,您的关注与点赞是我最大的动力!

 

标签:diagonalSum,求和,adjacentSum,网格,value,int,grid,哈希,一题
From: https://blog.csdn.net/yzxyy_zzb/article/details/143637714

相关文章

  • sicp每日一题[2.73]
    最近状态不太好,再加上2.73前面的内容有点多,学的有点吃力,所以昨天就没做。。Exercise2.73Section2.3.2describedaprogramthatperformssymbolicdifferentiation:(define(derivexpvar)(cond((number?exp)0)((variable?exp)(if(same-va......
  • cmu15545-哈希表(Hash Table)
    基本概念哈希和树一样,是数据库系统中用于访问数据的方法。空间复杂度:$O(n)$时间复杂度:$O(1)~O(n)$权衡:更大的哈希空间(碰撞减少),还是更少的哈希空间(碰撞处理)?哈希函数CRC-64(1975)MurmurHash(2008)GoogleCityHash(2011)FacebookXXHash(2012)【最常用】Goo......
  • 力扣21 打卡17 设计相邻元素求和服务
    思路:该方案通过构建一个字典,将每个元素值映射到其在二维数组中的坐标位置,以便快速查找。adjacentSum方法根据指定元素的坐标,计算其上下左右相邻元素之和;diagonalSum方法则计算该元素的四个对角线相邻元素之和。每个方法通过判断相邻坐标是否在数组边界内,确保不越界访问。......
  • Leetcode 每日一题 135.分发糖果
    问题描述给定一个整数数组ratings,表示一排孩子的评分。我们需要按照以下规则给孩子们分发糖果:每个孩子至少得到1个糖果。相邻两个孩子中,评分更高的孩子会得到更多的糖果。我们的目标是计算出按照这些规则分发糖果所需的最少糖果数。输入输出格式输入:一个整数数组 rating......
  • 【C++篇】无序中的法则:探索 STL之unordered_map 与 unordered_set容器的哈希美学
    文章目录C++`unordered_map`和`unordered_set`容器详解前言第一章:`unordered_map`和`unordered_set`的概念1.1`unordered_map`和`unordered_set`的定义1.2与`map`、`set`的区别第二章:`unordered_map`和`unordered_set`的构造方法2.1`unordered_map`......
  • clickhouse数据库,时间范围一周,周期为每一小时,聚合数据中的最新,最大值,最小值,平均值,求和
    工作中通过ai改来改去最后实现的,非常好用databaseVal举例:1HOURinterval:1WEEK最新,这里用到了ROW_NUMBER,就是编号,OVER就是分组,分组是通过一小时聚合,聚合后会有编号每一个组的,从1开始到该组结束,取每组的第一条就是最新的SELECTreport_timeAStimeInterval,cpu_usageAScpu......
  • L1-009 N个数求和
    目录一、问题描述二、问题分析 三、源码解答四、参考资料一、问题描述本题的要求很简单,就是求N个数字的和。麻烦的是,这些数字是以有理数分子/分母的形式给出的,你输出的和也必须是有理数的形式。1.输入格式输入第一行给出一个正整数N(≤100)。随后一行按格式a1/b1a2/b......
  • 树哈希 Hints
    简化代码注意hash的值具有可加减的特性,可以极大程度的简化代码。同时可以维护可能作为答案的“匹配池”中的hash值,这样就不用进行(超级dirtywork的)树加减了。树哈希是一种集合哈希(?),所以支持加减!!!hash函数我也不知道为什么大家都在用这个hash函数ullshift(ullx......
  • sicp每日一题[2.72]
    Exercise2.72ConsidertheencodingprocedurethatyoudesignedinExercise2.68.Whatistheorderofgrowthinthenumberofstepsneededtoencodeasymbol?Besuretoincludethenumberofstepsneededtosearchthesymbollistateachnodeencounter......
  • 代码随想录一刷day7 哈希表day1
    遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。常见三种实现哈希表的数据结构:数组set集合map映射下面是setmap的红黑树是一种平衡二叉搜索树,所以k......