5.11华为笔试复盘

导读

昨天参加了华为的上机笔试,整体感觉有点难,第一题直接整道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 为需要维护前缀和的序列的长度。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值