树状数组
leetcode每日一题遇上了一道线段树问题,没看过模板的我可以懵逼一早上了,不过只要看懂了树状数组的模板就可以很轻松解决这个问题,毕竟也确实只是个模板题,就看你有没有涉及过,如果自己硬生生憋出树状数组的结构也确实厉害。
树状数组学习:
https://oi-wiki.org/ds/fenwick/
1、结构性质
数组内,从索引 1 开始,每两个相邻索引值连接,然后索引值大的提到索引值小的上一层 ,假设是 C 层,然后再把在 C 层的索引值再两两相连,依然是索引值大的提到上一层,假设是 D 层,小的留在 C 层,然后再递归 两两相连 D 层 继续 创建 E F G H...... 这样的层。
很明显看到 1 3 5 7 9 这样奇数的索引值,他们都是叶子节点,因为 在 第一次 两两相连的时候,已经决定了 奇数 的 索引值 会被连接到 偶数的索引值后面。
而且 两两相连 的做法 也正是 2 ^ N 体现,只有 2 ^N 的索引值才会提升到 更高的 N 层。
2、查找
那这样搞了结构,都是有序的索引值的结构,那么数组内就可以假设存放每个节点的数量,查找方法,也就是查目标节点(索引值)以及小于该索引值下一共存放多少个节点数量,或者反过来找父节点,把子节点的节点数统计到父节点。
lowbit:
public int lowbit(int i) {
return i & -i;
}
这块是在统计过程中,我计算完了当前节点 i (索引值 i),然后我跳到哪个节点继续统计呢?就是跳到 i + lowbit(i) 或 i - lowbit(i) 继续统计。
因为根据性质可以看到数组索引值是两两相连而成,奇数都是孤儿,2 ^ N 都是人上人,那么二进制 2 ^ N 就代表 1 000 000... (N 个 0),多少个 0 就多少层,下面往上找就得 i + lowbit(i),上面往下找就得 i - lowbit(i)。
而 i 是正整数, - i 是一个补数,如果 i 是奇数 i & - i = 1 如果 i 是偶数 i & -i = 2 ^ N 是定理来的。
往上更新父节点数据
public void update(int i, int v) { // 索引 i 插入数量 v
while(i <= N) {
tree[i] += v;
i += lowbit(i); // 往上找父节点,每个节点都更新数量 v
}
}
往下统计
public int getSum(int i) { // 从索引 i 下面,统计所有 <= i 的索引存放数量
int ans = 0;
while(i > 0) {
ans += tree[i];
i -= lowbit(i); // 不断往 <= i 的索引找
}
return ans;
}
问题:Create Sorted Array through Instructions
难度:hard
说明:
给出一个无序数组,然后将数组进行插入排序,要求求出,数组内每个元素进行插入排序时候,所有元素需要最小共多少的比较次数(称为消耗),插入排序可以队前插入或者队后插入,每个元素只需要计算队前或队后插入的最小消耗就行(所以题目就说,多少个比它大,多少个比它小的最小值)。
题目连接:https://leetcode.com/problems/create-sorted-array-through-instructions/submissions/
输入范围:
1 <= instructions.length <= 105
1 <= instructions[i] <= 105
输入案例:
Example 1:
Input: instructions = [1,5,6,2]
Output: 1
Explanation: Begin with nums = [].
Insert 1 with cost min(0, 0) = 0, now nums = [1].
Insert 5 with cost min(1, 0) = 0, now nums = [1,5].
Insert 6 with cost min(2, 0) = 0, now nums = [1,5,6].
Insert 2 with cost min(1, 2) = 1, now nums = [1,2,5,6].
The total cost is 0 + 0 + 0 + 1 = 1.
Example 2:
Input: instructions = [1,2,3,6,5,4]
Output: 3
Explanation: Begin with nums = [].
Insert 1 with cost min(0, 0) = 0, now nums = [1].
Insert 2 with cost min(1, 0) = 0, now nums = [1,2].
Insert 3 with cost min(2, 0) = 0, now nums = [1,2,3].
Insert 6 with cost min(3, 0) = 0, now nums = [1,2,3,6].
Insert 5 with cost min(3, 1) = 1, now nums = [1,2,3,5,6].
Insert 4 with cost min(3, 2) = 2, now nums = [1,2,3,4,5,6].
The total cost is 0 + 0 + 0 + 0 + 1 + 2 = 3.
Example 3:
Input: instructions = [1,3,3,3,2,4,2,1,2]
Output: 4
Explanation: Begin with nums = [].
Insert 1 with cost min(0, 0) = 0, now nums = [1].
Insert 3 with cost min(1, 0) = 0, now nums = [1,3].
Insert 3 with cost min(1, 0) = 0, now nums = [1,3,3].
Insert 3 with cost min(1, 0) = 0, now nums = [1,3,3,3].
Insert 2 with cost min(1, 3) = 1, now nums = [1,2,3,3,3].
Insert 4 with cost min(5, 0) = 0, now nums = [1,2,3,3,3,4].
Insert 2 with cost min(1, 4) = 1, now nums = [1,2,2,3,3,3,4].
Insert 1 with cost min(0, 6) = 0, now nums = [1,1,2,2,3,3,3,4].
Insert 2 with cost min(2, 4) = 2, now nums = [1,1,2,2,2,3,3,3,4].
The total cost is 0 + 0 + 0 + 0 + 1 + 0 + 1 + 0 + 2 = 4.
我的代码:
开始用 BST 完全超时了。
没学过树状数组就是地狱难度,不过学了这个别人家的天才树,就一模板,拿元素值作为 key 对应 tree 的下标值。
java:
class Solution {
private static int N = 100001;
private static int[] tree = new int[N];
public int lowbit(int i) {
return i & -i;
}
public void update(int i, int v) {
while(i <= N) {
tree[i] += v;
i += lowbit(i);
}
}
public int getSum(int i) {
int ans = 0;
while(i > 0) {
ans += tree[i];
i -= lowbit(i);
}
return ans;
}
public int createSortedArray(int[] inst) {
long res = 0;
int len = inst.length;
Arrays.fill(tree, 0);
for(int i = 0;i < len;i ++) {
res += Math.min(getSum(inst[i] - 1), i - getSum(inst[i]));
update(inst[i], 1);
}
return (int)(res % ((int)Math.pow(10, 9) + 7));
}
}
C++:
class Solution {
public:
int N = 100001;
int *tree = new int[N];
int lowbit(int i) {
return i & -i;
}
void update(int i, int v) {
while (i < N) {
tree[i] += v;
i += lowbit(i);
}
}
int getSum(int i) {
int ans = 0;
while (i) {
ans += tree[i];
i -= lowbit(i);
}
return ans;
}
int createSortedArray(vector<int>& instr) {
memset(tree, 0, N);
long res = 0;
int n = instr.size();
for (int i = 0; i < n; ++i) {
res += min(getSum(instr[i] - 1), i - getSum(instr[i]));
update(instr[i], 1);
}
return (int)(res % 1000000007);
}
};
还有个超时的 BST 但是占用内存太大,而且比较统计次数过多。
class Solution {
private static long res;
private static int num, len, size;
public int createSortedArray(int[] inst) {
res = 0;
num = 1;
len = inst.length;
size = len + 1;
int[] val = new int[size], left = new int[size], right = new int[size]
,nleft = new int[size], nright = new int[size], n = new int[size];
val[1] = inst[0];
n[1] = 1;
for(int i = 1;i < len;i ++) {
int[] mamx = new int[2];
insert(inst[i], 1, val, left, right, nleft, nright, n, mamx);
res += Math.min(mamx[0], mamx[1]);
}
return (int)(res % ((int)Math.pow(10, 9) + 7));
}
public int insert(int v, int r, int[] val, int[] left, int[] right, int[] nleft, int[] nright, int[] n, int[] mamx) {
if(val[r] == 0) {
r = ++ num;
n[r] ++;
val[r] = v;
} else if(val[r] == v) {
n[r] ++;
mamx[0] += nleft[r];
mamx[1] += nright[r];
} else if(val[r] < v) {
right[r] = insert(v, right[r], val, left, right, nleft, nright, n, mamx);
nright[r] ++;
mamx[0] += n[r] + nleft[r];
} else if(val[r] > v) {
left[r] = insert(v, left[r], val, left, right, nleft, nright, n, mamx);
nleft[r] ++;
mamx[1] += n[r] + nright[r];
}
return r;
}
}