leetcode 1649 通过指令创建有序数组
题目链接
难度:困难
描述
给你一个整数数组 instructions ,你需要根据 instructions 中的元素创建一个有序数组。一开始你有一个空的数组 nums ,你需要 从左到右 遍历 instructions 中的元素,将它们依次插入 nums 数组中。每一次插入操作的 代价 是以下两者的 较小值 :
- nums 中 严格小于 instructions[i] 的数字数目。
- nums 中 严格大于 instructions[i] 的数字数目。
示例 1:
输入:instructions = [1,5,6,2]
输出:1
解释:一开始 nums = [] 。
插入 1 ,代价为 min(0, 0) = 0 ,现在 nums = [1] 。
插入 5 ,代价为 min(1, 0) = 0 ,现在 nums = [1,5] 。
插入 6 ,代价为 min(2, 0) = 0 ,现在 nums = [1,5,6] 。
插入 2 ,代价为 min(1, 2) = 1 ,现在 nums = [1,2,5,6] 。
总代价为 0 + 0 + 0 + 1 = 1 。
示例 2:
输入:instructions = [1,2,3,6,5,4]
输出:3
解释:一开始 nums = [] 。
插入 1 ,代价为 min(0, 0) = 0 ,现在 nums = [1] 。
插入 2 ,代价为 min(1, 0) = 0 ,现在 nums = [1,2] 。
插入 3 ,代价为 min(2, 0) = 0 ,现在 nums = [1,2,3] 。
插入 6 ,代价为 min(3, 0) = 0 ,现在 nums = [1,2,3,6] 。
插入 5 ,代价为 min(3, 1) = 1 ,现在 nums = [1,2,3,5,6] 。
插入 4 ,代价为 min(3, 2) = 2 ,现在 nums = [1,2,3,4,5,6] 。
总代价为 0 + 0 + 0 + 0 + 1 + 2 = 3 。
示例 3:
输入:instructions = [1,3,3,3,2,4,2,1,2]
输出:4
解释:一开始 nums = [] 。
插入 1 ,代价为 min(0, 0) = 0 ,现在 nums = [1] 。
插入 3 ,代价为 min(1, 0) = 0 ,现在 nums = [1,3] 。
插入 3 ,代价为 min(1, 0) = 0 ,现在 nums = [1,3,3] 。
插入 3 ,代价为 min(1, 0) = 0 ,现在 nums = [1,3,3,3] 。
插入 2 ,代价为 min(1, 3) = 1 ,现在 nums = [1,2,3,3,3] 。
插入 4 ,代价为 min(5, 0) = 0 ,现在 nums = [1,2,3,3,3,4] 。
插入 2 ,代价为 min(1, 4) = 1 ,现在 nums = [1,2,2,3,3,3,4] 。
插入 1 ,代价为 min(0, 6) = 0 ,现在 nums = [1,1,2,2,3,3,3,4] 。
插入 2 ,代价为 min(2, 4) = 2 ,现在 nums = [1,1,2,2,2,3,3,3,4] 。
总代价为 0 + 0 + 0 + 0 + 1 + 0 + 1 + 0 + 2 = 4 。
解题
方法1 - 归并排序
熟悉的
归并排序
,可以看到,我前几个博客里几乎都有归并排序
的身影,可见,它有多么重要。
那么这道题这么会使用归并排序
呢?在上一道题(315.计算右侧小于当前元素的个数)中,我们讨论了求右侧小于当前元素的个数。如果我们能求出每一个数左侧小于当前元素的个数,这道题不就迎刃而解了吗?
但是,很难受的是,归并排序每一层都是从左向右排(递归总还是有顺序的)。
所以,我们需要换一个思路,既然找小于当前元素的个数,那么我们其实有两种解决方案(可能还有,但我想不出来)
- 翻转原数组
- 倒序排列
翻转原数组
前者很容易理解,在315.计算右侧小于当前元素的个数中是计算右侧,这里需要计算左侧,翻转之后用相同的代码,再从后向前,就是了。
这里直接上代码,求右侧小于当前元素个数的代码,可以看315.计算右侧小于当前元素的个数中的讲述理解
class Solution {
public:
void mergeSort(vector<int> & nums, vector<int> & smallCount, int * indexes, int start, int end, int * tmp) {
if (start + 1 == end) {
tmp[start] = start;
return;
}
int mid = (start + end) / 2;
mergeSort(nums, smallCount, indexes, start, mid, tmp);
mergeSort(nums, smallCount, indexes, mid, end, tmp);
int li = start, le = mid, ri = mid, re = end, i = start, ie = end;
// 优化
// 当 nums[tmp[le - 1]]已经小于等于nums[tmp[ri]]时
// 左边和右边已经是排好序的状态,就不需要再排了
if (nums[tmp[le - 1]] <= nums[tmp[ri]]) {
return;
}
while (li < le && ri < re) {
if (nums[tmp[li]] <= nums[tmp[ri]]) {
smallCount[tmp[li]] += ri - mid;
indexes[i++] = tmp[li++];
} else {
indexes[i++] = tmp[ri++];
}
}
while (ri < re) indexes[i++] = tmp[ri++];
while (li < le) smallCount[tmp[li]] += ri - mid, indexes[i++] = tmp[li++];
for (i = start; i < ie; i++) {
tmp[i] = indexes[i];
}
// printf("\n\tto "); printVec(tmp, start, end); putchar('\n');
}
// 这里不需要返回值
void mergeSort(vector<int> & nums, vector<int> & smallCount) {
int * tmp = (int *)malloc(sizeof(int) * nums.size());
int * indexes = (int *)malloc(sizeof(int) * nums.size());
mergeSort(nums, smallCount, indexes, 0, nums.size(), tmp);
free(tmp);
free(indexes);
// for (int i = 0; i < nums.size(); i++)
// indexes[i] = nums[indexes[i]];
// return indexes;
}
int createSortedArray(vector<int>& instructions) {
int is = instructions.size();
// 由于是需要找左侧小于自己的数,所以把instructions翻转之后
// 从后向前一次查找
reverse(instructions.begin(), instructions.end());
vector<int> rightSmall(is);
mergeSort(instructions, rightSmall);
// printVec(instructions);
// printVec(rightSmall); putchar('\n');
const int MODMAX = 1e9+7;
long long cost = 0;
// 需要用一个map来存当前数字出现了多少次
// 在计算比本身大的数时需要用总数减去比自己小的数的个数,以及自己出现的个数
map<int, int> mp;
int ii;
for (int i = is - 1; i >= 0; i--) {
ii = instructions[i];
cost = (cost + min(rightSmall[i], is - i - rightSmall[i] - ++mp[ii]));
}
return (int)(cost % MODMAX);
}
};
虽然可以有优化,比倒序排列快一点点,但还是慢。
之所以讲倒序排序的方法只是为了提供新的思路
倒序排列
其实也很好理解,只需要改一小点点的代码
分析与315.计算右侧小于当前元素的个数中归并排序的分析类似,或者说是一模一样。所以说,咱就懒得写了吧
class Solution {
public:
void printVec(vector<int> & v) {
printf("[");
int i;
for (i = 0; i < v.size() - 1; i++) {
printf("%d ", v[i]);
}
printf("%d]", v[i]);
}
void printVec(int * v, int left, int right) {
putchar('[');
while (left + 1 < right) {
printf("%d ", v[left++]);
}
printf("%d]", v[left]);
}
void mergeSort(vector<int> & nums, vector<int> & leftCount, int * indexes, int start, int end, int * tmp) {
if (start + 1 == end) {
tmp[start] = start;
return;
}
int mid = (start + end) / 2;
mergeSort(nums, leftCount, indexes, start, mid, tmp);
mergeSort(nums, leftCount, indexes, mid, end, tmp);
// printf("combine left right:");
// printVec(tmp, start, mid); printVec(tmp, mid, end); putchar('\n');
int li = start, le = mid, ri = mid, re = end, i = start, ie = end;
while (li < le && ri < re) {
if (nums[tmp[li]] >= nums[tmp[ri]]) {
indexes[i++] = tmp[li++];
} else {
leftCount[tmp[ri]] += le - li;
indexes[i++] = tmp[ri++];
}
}
while (ri < re) leftCount[tmp[ri]] += le - li, indexes[i++] = tmp[ri++];
while (li < le) indexes[i++] = tmp[li++];
for (i = start; i < ie; i++) {
tmp[i] = indexes[i];
}
// printf("\tto "); printVec(tmp, start, end); putchar('\n');
// printf("\tupdate smallCount to "); printVec(leftCount); putchar('\n');
}
// 这里不需要返回值
void mergeSort(vector<int> & nums, vector<int> & smallCount) {
int * tmp = (int *)malloc(sizeof(int) * nums.size());
int * indexes = (int *)malloc(sizeof(int) * nums.size());
mergeSort(nums, smallCount, indexes, 0, nums.size(), tmp);
free(tmp);
free(indexes);
// for (int i = 0; i < nums.size(); i++)
// indexes[i] = nums[indexes[i]];
// return indexes;
}
int createSortedArray(vector<int>& instructions) {
int is = instructions.size();
vector<int> leftSmall(is);
mergeSort(instructions, leftSmall);
// printVec(leftSmall);
const int MODMAX = 1e9+7;
long long cost = 0;
// 需要用一个map来存当前数字出现了多少次
// 在计算比本身大的数时需要用总数减去比自己小的数的个数,以及自己出现的个数
map<int, int> mp;
int ii;
for (int i = 0; i < is; i++) {
ii = instructions[i];
// printf("cost add min(%d, %d), mp[ii] = %d\n", leftSmall[i], i - leftSmall[i] - mp[ii], mp[ii]);
cost = (cost + min(leftSmall[i], i + 1 - leftSmall[i] - ++mp[ii])) % MODMAX;
}
return (int)(cost);
}
};
方法2 - 树状数组
哎,我不想讲……各位自行查阅网上资料吧 T^T
const int MAX_I = 1e5;
const int MAX_R = 1e9 + 7;
// 利用树状数组解决
class BIT {
// Binary Index Tree
public:
int nums[MAX_I + 1] = {0};
int lowbit(int i) {
return i & -i;
}
void update(int i, int k) {
while (i < MAX_I) {
nums[i] += k;
i += lowbit(i);
}
}
int getsum(int i) {
int ans = 0;
while (i) {
ans += nums[i];
i -= lowbit(i);
}
return ans;
}
};
class Solution {
public:
int min(int a, int b) {
return a < b ? a : b;
}
int createSortedArray(vector<int>& instructions) {
int cost = 0;
BIT bit;
for (int i = 0; i < instructions.size(); i++) {
int ni = instructions[i];
bit.update(ni, 1);
int left = bit.getsum(ni - 1);
int right = i + 1 - bit.getsum(ni);
cost = (min(left, right) + cost) % MAX_R;
}
return cost;
}
};