• double-weekly-contest-35


    double-weekly-contest-35

    • 本次双周赛重要知识点为前缀和,之前不了解前缀和相关知识点,导致题目做的磕磕绊绊

    • 连续子数组问题往往可以通过前缀和来解决

    1/5503. 所有奇数长度子数组的和

    1. 暴力枚举所有奇数长度子数组并且求和,时间复杂度O(n^3)
    class Solution {
        public int sumOddLengthSubarrays(int[] arr) {
            int len = arr.length;
            int res = 0;
            
            for(int i=0;i<len;i++){
                for(int j=i;j<len;j++){
                    if((j-i)%2==0){
                        res+= add(arr,i,j);
                    }
                }
            }
            return res;
        }
        public int add(int[] arr, int start, int end){
            int res = 0;
            for(int i=start; i<=end; i++){
                res += arr[i];
            }
            return res;
        }
    }
    
    1. 使用前缀和数组预计算数组子区间的和,prefix[j]-prefix[i-1]等于从 i 到 j 的元素和,时间复杂度缩减到O(n^2)
    // 前缀和
    // 在解法一中,add函数是计算数组从start到end的和,数组被反复计算多次,这里可以使用前缀和预先计算
    class Solution{
        public int sumOddLengthSubarrays(int[] arr){
            int len = arr.length;
            int res = 0;
        
            int[] prefix = new int[len];
            prefix[0] = arr[0];
            for(int i=1; i<len; i++){
                prefix[i] = prefix[i-1] + arr[i];
            }
    
            for(int i=0;i<len;i++){
                for(int j=i;j<len;j+=2){
                    if(i==0)
                    res+= prefix[j];
                    else
                    res+= prefix[j]-prefix[i-1];
                }
            }
            return res;
        }
    }
    

    2/5504. 使数组和能被 P 整除

    • 第一个想到的是使用循环遍历删除(代码里写的DFS,但实际上不是DFS),枚举nums数组中的每个数字,从该数字开始删除,如果删除后的数字能被p整除,则更新一个最短删除子数组,这样的做法时间复杂度高,超时
    class Solution {
    //     子数组连续
    //     只能删除一个子数组
    //     回溯,每次只删除一个元素,下一步删除下一个元素
    //     当删除结果可以被p整除时将该结果更新到minDelete中
    //     回溯完全部分支后能得到最小删除数组
        int minDel;
        int p;
        public int minSubarray(int[] nums, int p) {
            long numsSum = 0;
            minDel = Integer.MAX_VALUE;
            this.p = p;
            for(int i=0;i<nums.length;i++){
                numsSum += nums[i];
            }
            //不需要删除任何元素,和已经能被p整除
            if(numsSum%p==0)return 0;
            for(int i=0; i<nums.length;i++){
                deleteDFS(nums, numsSum-nums[i], i,i);    
            }
            //当minDel为Integer.MAX_VALUE时,回溯中没有进行任何一次删除,即任何方案都不能使得和被整除
            return minDel==Integer.MAX_VALUE?-1:minDel;
        }
        
        public void deleteDFS(int[] nums, long left, int delBegin, int cur){
            if(left<p)return;
            //当前删除已可以被整除时,不需要判断下一个,下一个minDel一定比当前minDel大
            if(left%p==0){
                minDel = Math.min(minDel, cur-delBegin+1);
                return;
            }
            if(cur+1<nums.length)
            deleteDFS(nums, left-nums[cur+1], delBegin, cur+1);
        }
    }
    
    • 由于取模对加减法有分配律,所以可以通过计算前缀和以及对前缀和进行取模来计算子数组区间,当区间取模的结果等于总和取模的结果时,将该区间删除,剩余的数组和就能够被p整除
    • 在实现上可以使用一个哈希表(HashMap)储存距离当前位置最近的targetmod,具体细节见代码
    class Solution{
        // 取模前缀和
        public int minSubarray(int[] nums, int p){
            long sum =0;
            for(int num:nums){
                sum+=num;
            }
            int mod = (int)(sum%p);
            System.out.println(mod);
            if(mod==0)return 0;
    
            int res = nums.length;
            // key-value : curmod-index
            HashMap<Integer,Integer> map = new HashMap<>();
            // 当curmod==mod的时候,该前缀和求模等于总和求模,所以该前缀全部可以删除
            // 此时的target==0
            map.put(0,-1);
    
            sum=0;
            for(int i=0; i<nums.length;i++){
                sum += nums[i];
                int curmod = (int)(sum%p);
                int targetmod = curmod>=mod? curmod-mod: curmod-mod+p;
                if(map.containsKey(targetmod)){
                    res = Math.min(res, i-map.get(targetmod));
                }
                map.put(curmod, i);
            }
            return res==nums.length?-1:res;
        }
    }
    

    3/5505. 所有排列中的最大和

    • 自己解答的时候使用的是两个哈希函数来计算查询次数,start放置区间开始的位置和次数,end放置区间结束的位置和次数,通过start-end动态计算count就能判断当前位置被查询过多少次。
    • 在看了题解后发现还可以通过使用差分数组计算区间累加的数组
    class Solution {
    //     使用requests计算每个位置被查询的次数,查询次数最多的位置放置数组最大值,第二多的放数组第二大值
        public int maxSumRangeQuery(int[] nums, int[][] requests) {
            int res = 0;
            int[] times = new int[nums.length];
            HashMap<Integer,Integer> start = new HashMap<>();
            HashMap<Integer,Integer> end = new HashMap<>();
            
            for(int[] request:requests){
                start.put(request[0], start.getOrDefault(request[0],0)+1);
                end.put(request[1], end.getOrDefault(request[1],0)+1);
            }
            // System.out.println(end);
            int count=0;
            for(int i=0;i<nums.length; i++){
                if(start.containsKey(i)){
                    count+=start.get(i);
                }
                times[i] = count;
                if(end.containsKey(i)){
                    count-=end.get(i);
                }
            }
            // System.out.println(Arrays.toString(times));
            
            Arrays.sort(times);
            Arrays.sort(nums);
            
            for(int i=0; i<nums.length; i++){
                // if(nums[i]==0)break;
                res += times[i]*nums[i];
                res %= 1000000007;
            }  
            return res;
        }
    }
    
    • 使用差分数组
    class Solution {
    //     使用requests计算每个位置被查询的次数,查询次数最多的位置放置数组最大值,第二多的放数组第二大值
        public int maxSumRangeQuery(int[] nums, int[][] requests) {
            int res = 0;
            int[] fre = new int[nums.length+1];
            for(int[] request:requests){
                fre[request[0]] ++;
                fre[request[1]+1] --;
            }
    
            for(int i=1; i<nums.length; i++){
                fre[i] += fre[i-1];
            }
            //System.out.println(Arrays.toString(fre));
            Arrays.sort(fre);
            Arrays.sort(nums);
            
            for(int i=nums.length; i>=1 && fre[i]>0; i--){
                // if(nums[i]==0)break;
                res += fre[i]*nums[i-1];
                res %= 1000000007;
            }  
            return res;
        }
    }
    

    4/5506. 奇怪的打印机 II

    • 判断出颜色与颜色之间的关系,一个颜色只能出现一次,所以每个颜色有严格的染色顺序,当B被包括在A矩阵中时,A要先于B被染色,即建立A->B的有向边。整个矩阵targetGrid全部建立完边之后,判断图是否满足拓扑排序,如果不满足则存在环。
    class Solution {
        // 建立有向图,对每个颜色建立边
        // 图必须存在拓扑排序,即一个颜色必须在另一个颜色之后进行染色
        public boolean isPrintable(int[][] targetGrid) {
            int rows= targetGrid.length, cols = targetGrid[0].length;
    
            // 初始化每个颜色矩阵的边界
            int[] top= new int[61],bottom= new int[61],left= new int[61],right = new int[61];
            Arrays.fill(top,61);
            Arrays.fill(left,61);
            Arrays.fill(bottom,-1);
            Arrays.fill(right,-1);
            int color = 0;
            // 建立每个颜色的矩阵边界
            for(int i=0;i<rows;i++){
                for(int j=0; j<cols; j++){
                    color = targetGrid[i][j];
                    top[color] = Math.min(top[color], i);
                    bottom[color] = Math.max(bottom[color], i);
                    left[color] = Math.min(left[color], j);
                    right[color] = Math.max(right[color], j);
                }
            }
    
            // 根据矩阵建立有向图,遍历targetGrid,
            // 当前位置颜色X在某个矩阵A中但是不为矩阵A的颜色时,建立从A到X的边
            // X可以存在于多个矩阵中
            // 变量:是否存在边-防止重复建立边;入度,便于后期判断是否拓扑排序;邻接表,从i出发到达的点
            boolean[][] hasEdge= new boolean[61][61];
            int[] inDegree = new int[61];
            List<List<Integer>> adjs = new ArrayList<>();
            for(int i=0;i<=60;i++){
                adjs.add(new ArrayList<Integer>());
            }
            int curColor = 0;
            // 建立图
            for(int i=0;i<rows;i++){
                for(int j=0; j<cols; j++){
                    curColor = targetGrid[i][j];
                    for(color=1; color<=60; color++){
                        if(i>=top[color] && i<=bottom[color] && j>=left[color] && j<=right[color])
                            if(curColor!=color && !hasEdge[color][curColor]){
                                adjs.get(color).add(curColor);
                                inDegree[curColor]++;
                                hasEdge[color][curColor] = true;
                            }
                    }
                }
            }
    
            // 寻找入度为0的颜色点,减小该点连结的点的入度,直到所有点的入度都为0
            ArrayList<Integer> zeroC = new ArrayList<>();
    
            while(true){
                int i;
                for(i=1;i<=60;i++){
                    if(inDegree[i]==0){
                        zeroC.add(i);
                        for(Integer pointTo:adjs.get(i)){
                            inDegree[pointTo]--;
                        }
                        inDegree[i]=-1;
                        break;
                    }
                }
                if(i==61)break;
            }
            return zeroC.size()==60;
    
        }
    }
    
  • 相关阅读:
    Linq To Sql 练习
    伪静态在webconfig中配置
    log4net.dll配置以及在项目中应用
    C#Windows服务安装
    .net平台推送ios消息
    asp.net下js调用session
    MAC地址泛洪攻击测试
    wifipineapple使用教程
    python程序的调试方法
    python import的用法
  • 原文地址:https://www.cnblogs.com/Yuasin/p/13703988.html
Copyright © 2020-2023  润新知