LeetCode 1649 通过指令创建有序数组

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;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值