这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战
题目
解析
暴力
对于这种灯照之类的问题,有一个最粗暴的解法:
- 在查询之前,把nXn的所有网格的情况,都放上去。
然而,在这个问题上,这个解法就有一个问题,根据题目:
- 在每次查询后,都要关闭相邻的8个网格上的所有灯。
那么,这就相当于每次查询之后,都要再根据目前已经亮着的灯,重新做一次mapping,这样子时间复杂度就会相当地高。
或许可能会想:这里如果根据原有的mapping做减法,是否可以?
答案是不可以的,因为可能出现重叠,需要判断是否有其他的灯泡照亮该位置。
mapping存储
那么,换一种思路:
-
既然可以照亮四个方向(上下、左右、左右对角线),那么其实具体哪些点可以被照亮,是确定的。假设灯泡是(x,y),那么:
- 对于范围内的a,有:(x,a)被照亮。
- 对于范围内的b,有:(b,y)被照亮。
- 对于任意范围内的(c,d)满足c - d = x - y 的,有:(c,d)被照亮。
- 对于任意范围内的(e,f)满足 e+f = x+y的,有:(e,f)被照亮。
那么,其实对于一个灯泡来说,照亮的范围就由这么四个维度决定。因此存储这么4个维度即可:
- x->(x,a)
- y->(b,y)
- x-y
- x+y
省去了做整个数组mapping的时间。
这样子做的话,假设灯泡有L个,时间复杂度是O(4L),空间复杂度是O(4L).
然后对于查询过后的操作,总计是8个方向,并且要查询其中的哪些灯泡;查询到了就要更新上述的4个存储维度。那么,对于每一次的查询操作:
-
查询本身动作的时间复杂度是O(4):只要查询是否存在存储的维度之中,即可。
-
查询过后,要查询附近包括自身的9个方格是否存在灯泡;存在的话,就要关闭对应的灯泡,修改照亮的区域。
分解这一步为2个问题:
第一,如何做到快速查找这9个方格是否在灯泡区域之中?
第二,修改需要如何做?
- 先来考虑第二个问题:
如果在某次查询之中,发现了查询范围内存在灯泡,那么就要关掉这个灯泡,需要更新上述的4个存储维度。
然而,这些存储是可能会重复的(比如:(1,0)和(1,1),都会使得(1,X)亮起来);
因此,在存储上,需要将具体哪些灯泡会影响了这些维度的存在,储存起来。这样子在删除的时候,我们将对应的灯泡去掉;如果去掉了某个灯泡,影响该维度存在的灯泡都不存在了,那么这个维度也可以去掉了。
-
第二个问题有解决方案了,接下来再来考虑第一个问题:
对于某个点(x,y),要检查的点的位置是:
-
(x-1,y-1)~(x+1,y+1) 这么9个点。
那么,从存储的第一或者第二个维度,即可快速查找出是否存在对应的灯泡。
从这个地方也可以优化一下上面所说的第二个问题的解决方案:
- 实际上,不必存储点,只需要存储x或者y的值即可。
-
综上,总结一下所做的工作(假设有L个灯泡,N次查询):
-
第一步,做indexing:
//x Map<Integer, Set<Integer>> columns = new HashMap<>(); //y Map<Integer, Set<Integer>> lines = new HashMap<>(); //x-y Map<Integer, Set<Integer>> minus = new HashMap<>(); //x+y Map<Integer, Set<Integer>> add = new HashMap<>(); for (int[] lamp : lamps) { safePut(lamp[0],lamp[1],columns); safePut(lamp[1],lamp[0],lines); safePut(lamp[0] - lamp[1],lamp[0],minus); safePut(lamp[0] + lamp[1],lamp[0],add); } public void safePut(int val,int idx,Map<Integer,Set<Integer>> dic){ Set<Integer> set = dic.getOrDefault(val,new HashSet<>()); set.add(idx); dic.put(val, set); }
时间复杂度O(4L),空间复杂度O(4L^2^).
-
第二步:查询
for (int i = 0; i < queries.length; i++) { if (contains(queries[i])) result[i] = 1; else result[i] = 0; //modify modify(queries[i],n); } public boolean contains(int[] ptr){ int x = ptr[0],y = ptr[1]; return columns.containsKey(x) || lines.containsKey(y) || minus.containsKey(x-y) || add.containsKey(x+y); }
时间复杂度O(N).
-
第三步:修改
public void modify(int[] ptr,int n){ int x = ptr[0],y = ptr[1]; //cache for final delete(in case no existing for indexing dimension) List<int[]> ptrToDelete = new ArrayList<>(); //search in x for (int i = Math.max(0,x-1); i <= x + 1 && i<n ; i++) { Set<Integer> p = columns.getOrDefault(i,null); if(null == p) continue; for (int j = Math.max(0,y-1); j<=y+1 && j<n ;j++){ if(p.contains(j)) ptrToDelete.add(new int[]{i,j}); } } delete(ptrToDelete); } public void delete(List<int[]> ptrs){ for (int[] ptr : ptrs) { int x = ptr[0],y = ptr[1]; remove(x,y,columns); remove(y,x,lines); remove(x-y,x,minus); remove(x+y,x,add); } } public void remove(int idx,int val,Map<Integer,Set<Integer>> dic){ Set<Integer> s = dic.getOrDefault(idx,null); if(null == s) return; s.remove(val); if(s.size()==0) dic.remove(idx); }
时间复杂度:O(9).(每次固定修改9个点.)
综合一下,时间复杂度就是O(4L+9N)->O(L+N),空间复杂度是(4L^2^)->O(L^2^).
结果:
执行用时:57 ms, 在所有 Java 提交中击败了96.92%的用户
内存消耗:76.6 MB, 在所有 Java 提交中击败了13.84%的用户
修改内存消耗
根据上述的成绩,其实可以看出来:消耗的内存还是比较大的.
其实,可以将点,使用Long将其hash并存起来(注意题目中所给的点大小).
随后,对于每一个路径,可以直接将Set改为记录次数即可,去重的工作就交给上面的点集合.
同时,需要注意到:所给的灯可能重复,因此在添加的时候,需要判断是否该灯之前已经添加过了.
修改后,代码如下:
class Solution {
//x
Map<Integer, Integer> columns = new HashMap<>();
//y
Map<Integer, Integer> lines = new HashMap<>();
//x-y
Map<Integer, Integer> minus = new HashMap<>();
//x+y
Map<Integer, Integer> add = new HashMap<>();
//ptr
Set<Long> ptrSet = new HashSet<>();
public int[] gridIllumination(int n, int[][] lamps, int[][] queries) {
//init
int lampLength = lamps.length;
int[] result = new int[queries.length];
//x
columns = new HashMap<>(lampLength);
//y
lines = new HashMap<>(lampLength);
//x-y
minus = new HashMap<>(lampLength);
//x+y
add = new HashMap<>(lampLength);
//ptr
ptrSet = new HashSet<>(lampLength);
//indexing
for (int[] lamp : lamps) {
if(ptrSet.contains(hashPtr(lamp))) continue;
safePut(lamp[0],columns);
safePut(lamp[1],lines);
safePut(lamp[0] - lamp[1],minus);
safePut(lamp[0] + lamp[1],add);
ptrSet.add(hashPtr(lamp));
}
//search
for (int i = 0; i < queries.length; i++) {
if (contains(queries[i])) result[i] = 1;
else result[i] = 0;
//modify
modify(queries[i],n);
}
return result;
}
public void safePut(int val,Map<Integer,Integer> dic){
int cnt = dic.getOrDefault(val,0);
cnt++;
dic.put(val, cnt);
}
public boolean contains(int[] ptr){
int x = ptr[0],y = ptr[1];
return
columns.containsKey(x) || lines.containsKey(y) || minus.containsKey(x-y) || add.containsKey(x+y);
}
public void modify(int[] ptr,int n){
int x = ptr[0],y = ptr[1];
//cache for final delete(in case no existing for indexing dimension)
List<int[]> ptrToDelete = new ArrayList<>();
//search in x
for (int i = Math.max(0,x-1); i <= x + 1 && i<n ; i++) {
if(!columns.containsKey(i)) continue;
for (int j = Math.max(0,y-1); j<=y+1 && j<n ;j++){
int[] curPtr = new int[]{i,j};
if(ptrSet.contains(hashPtr(curPtr))) {
ptrToDelete.add(curPtr);
ptrSet.remove(hashPtr(curPtr));
}
}
}
delete(ptrToDelete);
}
public void delete(List<int[]> ptrs){
for (int[] ptr : ptrs) {
int x = ptr[0],y = ptr[1];
remove(x,columns);
remove(y,lines);
remove(x-y,minus);
remove(x+y,add);
}
}
public void remove(int idx,Map<Integer,Integer> dic){
Integer s = dic.getOrDefault(idx,null);
if(null == s) return;
s--;
if(s==0) dic.remove(idx);
else dic.put(idx,s);
}
public long hashPtr(int[] ptr){
return ((long)ptr[0]<<32) + (long)ptr[1];
}
}
执行用时:54 ms, 在所有 Java 提交中击败了98.46%的用户
内存消耗:52.8 MB, 在所有 Java 提交中击败了83.08%的用户