周赛地址:https://leetcode-cn.com/contest/weekly-contest-233/
第一题:最大升序子数组和
题目要求连续上升子数组的和最大,进行遍历,对比当前元素和前一个元素的大小关系。
- 当 a [ i − 1 ] < a [ i ] a[i-1] < a[i] a[i−1]<a[i]的时候,此时是递增的,累加a[i]到current中,用max一直维护current的最大值。
- 当 a [ i − 1 ] ≥ a [ i ] a[i-1] ≥ a[i] a[i−1]≥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;
}
}
}
第三题:有界数组中指定下标处的最大值
先理解题目描述的条件:
- 数组长度是n。
- nums[i]是正整数,最小为1。
- 相邻的数字差值只能是0、±1。
- 元素和不超过maxSum。
- 使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
0≤i<j≤nums.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}
1≤nums.length≤2×104,朴素做法是
O
(
n
2
)
O(n^{2})
O(n2)的复杂度,会超时。
那么,就需要找一种方案,加快统计速度。
考虑一个数
a
=
3
,
m
a
x
=
5
a=3,max=5
a=3,max=5,试判断另一个数
b
b
b是否满足
a
⨁
b
≤
5
a \bigoplus b ≤ 5
a⨁b≤5。将
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=0112,510=1012。根据再十进制中比较数字大小的原则:两数字位数相同的时候,从高位开始比较,如果高位就能决定出来大小,那么是没有必要比较低位的,同理,在二进制中表示也是如此。
我们考虑
b
b
b的二进制形式:
- 假设 b b b的二进制表示最高位是 0 0 0,因为最高位 0 ⨁ 0 = 0 0 \bigoplus 0=0 0⨁0=0,无论 b b b的次高位和最低位是什么,一定满足 a ⨁ b ≤ m a x a \bigoplus b ≤ max a⨁b≤max。
- 假设 b b b的二进制表示最高位是 1 1 1,因为最高位 0 ⨁ 1 = 1 0 \bigoplus 1=1 0⨁1=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
value的结果answer,满足
0
≤
a
n
s
w
e
r
≤
m
a
x
0≤answer≤max
0≤answer≤max的元素个数。题目求解的
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,min−1),最终结果就是
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,min−1)。
整个代码流程分为两步:对于数组中的每个数字,在字典树上查询value,插入value。先查询后插入,可以保证查询value的时候,字典树上都是value之前的数字,保证了
i
i
i和
j
j
j的位置关系。
查询就相当于拿value和字典树上已经存储过的值按位进行异或运算。
如果某一位足以确定大小关系
v
a
l
u
e
⨁
k
<
m
a
x
value \bigoplus k < max
value⨁k<max,就可以累加
v
a
l
u
e
value
value在该二进制位处的叶子数量值,低位的就不用继续算了。
如果某一位足以确定大小关系
v
a
l
u
e
⨁
k
>
m
a
x
value \bigoplus k > max
value⨁k>max,就不用继续进行了,此时低位无论取什么值都无法满足题意,累加值为0。
如果存在
v
a
l
u
e
⨁
k
=
m
a
x
value \bigoplus k = max
value⨁k=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;
}