面试题45. 把数组排成最小的数

一、题目

题目链接:力扣

输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

示例 1:

输入: [10,2]
输出: "102"

示例 2:

输入: [3,30,34,5,9]
输出: "3033459"

提示:

0 < nums.length <= 100

说明:

输出结果可能非常大,所以你需要返回一个字符串而不是整数
拼接起来的数字可能会有前导 0,最后结果不需要去掉前导 0


二、题解

1、思路

👩‍⚕️ 自定义归则 + 快速排序

思路描述:首先我们要明白就是

  • 无论这些数字怎么取排列,形成的数字的位数是不变的

那么就是高位的数字肯定是越小越好。

我们先考虑一下怎么排列两个数字,比如 1 和 20,高位越小越好,放 1,组合成 120

我们再看一下三个数的情况,比如 36、38 和 5,首先肯定先放 36,剩下 38 和 5,然后对这两个数进行排列 385,所以最后的结果为 36385。

由上面的两个例子我们其实就可以知道,放数字的顺序肯定是先放第一位(最左边一位)最小的元素,如果第一位相等,比较第二位....,以此类推。

关键是怎么进行排序呢?

设数组 nums 中任意两数字的字符串为 x 和 y ,则规定排序判断规则为:注意”+“代表两个数字组合成数字。

  • 若拼接字符串 x + y > y + x ,则 x “大于” y ;
  • 反之,若 x + y < y + x ,则 x “小于” y ;
  • 总结:哪个字符在前组成的数组大,哪个就大,比如,36、38、5,5>38>36

x “小于” y 代表:排序完成后,数组中 x 应在 y 左边;“大于” 则反之。

对于上面的36、38、5:

36 + 38 --> 3638 

38 + 36 --> 3836

36 + 38 < 38 + 36,因此38>36

38 + 5 --> 385

5 + 38 --> 538

38 + 5 < 5 + 38,因此5>38

以上说法仅针对两个数字,针对n个数字的数组结论是否正确呢?也就是是否具有传递性呢?

具有。不再证明,详见力扣

那么对于该题,我们只需要:

1.先将数字转化为字符串数组;

2.对字符串数组按照自定义的归则进行升序排序;

3.从小到大连接字符串作为结果。

2、代码实现

👩‍⚕️ 自定义归则 + 快速排序

快排:排序算法--快速排序--详解与代码示例_快速排序算法_阿尔兹的博客-CSDN博客

易错点:

  • 首先要将数组转化为字符串数组,也就是vector<string> str;
  • j > i,而不是 j > low,因为i,j都是在变的;
  • low是可能大于high的,而不是在low==high的时候停止排序;
  • 右侧找到一个最小的(左同),赋值到左侧空位置后,左侧下标记得加一。
class Solution {
public:
    string minNumber(vector<int>& nums) {
        int n = nums.size();// 题目中说n一定大于0
        vector<string> strs;// 将数组中的数字转化为string数组
        // 数字转化为string数组
        for(int i= 0; i < n; i++)
        {
            strs.emplace_back(to_string(nums[i]));
        }
        // 自定义归则快排,s升序
        quick_sort(strs, 0, n-1);
        // 结果重组
        // += 运算符:追加单个参数值。
        // append 函数:允许追加多个参数值。
        // push_back 函数:只能追加单个字符。
        string res;
        for(string &str : strs)res += str;

        return res;
    }

private:
    // https://blog.csdn.net/qq_41709234/article/details/128476788
    // 对输入数组strs进行快速排序
    // low: 需要排序的最低下标
    // high: 需要排序的最高下标
    // 每次排好一个元素的位置
    void quick_sort(vector<string>& strs, int low, int high)
    {
        if(low >= high)return;// low=high指向同一个数字,不需要排序
        int i = low, j = high;
        string pivot = strs[i];

        while(i < j)
        {
            // 找一个比pivot小的
            while(i<j && pivot+strs[j] < strs[j]+pivot)j--;
            
            if(i<j)strs[i++] = strs[j];// 覆盖pivot所在位置 i++;刚移动过来肯定比pivot小,而下面找大的

            // 找一个比pivot大的
            while(i<j && pivot+strs[i] > strs[i]+pivot)i++;
            
            // 放到上面j位置
            if(i<j)strs[j--] = strs[i];//j--;下次循环找比pivot小的
            // 注意上面的i++,这里的j--必须放在if(i<j)里面,否则i=j不需要排序的时候,仍然会被排序
        }
        // 为什么i=j的时候一定是可以被覆盖呢?
        // 看上方代码,while中i、j每次只能一个一移动、一个静止,静止的指向要被覆盖的内容,比如,第一次,首个元素可以被覆盖,i指向,是静止的,j移动。最终一个一移动、一个静止,所以这个内容一定可以被覆盖
        strs[i] = pivot;// i == j

        // 至此一个元素的位置已经完全找好,开始对pivot左侧、右侧进行排序
        quick_sort(strs, low, i - 1);
        quick_sort(strs, i + 1, high);
    }        
        
};

3、复杂度分析

👩‍⚕️ 自定义归则 + 快速排序

时间复杂度:O(nlogn);快速排序平均时间复杂度O(nlogn);

空间复杂度:O(n)。字符串占用空间。

4、运行结果

👩‍⚕️ 自定义归则 + 快速排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kashine

你的鼓励将是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值