LeetCode第233场周赛

周赛地址:https://leetcode-cn.com/contest/weekly-contest-233/

第一题:最大升序子数组和

题目要求连续上升子数组的和最大,进行遍历,对比当前元素和前一个元素的大小关系。

  1. a [ i − 1 ] < a [ i ] a[i-1] < a[i] a[i1]<a[i]的时候,此时是递增的,累加a[i]到current中,用max一直维护current的最大值。
  2. a [ i − 1 ] ≥ a [ i ] a[i-1] ≥ a[i] a[i1]a[i]的时候,此时递增停止,那么让current=a[i],从而继续求和。
class Solution {
    public int maxAscendingSum(int[] nums) {
        int max = nums[0];
        int current = nums[0];
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] > nums[i - 1]) {
                current += nums[i];
                max = Math.max(max, current);
            } else {
                current = nums[i];
            }
        }
        return max;
    }
}

第二题:积压订单中的订单总数

首先把题目看懂,题目描述有点长,耐心看完,会发现题目思路并不难。
如果订单是buy,先检查sell的队列里,有没有可以合并的,合并条件是:sell的最低价格≤当前buy的价格。
如果订单是sell,先检查buy的队列里,有没有可以合并的,合并条件是:buy的最高价格≥当前sell的价格。
于是sell用小顶堆,buy用大顶堆,Java里就用优先队列即可,并告知优先队列的排序规则。
订单的数量最大是 1 0 9 10^{9} 109,所以不可能能把每个订单放进去,而是放一个对象进去,所以也就有了Order类,这里用length=2的数组更节省空间。
每次合并的时候,考虑count的比较和处理。

class Solution {
    public int getNumberOfBacklogOrders(int[][] orders) {
        PriorityQueue<Order> buyPriorityQueue = new PriorityQueue<>(Comparator.comparingInt(order -> -order.price));
        PriorityQueue<Order> sellPriorityQueue = new PriorityQueue<>(Comparator.comparingInt(order -> order.price));
        for (int[] order : orders) {
            if (order[2] == 0) {// buy
                while (!sellPriorityQueue.isEmpty()) {
                    Order sell = sellPriorityQueue.poll();
                    if (sell.price <= order[0]) {
                        if (sell.count > order[1]) {
                            sell.count -= order[1];
                            order[1] = 0;
                            sellPriorityQueue.offer(sell);
                            break;
                        } else {
                            order[1] -= sell.count;
                        }
                    } else {
                        sellPriorityQueue.offer(sell);
                        break;
                    }
                }
                if (order[1] > 0) {
                    buyPriorityQueue.offer(new Order(order[0], order[1]));
                }
            } else {// sell
                while (!buyPriorityQueue.isEmpty()) {
                    Order buy = buyPriorityQueue.poll();
                    if (buy.price >= order[0]) {
                        if (buy.count > order[1]) {
                            buy.count -= order[1];
                            order[1] = 0;
                            buyPriorityQueue.offer(buy);
                            break;
                        } else {
                            order[1] -= buy.count;
                        }
                    } else {
                        buyPriorityQueue.offer(buy);
                        break;
                    }
                }
                if (order[1] > 0) {
                    sellPriorityQueue.offer(new Order(order[0], order[1]));
                }
            }
        }
        long result = 0;
        while (!buyPriorityQueue.isEmpty()) {
            result += buyPriorityQueue.poll().count;
        }
        while (!sellPriorityQueue.isEmpty()) {
            result += sellPriorityQueue.poll().count;
        }
        return (int) (result % 1000000007);
    }

    static class Order {
        int price, count;

        public Order(int price, int count) {
            this.price = price;
            this.count = count;
        }
    }
}

第三题:有界数组中指定下标处的最大值

先理解题目描述的条件:

  1. 数组长度是n。
  2. nums[i]是正整数,最小为1。
  3. 相邻的数字差值只能是0、±1。
  4. 元素和不超过maxSum。
  5. 使nums[index]最大化。

观察题目给的例子,将数值想象成高度,那么,整个数组画出来就是一个小山的样子,index位置是小山最高点。两侧依次递减如果递减到1,不继续递减,保持1直到数组的两端,当然,也可以还没减到1就碰到数组的边界了。所以这里计算的时候,要注意边界的取值范围。
有了这个直观的视觉感受,就考虑求和判断了,求和就是等差数列前n项和公式: S = ( a 1 + a n ) × n 2 S=\frac{(a_{1}+a_{n}) \times n}{2} S=2(a1+an)×n,能用公式就用公式,可别一个一个加去,太浪费时间了。
这里再次强调一下爆int的问题,两个int相乘,结果是int,结果可能会爆int,此时就出错了,在用long接收int相乘的结果时,先把int强转成long再进行乘法运算,因为这个wa了好几次。
设nums[index]的值是x,x的最小值是maxSum/n,最大值是maxSum,因为要求nums[index]的最大值,相当于二分法求满足条件的右边界。

class Solution {
    public int maxValue(int n, int index, int maxSum) {
        int left = maxSum / n, right = maxSum;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            long sum = getSum(index, n, mid);
            if (sum > maxSum) {
                right = mid - 1;
            } else if (sum < maxSum) {
                left = mid + 1;
            } else {
                left = mid + 1;
            }
        }
        // 题目没有提及无解的情况,实际这个if是不会走的
        if (right == -1 || getSum(index, n, right) > maxSum) {
            return -1;
        }
        return right;
    }

    // 计算a[index]=value的时候,整个数组的和
    private long getSum(int index, int length, int value) {
        long sum = 0;
        // a0的意思是a[0]的值,an_1的意思是a[n-1]的值,先按照-1的递减规则,减到数组的边界
        long a0 = value - index, an_1 = value - (length - index - 1);
        // 判断边界值分类计算和
        if (a0 >= 1) {
            sum += (a0 + value) * (index + 1) / 2;
        } else {
            sum += (long) (1 + value) * value / 2 + (index - value + 1);
        }
        if (an_1 >= 1) {
            sum += (an_1 + value) * (length - index) / 2;
        } else {
            sum += (long) (1 + value) * value / 2 + (length - index - value);
        }
        return sum - value;// 在计算左右侧的和时,value会被计算两次,所以需要减去一次
    }
}

第四题:统计异或值在范围内的数对有多少

题目的标签是字典树。
先说下朴素做法,分别取出 i i i j j j,满足 0 ≤ i < j ≤ n u m s . l e n g t h 0≤i<j≤nums.length 0i<jnums.length。对于每个 ( i , j ) (i,j) (i,j)对,去求 n u m s [ i ] ⨁ n u m s [ j ] nums[i] \bigoplus nums[j] nums[i]nums[j]的值,如果在 [ l o w , h i g h ] [low, high] [low,high]范围内,计数器加一。
看下数据范围, 1 ≤ n u m s . l e n g t h ≤ 2 × 1 0 4 1≤nums.length≤2 \times 10^{4} 1nums.length2×104,朴素做法是 O ( n 2 ) O(n^{2}) O(n2)的复杂度,会超时。
那么,就需要找一种方案,加快统计速度。
考虑一个数 a = 3 , m a x = 5 a=3,max=5 a=3max=5,试判断另一个数 b b b是否满足 a ⨁ b ≤ 5 a \bigoplus b ≤ 5 ab5。将 3 3 3 5 5 5表示成二进制的形式(这里为了方便表述,只写出来低位的3位): 3 10 = 01 1 2 , 5 10 = 10 1 2 3_{10}=011_{2},5_{10}=101_{2} 310=0112510=1012。根据再十进制中比较数字大小的原则:两数字位数相同的时候,从高位开始比较,如果高位就能决定出来大小,那么是没有必要比较低位的,同理,在二进制中表示也是如此。
我们考虑 b b b的二进制形式:

  1. 假设 b b b的二进制表示最高位是 0 0 0,因为最高位 0 ⨁ 0 = 0 0 \bigoplus 0=0 00=0,无论 b b b的次高位和最低位是什么,一定满足 a ⨁ b ≤ m a x a \bigoplus b ≤ max abmax
  2. 假设 b b b的二进制表示最高位是 1 1 1,因为最高位 0 ⨁ 1 = 1 0 \bigoplus 1=1 01=1,结果的 1 1 1 m a x max max中最高位的 1 1 1是无法比较出大小的,此时还需要继续比较次高位,如果相等,继续比较更低位,直到确定大小关系或者两者相等为止。

这个的比较过程,就是在字典树上进行查询的一个过程。考虑上面的情况1,如果某一位已经足够确定大小关系了,那么低位取什么都无所谓了,也就不用计算了,直接取这个结点的某个属性值即可,这个属性值是:该结点下有几个叶子节点的统计量。
此外,还要介绍一个思路,我们令 q u e r y ( v a l u e , m a x ) query(value, max) query(value,max)表示在字典树上每个元素异或 v a l u e 的 结 果 a n s w e r value的结果answer valueanswer,满足 0 ≤ a n s w e r ≤ m a x 0≤answer≤max 0answermax的元素个数。题目求解的 a n s w e r ∈ [ l o w , h i g h ] answer∈[low, high] answer[low,high]的数量,我们可以求 q u e r y ( v a l u e , m a x ) query(value, max) query(value,max) q u e r y ( v a l u e , m i n − 1 ) query(value, min - 1) query(value,min1),最终结果就是 q u e r y ( v a l u e , m a x ) − q u e r y ( v a l u e , m i n − 1 ) query(value, max)-query(value, min - 1) query(value,max)query(value,min1)
整个代码流程分为两步:对于数组中的每个数字,在字典树上查询value,插入value。先查询后插入,可以保证查询value的时候,字典树上都是value之前的数字,保证了 i i i j j j的位置关系。
查询就相当于拿value和字典树上已经存储过的值按位进行异或运算。
如果某一位足以确定大小关系 v a l u e ⨁ k < m a x value \bigoplus k < max valuek<max,就可以累加 v a l u e value value在该二进制位处的叶子数量值,低位的就不用继续算了。
如果某一位足以确定大小关系 v a l u e ⨁ k > m a x value \bigoplus k > max valuek>max,就不用继续进行了,此时低位无论取什么值都无法满足题意,累加值为0。
如果存在 v a l u e ⨁ k = m a x value \bigoplus k = max valuek=max,这种情况的k也是符合条件的,也要算进去。

class Solution {
    public int countPairs(int[] nums, int low, int high) {
        Node root = new Node();
        int result = 0;
        for (int num : nums) {
            // 先查询,后插入
            result += root.query(root, num, high) - root.query(root, num, low - 1);
            root.insert(root, num);
        }
        return result;
    }
}

class Node {
    int count;
    Node[] child;

    public Node() {
        this.count = 0;
        this.child = new Node[]{null, null};
    }

    public void insert(Node root, int value) {
        // 假设树的根节点是第0层,只有一个根节点
        // 那么,树的第i层,有2^i个结点
        // 要想知道一个值是什么,需要从根结点遍历到叶子结点
        // 所以,树中的叶子结点数代表树中存储了多少个数字
        // 2^14 < 2*10^4 < 2^15
        // 所以,用一棵高度为15(从0开始计数)的字典树,可以存储下所有的数据
        // 我们规定,左孩子代表二进制是0的分支,右孩子代表二进制是1的分支
        root.count++;// 根节点的count++
        for (int i = 15; i >= 0; i--) {
            int bit = (value >> i) & 1;// 取出value二进制的第i位
            if (root.child[bit] == null) {// bit分支还是空的,就给它创建一个
                root.child[bit] = new Node();
            }
            root = root.child[bit];// 根据bit的值,移动到不同的分支上
            root.count++;// 分支结点的count++
        }
    }

    public int query(Node root, int value, int maxValue) {
        int result = 0, i;
        for (i = 15; i >= 0; i--) {
            int valuebit = (value >> i) & 1;
            int maxValuebit = (maxValue >> i) & 1;
            int answerbit;
            // 分别求左右子树对应的位和valuebit异或的结果,对比maxValuebit
            Node node = null;// 当出现异或结果相等的时候,临时存储这个相等的结点,用在低位的比较,0和1最多只有一边出现相等的情况
            // 遍历root的所有孩子,这里只有0和1两个孩子,也写成循环的形式,更有通用性,如果孩子是a-z,可以改成遍历[a, z]即可
            for (int j = 0; j < 2; j++) {
                if (root.child[j] != null) {
                    answerbit = j ^ valuebit;
                    if (answerbit < maxValuebit) {// 在当前位已经比较出大小,就不用继续比较低位了
                        result += root.child[j].count;
                    }
                    if (answerbit == maxValuebit) {// 在当前位无法比较出大小,继续比较低位,将child[j]临时存储下来
                        node = root.child[j];
                    }
                }
            }
            if (node != null) {// 出现了异或结果相等的情况,还需继续比较更低位
                root = node;
            } else {// 无需继续比较低位了
                break;
            }
        }
        if (i == -1) {// 最低位都比较过了,还没有比较出来maxVlue和value ^ a的大小关系,也就是value ^ a == maxValue的情况
            // 这里需要注意,nums里可能有相同的值,如果有两个a都满足value ^ a == maxValule了,这里就得加2,所以不能用result++;
            result += root.count;// 这里的root一定是叶结点了
        }
        return result;
    }
}

还有另一种写法,不需要考虑等于的情况,写起来更容易一些。需要改动的是query的参数和query的方法体。

public int countPairs(int[] nums, int low, int high) {
    Node root = new Node();
    int result = 0;
    for (int num : nums) {
        // 先查询,后插入
        result += root.query(root, num, high + 1) - root.query(root, num, low);
        root.insert(root, num);
    }
    return result;
}

public int query(Node root, int value, int maxValue) {
    int result = 0, i;
    for (i = 15; i >= 0; i--) {
        int valuebit = (value >> i) & 1;
        int maxValuebit = (maxValue >> i) & 1;
        if (maxValuebit == 1) {// maxValuebit == 1,要使得value ^ a的这一位是0,也就是value和a的这一位同1或同0
            if (root.child[valuebit] != null) {// 这里的valuebit == abit,按位异或 == 0,此时可以判断小于maxValue了
                result += root.child[valuebit].count;
            }
            // value ^ a的这一位是1的情况,也就是value和a的这一位不同
            if (root.child[1 ^ valuebit] != null) {
                root = root.child[1 ^ valuebit];// 转向这一侧继续判断
            } else {
                break;
            }
        } else {// maxValuebit == 0,如果value ^ a的这一位是1,不满足;如果value ^ a的这一位是0,有可能满足,这里就只需要考虑异或结果是0的情况,异或结果是0,那么value和a的这一位同1或同0
            if (root.child[valuebit] != null) {// 这里的valuebit == abit,按位异或 == 0,继续判断
                root = root.child[valuebit];
            } else {
                break;
            }
        }
    }
    return result;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值