【周赛总结】单调栈,离线查询,绝对值不等式,Trie树


第220,221场周赛和第42场双周赛

1696. 跳跃游戏 VI (单调栈)

在这里插入图片描述

利用单调栈的思路维护。单调栈的特点在于,栈内存储的是坐标而比较的内容则是映射的数据的数值大小。

对于这道题目,我们在进行转移的时候,首先检查栈头的元素是不是距离太远。再加入当前的元素,然后维护单调栈。

class Solution {
    public int maxResult(int[] nums, int k) {
        // 单调栈的思路,单调栈中存储的index
        // dp[i]表示当右端点为ii时候的最大值
        // 首先需要判断index和当前的index的距离,删除不合法的
        // 然后计算出加上当前的nums[index]的最大值
        // 单调栈的维护保证新入栈的位置是递减的。也即是说,要是栈尾元素小于当前元素就弹出
        // 添加当前的元素
        int n = nums.length;
        Deque<Integer> queue = new LinkedList<>();
        int[] dp = new int[n];
        dp[0] = nums[0];
        queue.offer(0);

        for(int i = 1; i<n;i++){
            while(!queue.isEmpty() && i-queue.peekFirst()>k){
                queue.pollFirst();
            }
            dp[i] += dp[queue.peekFirst()]+nums[i]; 
            while(!queue.isEmpty() && dp[queue.peekLast()]<=dp[i]){
                queue.pollLast();
            }
            queue.offerLast(i);
        }
        return dp[n-1];

1697. 检查边长度限制的路径是否存在 (离线查询)

在这里插入图片描述在这里插入图片描述

离线查询的思路特点在于,限制了查询的范围。也就是在小于某个数值的范围内进行搜索。特点是对需要查询的数值进行排序,一边进行插入,一边进行排序。对于这道题目,我们排序之后。先插入小于限制的数字,然后检查连通性。这样得到的结果就是符合限制的。

特别需要注意,这种题目与,需要额外在排序时候加上index,这样每次查询之后需要对应到合适的位置去

class Solution {
    public boolean[] distanceLimitedPathsExist(int n, int[][] edgeList, int[][] queries) {
        UF uf = new UF(n);
        int m = queries.length;
        int l = edgeList.length;
        int[][] q = new int[m][4];
        for(int i = 0;i<m;i++) {
            q[i][3] = i;
            q[i][0] = queries[i][0];
            q[i][1] = queries[i][1];
            q[i][2] = queries[i][2];
        }
        Arrays.sort(edgeList, (a,b)->a[2]-b[2]);
        Arrays.sort(q, (a,b)->a[2]-b[2]);
        int index = 0;
        boolean[] ans = new boolean[m];
        // ---------------典型特点:外层循环查询,内层while循环,不断插入满足要求的内容。-----
        for(int i = 0;i<m;i++){
            int limit = q[i][2];
            while(index<l){
                if(edgeList[index][2]>=limit) break;
                uf.union(edgeList[index][0], edgeList[index][1]);
                index++;
            }
            ans[q[i][3]] = uf.isUnion(q[i][0],q[i][1]);
        }
        return ans;
    }
    class UF{
        private int[] fa;
        private  int[] sz;
        public UF(int N){
            fa = new int[N];
            sz = new int[N];
            for(int i = 0 ;i<N;i++) fa[i] = i;
            Arrays.fill(sz,1);
        }

        public int findFa(int n){
            if(fa[n] == n) return n;
            fa[n] = findFa(fa[n]);
            return fa[n];
        }

        public boolean isUnion(int a, int b){
            return findFa(a) == findFa(b);
        }

        public void union(int a, int b){
            int afa = findFa(a);
            int bfa = findFa(b);
            if(afa == bfa) return;
            int asz = sz[a];
            int bsz = sz[b];
            if(asz>bsz){
                fa[bfa] = afa;
                sz[a] += sz[b];
            }else{
                fa[afa] = bfa;
                sz[b] += sz[a];
            }
        }
    }
}

1703. 得到连续 K 个 1 的最少相邻交换次数 (绝对值不等式)

在这里插入图片描述

首先有一个模板问题,对于一系列数字,选取一个值作为特殊点,使得其余点到特殊点的距离和最小。请问特殊点是哪一个。这个问题的答案特殊点就是中点。

但是对于这个题目这里需要稍微变化,因为我们是要求连续的,因此实际是 ∣ a − x ∣ + ∣ a − x − 1 ∣ + ∣ a − x − 2 ∣ |a-x|+|a-x-1|+|a-x-2| ax+ax1+ax2,因为会少一个位置。

对于这个问题,我们可以稍微修改,a’ = a-a.size();这样就回到了我们最熟悉的问题。最后的两边的移动和,其实就是,

  • 左侧 ( a ′ [ m i d ] − a ′ [ l ] ) + ( a ′ [ m i d ] − a ′ [ l + 1 ] ) . . . + ( a ′ [ m i d ] − a ′ [ m i d − 1 ] ) = ( m i d − l ) ∗ a ′ [ m i d ] − ( s u m [ m i d − 1 ] − s u m [ l ] ) (a'[mid]-a'[l])+(a'[mid]-a'[l+1])...+(a'[mid]-a'[mid-1]) = (mid-l)*a'[mid] - (sum[mid-1]-sum[l]) (a[mid]a[l])+(a[mid]a[l+1])...+(a[mid]a[mid1])=(midl)a[mid](sum[mid1]sum[l]),
  • 右侧 ( a ′ [ m i d + 1 ] − a ′ [ m i d ] ) + ( a ′ [ m i d + 2 ] − a ′ [ m i d ] ) . . . + ( a ′ [ m i d + r ] − a ′ [ m i d ] ) = ( s u m [ r ] − s u m [ m i d ] ) − ( r − m i d ) ∗ a ′ [ m i d ] (a'[mid+1]-a'[mid])+(a'[mid+2]-a'[mid])...+(a'[mid+r]-a'[mid]) = (sum[r]-sum[mid])- (r-mid)*a'[mid] (a[mid+1]a[mid])+(a[mid+2]a[mid])...+(a[mid+r]a[mid])=(sum[r]sum[mid])(rmid)a[mid]
// 对于随机index访问的get和set方法,一般ArrayList的速度要优于LinkedList。因为ArrayList直接通过数组下标直接找到元素;LinkedList要移动指针遍历每个元素直到找到为止。
// 新增和删除元素,一般LinkedList的速度要优于ArrayList。因为ArrayList在新增和删除元素时,可能扩容和复制数组;LinkedList实例化对象需要时间外,只需要修改指针即可。
class Solution {
    public int minMoves(int[] nums, int k) {
        // 绝对值不等式的问题,最小值是在中点位置
        // |a-x|+|a-x-1|+|a-x-2|
        List<Integer> dis = new ArrayList<>(); // 移动到位置0时候需要的次数
        //List<Integer> dis = new LinkedList<>(); 
        int n = nums.length;
        for (int i = 0; i<n;i++){
            if (nums[i] == 1){
                dis.add(i-dis.size());
            }
        }
        int m = dis.size();
        int[] sum = new int[m+1];
        for(int i = 1; i<=m; i++){
            sum[i] = sum[i-1]+dis.get(i-1);
        }
        int ans = Integer.MAX_VALUE;
        for(int l = 0;l<=m-k;l++){
            int r = l+k-1;
            int mid = (l+r)>>1;
            int am = dis.get(mid);
            int left = (mid-l)*am-(sum[mid]-sum[l]);
            int right = (sum[r+1]-sum[mid+1])-(r-mid)*am;
            ans = Math.min(ans, left+right);
        }
        return ans;
    }
}

1707. 与数组中元素的最大异或值(Trie树)

在这里插入图片描述

典型字典树的题目!!!模板题目

核心函数就是两个,insert和query

class Solution {
    int[][] son; // 用数组实现字典树
    int idx = 0;
    public int[] maximizeXor(int[] nums, int[][] queries) {
        Arrays.sort(nums);
        int n = nums.length;
        son = new int[n * 31][2]; // 最大就是2^31-1,最小是-2^31 并且可能每个数字的路径都完全不重复。
        
        Pair[] pair = new Pair[queries.length];// 添加index
        for(int i = 0; i < queries.length; i++) {
            pair[i] = new Pair(queries[i][0], queries[i][1], i);
        }
        
        Arrays.sort(pair, (o1, o2) ->(o1.m - o2.m));//把queries按照mi从小到大排序
        int[] ans = new int[queries.length];
        int pos = 0;
        for(Pair q : pair) {
            //所有<=m的数字,可以加入当前字典树中
            while(pos < n && nums[pos] <= q.m) {
                insert(nums[pos]);
                pos++;
            }
            //写答案
            if(idx == 0) { // 树是空的
                ans[q.index] = -1;
            }else {
                int t = query(q.x); //返回此时trie树中的数字里,与a异或最大的数字
                ans[q.index] = q.x ^ t;
            }
        }
        return ans;
    }
    
    private int query(int a){
        int p = 0, res = 0;
        for(int i = 30; i >= 0; i--){
            int u = (a >> i) & 1;//最高位,次高位,...
            if(son[p][u ^ 1] != 0){// 为1表示当前这位异或存在     //如果可以走,u是1,就往0走。u是0,就往1走
                res = res * 2 + u ^ 1;
                p = son[p][u ^ 1];
            }else{// 为0表示当前这位异或不存在,那就只能勉强的选择与当前位一样的。也就是异或为0的
                res = res *2 + u;
                p = son[p][u];
            }
        }
        return res;
    }
    //在trie树中插入数字a
    private void insert(int a){
        int p = 0;
        for(int i = 30; i >= 0; i--){
            int u = (a >> i) & 1;
            if(son[p][u] == 0){ // son[p][u]表示当前状态还没有发生过,我们需要标记下,然后改变idx从而添加新的状态
                idx++;
                son[p][u] = idx;
            }
            p = son[p][u];
        }
    }
}
class Pair{
    int x;
    int m;
    int index;
    public Pair(int x, int m, int index) {
        this.x = x;
        this.m = m;
        this.index = index;
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值