【周赛总结】1582-1591 最小连通树,差分数组,前缀和+哈希表,拓扑排序转换

1583.统计不开心的朋友

在这里插入图片描述

比较暴力的方法,主要考察的还是数据结构。

class Solution {
    public int unhappyFriends(int n, int[][] preferences, int[][] pairs) {
        int ans = 0;
        int[] index = new int[n];
        // 维护一个匹配数组
        for (int i = 0 ;i<pairs.length;i++){
            int[] cur = pairs[i];
            index[cur[0]] = cur[1];
            index[cur[1]] = cur[0];
        }
		// 维护每个人心中的排位
        int[][] count = new int[n][n]; // 表示i心中在j的顺位
        for (int i = 0;i<n;i++){
            for (int j = 0; j<n-1;j++){
                count[i][preferences[i][j]] = j+1; // 越大好感越低
            }
        }
		// 开始检查,外层循环每个人,内存枚举如果匹配其他的人。
        for (int i = 0; i<n; i++){// 当前人
            int with = index[i]; // 当前人匹配的人
            for (int j = 0; j<n-1; j++){
                int cur = preferences[i][j];  当前人好感的人
                if (cur == with) break;
                int with_with = index[cur]; // 好感人匹配的人
                // 好感人对匹配人的好感低于对当前人
                if (count[cur][with_with]>count[cur][i]) {
                    ans++;
                    break;
                }
            }
        }
        return ans;
    }
}

1584.连接所有点的最小费用(最小连通树)

经典最小连通树的问题,注意一下这里的Java版本的写法和优化。这里的图是一个稠密图,也就是说点少边多。比较适合prim算法。

在这里插入图片描述

  • Prim算法
class Solution {
    public int minCostConnectPoints(int[][] points) {
        return Prim(points);
    }
    public int Prim(int[][] points) {
        int n=points.length;
        // 需要一个距离表,一个标记表
        int[] dist = new int[n];
        Arrays.fill(dist, Integer.MAX_VALUE);
        boolean[] added=new boolean[n];int ans=0;
        for(int i=0; i<n; i++){
            int min=0;
            // 循环1: 寻找不在联通图中,且距离最近的点
            for(int j=0; j<n; j++){
                if(!added[j]&&dist[j]<dist[min]){
                    min=j;
                }
            }

            added[min]=true;
            if(dist[min]!=Integer.MAX_VALUE) ans+=dist[min];
            // 循环2:更新距离表
            for(int j=0; j<n; j++){
                if(!added[j]){
                    // 更新在新的点进入之后,连通图到各个点的距离。
                    dist[j]=Math.min(dist[j], mdist(points[min], points[j]));
                }
            }
        }
        return ans;
    }
    int mdist(int[] x, int[] y){
        return Math.abs(x[0]-y[0])+Math.abs(x[1]-y[1]);
    }
}
  • Kruskal算法:利用并查集,每次查找最近的两个点,判断是否已经相连,若没则连接。
class Solution {
    class UF{
        int[] fa;
        int[] sz;
        public UF(int N){
            fa = new int[N+1];
            sz = new int[N+1];
            for (int i = 0;i<=N;i++)fa[i] = i;
            for (int i = 0; i<N;i++)sz[i] = 1;
        }

        public int findfa(int x){
            if (fa[x] == x)return x;
            fa[x] = findfa(fa[x]);
            return fa[x];
        }

        public boolean isUnion(int x, int y){
            return findfa(x) == findfa(y);
        }

        public void unino(int x, int y){
            int xfa = findfa(x);
            int yfa = findfa(y);
            //if (xfa == yfa) return;
            if (sz[xfa] > sz[yfa]){
                fa[yfa] = xfa;
                sz[xfa] += sz[yfa];
            }
            else{
                fa[xfa] = yfa;
                sz[yfa] += sz[xfa];
            }
        }
    }

    public int minCostConnectPoints(int[][] points) {
        int n = points.length;
        // 优先队列进行了优化,并且需要重写比较方法,这里用了很实用的lambda表达式。
        PriorityQueue<int[]> pq=new PriorityQueue<>((o1,o2)->o1[2]-o2[2]);
        for (int i = 0; i<n;i++){
            for (int j = i+1; j<n;j++){
                pq.add(new int[]{i,j, dis(points[i], points[j])});
            }
        }
        return Kruskal(pq, n);
    }

    public int Kruskal(PriorityQueue<int[]> pq, int N){
        int res = 0;
        int cnt = 0;
        UF uf = new UF(N);
        while(cnt != N-1){
            int[] cur = pq.poll();
            int x = cur[0], y = cur[1];
            if (uf.isUnion(x,y)) continue;
            else{
                uf.unino(x,y);
                res += cur[2];
                cnt++;
            }
        }
        return res;    
    }
   
    public int dis(int[] A, int[] B){
        return Math.abs(A[0]-B[0])+Math.abs(A[1]-B[1]);
    }
}

1585. 检查字符串是否可以通过排序子字符串得到另一个字符串(思路)

在这里插入图片描述

是一道思路题算法题,我觉着还是挺妙的。但是我很难找到通用的思路。坑神有一个思路,是说每次只是在两个元素之间进行调换。但是这里就有一个问题了,如果该数a的后面还有一个比他大的数字a+n,此时的调换是无法实现的。

因此我们可以从后往前数,如果当前需要a,但是比它大的数字b是存在的且在原数组中是在其后方,这时无论如何调换,都是无法实现的。

class Solution {
    public boolean isTransformable(String ss, String tt) {
        char[] s = ss.toCharArray();
        char[] t = tt.toCharArray();
        int n = ss.length();
        
        // 这里生成了一个数组,生成方法是类名+[]
        // 用deque实现stack。
        Deque<Integer>[] stack = new LinkedList[10];
        for (int i = 0;i<10;i++){
            stack[i] = new LinkedList<>();
        }
        for (int i = 0; i <n;i++){
            int a = s[i]-'0';
            stack[a].push(i);
        }
        for (int i = n-1; i>=0; i--){
            int a = t[i]-'0';
            if (stack[a].isEmpty()) return false;
            for (int j = a+1; j<10;j++){
                if (!stack[j].isEmpty() && stack[j].peek()>stack[a].peek()){
                    return false; 
                }
            }
            stack[a].pop();
            
        }
        return true;
    }
}

1588.所有奇数长度的子串和(低复杂度)

在这里插入图片描述

虽然是一道简单题目,但是可以用更低的复杂度算法。这个方法也是有点前缀和的意思。

class Solution {
    public int sumOddLengthSubarrays(int[] arr) {
        int n = arr.length;
        int ans = 0;
        /* 暴力
        for (int i = 1; i<=n;i += 2){ // 长度
            for (int j = 0; j+i-1<n; j++){
                for (int k = 0; k<i; k++){
                    ans += arr[j+k];
                }
            }
        }
        */
        // 枚举了起点
        for (int i = 0; i<n; i++){
            int sum = 0, cnt = 0;
            // 在该起点下,依次进行累加。
            for (int j = i ; j<n;j++){
                sum += arr[j];
                cnt++;
                if ((cnt&1) == 1) ans += sum;
            }
        }
        return ans;
    }
}

1589.所有排列中的最大和(差分数组)

在这里插入图片描述

是一类比较有特点的题目,主要是利用差分统计区间的覆盖频次问题。维护了一个差分数组,对于每次的覆盖区间,区间头位置+1,区间结尾+1的位置-1。最后在进行累加,这样数组每个位置就对应了相应位置的频次。真的很妙了。

class Solution {
    public int maxSumRangeQuery(int[] nums, int[][] requests) {
        // 差分数组
        Arrays.sort(nums);
        int mod = 1000000007;
        int n = nums.length;
        int[] f = new int[n];
        int ans = 0;
        // 差分数组
        for (int[] e:requests){
            f[e[0]] += 1;
            if (e[1]+1<n) f[e[1]+1] -= 1;
        }
        for (int i = 1; i<n ;i++){
            f[i] += f[i-1];
        }
        //
        Arrays.sort(f);
        for (int i = n-1; i>=0; i--){
            if (f[i] == 0) return ans;
            //ans = (int)((long)(ans + f[i]*nums[i])%mod);
            ans = (int)(ans + 1L*f[i]*nums[i])%mod;
        }
        return ans;
    }
}

1590.使数组和能被P整除

在这里插入图片描述

也是一道好题,利用了取模,和前缀和。由于我们要求得到前缀和除P之后的余数,因此我们可以利用hashmap存储一下。然后每次查找最近的符合要求的位置,计算得到长度。尤其注意大数的处理。一定小心一切可能爆整数的存在。

class Solution {
    public int minSubarray(int[] nums, int p) {
        int n = nums.length;
        Map<Integer, Integer> hashmap = new HashMap<>();
        hashmap.put(0,0);
        int[] presum = new int[n+1];
        int sum = 0;
        int ans = Integer.MAX_VALUE;    
        for (int i = 0; i < n ; i++){
            int now = nums[i];
            sum = (int)((long)(sum+now)%p);
            presum[i+1] = (int)((long)(presum[i]+now)%p);
        }
        int res = sum % p;

        for (int i = 1; i<=n ;i++){
            int cur = presum[i];
            hashmap.put(cur,i);
            // 思路:寻找res = cur-target;
            int target = (int)((long)(cur-res+p)%p);
            if (hashmap.containsKey(target)){
                ans = Math.min(ans, i-hashmap.get(target));
            }
        }
        
        if (ans == Integer.MAX_VALUE || ans == n)return -1;
        return ans;
    }
}

1591.奇怪的打印机II(拓扑排序)

在这里插入图片描述

又是一道很好的题,这周的周赛真是好题啊。一道转换为拓扑排序的题目。看似是涂色,其实可以构建起颜色转换之间的有向连接。当颜色1需要覆盖在颜色2上,可以得到一个从2指向1的标志,并且1的限制条件+1。如果某个颜色的限制条件为0,就可以揭开。和选课问题一样/

class Solution {
    public boolean isPrintable(int[][] targetGrid) {
        int n = targetGrid.length;
        int m = targetGrid[0].length;
        int[] up = new int[61];
        Arrays.fill(up, Integer.MAX_VALUE);
        int[] button = new int[61];
        int[] left = new int[61];
        Arrays.fill(left, Integer.MAX_VALUE);
        int[] right = new int[61];
        //Set<Integer> set = new HashSet<>();

        for (int i = 0; i<n; i++){
            for (int j = 0; j<m; j++){
                int color = targetGrid[i][j];
                //set.add(color);
                up[color] = Math.min(up[color], i);
                left[color] = Math.min(left[color], j);
                right[color] = Math.max(right[color], j);
                button[color] = Math.max(button[color], i);
            }
        }

        List<Integer>[] egde = new ArrayList[61]; // 构建图
        for (int i = 0; i<61;i++){
            egde[i] = new ArrayList<>();
        }
        int[][] have = new int[61][61]; // 防止重复
        int[] ins = new int[61]; // 入读

        for (int i = 0; i<n;i++){
            for (int j = 0;j <m; j++){
                int color = targetGrid[i][j];
                for (int k = 1; k<61;k++){
                    if (k == color) continue;
                    if (i>=up[k] && i<= button[k] && j>=left[k] && j<=right[k] && have[color][k] == 0) {
                        have[color][k] = 1;
                        egde[color].add(k);
                        ins[k]++;
                    }
                }
            }
        }
        // 拓扑排序
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 1; i<61; i++){
            if (ins[i] == 0) queue.offer(i);
        }
        int num = 0;
        while (!queue.isEmpty()){
            int cur = queue.poll();
            num++;
            for (int next:egde[cur]){
                ins[next]--;
                if (ins[next] == 0) {
                   queue.offer(next); 
                }
            }
        }
        System.out.println(num);
        return num == 60;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值