题目链接. - 力扣(LeetCode)
我有两种解决的办法:听我细细道来!
建模过程:
明确问题:通俗讲题干意思为我们有一个m*n的长方形的网格,每个格子里面会有一个正整数,我们要做的就是在这个网格中,找到3个菱形,这3个菱形满足都满足四个条件:菱形的顶点必定在格子的中心点;菱形只能是由正方形旋转四十五度得来;任意两个菱形不能完全重合;这三个菱形的边经过的格子的数加起来,是所有可能结果中最大的。
合理假设:
既然菱形只能是由正方形旋转四十五度得来,那么我们确定了菱形的一条边长,并确定了中心的点,那么菱形的所经过的每个格子都可以求出来,这些格子加起来的和也简而易求。这道题的数据量,n和m都是小于等于100的正整数。枚举出来每一个菱形的时间复杂度最多达不到100 *100 *100。所以存在我们枚举出每一个菱形,并且取出最大的3个菱形的可能性。题中没有要求我们输出具体的菱形信息,只要求了输出菱形了,我们在存储枚举出来的菱形的时候可以用菱形和并记录数量的方式进行存储。这样又简化了存储方式。
搭建模型:
我们可以将一个菱形抽象成中心点和最上面的点距中心点的距离的这两个数据。用两层循环分别枚举中心点和边长,我们使用stl库中的map存储菱形,用菱形和作为key值,value值记录数量。Map可以排序,最后只需反向遍历一遍即可。
class Solution {
public:
vector<int> getBiggestThree(vector<vector<int>>& grid) {
long long m = grid.size(); //获取矩阵的m
long long n = grid[0].size();//获取矩阵的n
//我们不妨规定m是纵轴,n是横轴
map<int,int> a;
for(int i=0;i<m;i++)
for(int j =0;j<n;j++)//枚举中心点
for(int len = 0;len<min(n,m);len++)//枚举中心距离最上面点的距离
{
if(i+len >=m || i -len <0 || j - len <0||j+len >=n)continue;//判断菱形是否合法
//若合法则求出这个菱形的菱形和
int sum =0;
if(len ==0)//分情况讨论,特殊情况 len=1
{
sum += grid[i][j];
}
else // 一般情况
{
sum += grid[i+len][j];
sum += grid[i-len][j];
sum += grid[i][j+len];
sum += grid[i][j-len];
//先把菱形的四个顶点加起来
//我们不妨规定m是纵轴,n是横轴
//枚举横轴x的值,sum加上横坐标等于 固定值的 且在菱形上的格子;
for(int plen = 1;plen <len;plen++)
{
sum+= grid[i+plen][j+len -plen];
sum+= grid[i-plen][j+len -plen];
}
for(int plen = 1;plen <len;plen++)
{
sum+= grid[i+plen][j-len +plen];
sum+= grid[i-plen][j-len +plen];
}
}
a[sum]++;//往a中存
}
//取三个
vector<int> ans;
//迭代器反向遍历map
for(auto t = a.rbegin();t!=a.rend()&&ans.size()!=3;t++)
{
ans.push_back(t->first);
}
return ans;
}
};
其实写中途我就发现了问题
思路分析:
首先数据量1 <= m, n <= 100是模拟枚举的方式可以通过的数据量
确定了菱形的核心参数,将菱形抽象成三个数值
利用这三个数值以枚举中心点和顶点距中心点距离的方式使用三重for循环枚举菱形
对每一组数组进行特判,分情况处理,对于一般情况再采用一层for循环计算菱形和。
程序的时间主要取决于这里的四重for循环复杂度为O(n*m*min(n,m)*min(n,m))。
用map去重,时间复杂度O(nlogn),去重所花费的时间也可以接受。
最后使用迭代器反向迭代map,取出其中最大的三个菱形和放到vector里返回答案即可。
存在问题:
时间复杂度
O(n*m*min(n,m)*min(n,m))
在n很大的时候,程序运行很慢,甚至存在不通过的风险。
能否优化?可见枚举部分一点也优化不了,因为这三个参数都是确定一个菱形必须要有的。那么我们可以从最内层循环入手,既然菱形已经确定了,那么他占用的格子就是确定的,那么菱形和也是确定的,那么我们有没有一种方式O(1)的求出菱形和呢?
改进意见
菱形占用的格子都是相邻的,对于这种格子相邻的情况可以考虑使用前缀和进行优化,这题是二维,故考虑二维前缀和,但是此题相邻是斜着相邻,那么我们不妨做两个方向的斜向前缀和,然后通过差分的方式O(1)得求出菱形和。
所以我就有了第二种写法!
long long sum1[105][105];
long long sum2[105][105];
//创建二维前缀和,因为是斜向前缀和所以我们使用二维数组进行创建
class Solution {
public:
vector<int> getBiggestThree(vector<vector<int>>& grid) {
long long m = grid.size(); //获取矩阵的m
long long n = grid[0].size();//获取矩阵的n
//我们不妨规定m是纵轴,n是横轴
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
sum1[i][j] = sum1[i - 1][j - 1] + grid[i - 1][j - 1]; //维护正对角线方向前缀和
sum2[i][j] = sum2[i - 1][j + 1] + grid[i - 1][j - 1]; //维护副对角线方向前缀和
}
}
map<int,int> a;
for(int i=1;i<=m;i++)
for(int j =1;j<=n;j++)//枚举中心点
for(int len = 0;len<min(n,m);len++)//枚举中心距离最上面点的距离
{
if(i+len >m || i -len <=0 || j - len <=0||j+len >n)continue;//判断菱形是否合法
//若合法则求出这个菱形的菱形和
long long sum =0;
if(len ==0)//分情况讨论,特殊情况 len=0
{
sum += grid[i-1][j-1];
}
else // 一般情况
{
sum += sum1[i][j+len]- sum1[i-len-1][j-1];
//最右边的 顶点 到 最顶上的顶点
sum += sum1[i+len][j]- sum1[i-1][j-len-1];
//最下面的点到最左边的点
sum += sum2[i][j-len]-sum2[i-len-1][j+1] ;
//最左边的 顶点 到 最顶上的顶点
sum += sum2[i+len][j]-sum2[i-1][j+len+1] ;
//最下面的点到 最右边的点
sum -= (grid[i-1][j+len-1]+ grid[i-1][j-len-1]+ grid[i-len-1][j-1]+ grid[i+len-1][j-1]);
//因为四个顶点加了两次,所以要减掉
}
a[sum]++;//往a中存
}
//取三个
vector<int> ans;
//迭代器反向遍历map
for(auto t = a.rbegin();t!=a.rend()&&ans.size()!=3;t++)
{
ans.push_back(t->first);
}
return ans;
}
};