[LeetCode]网格照明

164 阅读5分钟

这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战

题目

leetcode-cn.com/problems/gr…

解析

暴力

对于这种灯照之类的问题,有一个最粗暴的解法:

  • 在查询之前,把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%的用户