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;
    }
};

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340