导读
昨天参加了华为的上机笔试,整体感觉有点难,第一题直接整道hard,着实遭不住,下面就我的笔试情况,和大家谈论一下第一题。
题目
第一题,送祝福,题目大体意思就是一排人,每个人都有一张带有数字的卡片,大小不重复,如果你的比后面的人大,就要给后面的人送去祝福,最后返回祝福的数组blessing。
解题思路
大家一看,可以很快想出暴力解法,直接两层for循环,每次判断后面比你小的人有多少,记录count即可。但是毫无意外必定超时(我试了),接下来,我想可不可以利用dp,从后往前遍历,记录每个位置后面比你小的个数,但是元素是无须的无法找到比你小的第一个元素的下表。如果利用利用hashMap存储,还是要线性遍历,时间复杂度,完全不可以。
大家可以细心的发现,这就是个统计逆序对(前面大于后面)的数量,但是需要求对应位置的逆序对。逆序对,可以用归并排序,每次在合的时候做文章,此时判断左边与右边的大小,如果左边大于右边,此时统计逆序对,如果小于,正常归并即可。下面给出代码
public List<Integer> countSmaller(int[] nums) {
List<Integer> result = new ArrayList<>();
int len = nums.length;
if (len == 0) {
return result;
}
int[] temp = new int[len];
int[] res = new int[len];
// 索引数组,作用:归并回去的时候,方便知道是哪个下标的元素
int[] indexes = new int[len];
for (int i = 0; i < len; i++) {
indexes[i] = i;
}
mergeAndCountSmaller(nums, 0, len - 1, indexes, temp, res);
// 把 int[] 转换成为 List<Integer>,没有业务逻辑
for (int i = 0; i < len; i++) {
result.add(res[i]);
}
return result;
}
/**
* 针对数组 nums 指定的区间 [left, right] 进行归并排序,在排序的过程中完成统计任务
*/
private void mergeAndCountSmaller(int[] nums, int left, int right, int[] indexes, int[] temp, int[] res) {
if (left == right) {
return;
}
int mid = left + (right - left) / 2;
mergeAndCountSmaller(nums, left, mid, indexes, temp, res);
mergeAndCountSmaller(nums, mid + 1, right, indexes, temp, res);
// 归并排序的优化,如果索引数组有序,则不存在逆序关系,没有必要合并
if (nums[indexes[mid]] <= nums[indexes[mid + 1]]) {
return;
}
mergeOfTwoSortedArrAndCountSmaller(nums, left, mid, right, indexes, temp, res);
}
/**
* [left, mid] 是排好序的,[mid + 1, right] 是排好序的
*/
private void mergeOfTwoSortedArrAndCountSmaller(int[] nums, int left, int mid, int right, int[] indexes, int[] temp, int[] res) {
for (int i = left; i <= right; i++) {
temp[i] = indexes[i];
}
int i = left;
int j = mid + 1;
for (int k = left; k <= right; k++) {
if (i > mid) {
indexes[k] = temp[j];
j++;
} else if (j > right) {
indexes[k] = temp[i];
i++;
res[indexes[k]] += (right - mid);
} else if (nums[temp[i]] <= nums[temp[j]]) {
// 注意:这里是 <= ,保证稳定性
indexes[k] = temp[i];
i++;
res[indexes[k]] += (j - mid - 1);
} else {
indexes[k] = temp[j];
j++;
}
}
}
树状数组
下面还提供一种思路,就是树状数组(看了大佬的复盘),这里我只是简单的说下(因为不会用)。
-
预备知识
「树状数组」是一种可以动态维护序列前缀和的数据结构,它的功能是: -
单点更新 update(i, v): 把序列 i 位置的数加上一个值 v,在该题中 v = 1
-
区间查询 query(i): 查询序列[1⋯i] 区间的,即 i 位置的前缀和
修改和查询的时间代价都是 O(log n),其中 n 为需要维护前缀和的序列的长度。