前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >华为面试题,我的回忆杀

华为面试题,我的回忆杀

作者头像
宫水三叶的刷题日记
发布2023-12-26 16:17:40
2170
发布2023-12-26 16:17:40
举报
文章被收录于专栏:宫水三叶的刷题日记

写在前面

今天节日,为了上来和大家道一声冬至快乐,专门加更一篇。

正当我愁写哪个公司的原题时,发现「华为」题单中的 top5 有一道十分眼熟的题。

我依稀记得,当时我已经坚持日更 LeetCode 每日一题大概

100

多天?

但常看的留言读者还是只有那么几位。

直到这一题成为了每日一题。

也许是题解的严谨性碰巧做到了独一档,所以收获了大家的认可。

点赞和留言达到了新高,同时也被官方标为精选:

从那以后,熟悉的面孔越来越多,跟随刷穿 LeetCode 的大队也越发壮大。

按道理,这样的时刻,这样的题目,我应该记得很清楚才对,但事实没有。

人就是这样的,在信息过载年代,能被记住的东西可能也只是暂时。

包括现在,我连当时连续日更的具体天数,这个记录都忘了。

只记得好像是一个

6

字头的三位数?两年多?真的忘记了。

但,陪大家每天快乐刷题的感觉不会忘

我想过去几年,做过最正确的决定。

就是做了这个公众号和一些社群。

让我们在任意时刻找到彼此。

哪怕我没有登陆 LeetCode,你还是可以在后台留言找到我,我也还是可以发推文来联系大家。

不失联,就一切都好。

冬至快乐鸭,我重要的各位读者大人 🥟

题目描述

给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。

注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。

示例 1:

代码语言:javascript
复制
输入:nums = [10,2]

输出:"210"

示例 2:

代码语言:javascript
复制
输入:nums = [3,30,34,5,9]

输出:"9534330"

提示:

1 <= nums.length <= 100
0 <= nums[i] <= 10^9

贪心

对于

nums

中的任意两个值

a

b

,我们无法直接从常规角度上确定其大小/先后关系。

但我们可以根据「结果」来决定

a

b

的排序关系:

如果拼接结果

ab

要比

ba

好,那么我们会认为

a

应该放在

b

前面。

另外,注意我们需要处理前导零(最多保留一位)。

Java 代码:

代码语言:javascript
复制
class Solution {
    public String largestNumber(int[] nums) {
        int n = nums.length;
        String[] ss = new String[n];
        for (int i = 0; i < n; i++) ss[i] = "" + nums[i];
        Arrays.sort(ss, (a, b) -> {
            String sa = a + b, sb = b + a ;
            return sb.compareTo(sa);
        });
        StringBuilder sb = new StringBuilder();
        for (String s : ss) sb.append(s);
        int len = sb.length();
        int k = 0;
        while (k < len - 1 && sb.charAt(k) == '0') k++;
        return sb.substring(k);
    }
}

C++ 代码:

代码语言:javascript
复制
class Solution {
public:
    string largestNumber(vector<int>& nums) {
        int n = nums.size();
        vector<string> ss(n);
        for (int i = 0; i < n; i++) ss[i] = to_string(nums[i]);
        sort(ss.begin(), ss.end(), [](const string& a, const string& b) {
            return a + b > b + a;
        });
        string result;
        for (const string& s : ss) result += s;
        int len = result.length(), k = 0;
        while (k < len - 1 && result[k] == '0') k++;
        return result.substr(k);
    }
};

Python 代码:

代码语言:javascript
复制
class Solution:
    def largestNumber(self, nums: List[int]) -> str:
        nums = list(map(str, nums))
        nums.sort(key=lambda x: x * 10, reverse=True)
        result = ''.join(nums)
        k = 0
        while k < len(result) - 1 and result[k] == '0': 
            k += 1
        return result[k:]

TypeScript 代码:

代码语言:javascript
复制
function largestNumber(nums: number[]): string {
    const ss = nums.map(num => num.toString());
    ss.sort((a, b) => (b + a).localeCompare(a + b));
    let result = '';
    for (const s of ss) result += s;
    let k = 0;
    while (k < result.length - 1 && result[k] === '0') k++;
    return result.substring(k);
};
  • 时间复杂度:由于是对
String

进行排序,当排序对象不是

Java

中的基本数据类型时,不会使用快排(考虑排序稳定性问题)。Java 中的 Arrays.sort 的底层实现会「元素数量/元素是否大致有序」决定是使用插入排序还是归并排序。这里直接假定使用的是「插入排序」。复杂度为

O(n^2)
  • 空间复杂度:
O(n)

证明

上述解法,我们需要证明两个内容:

  • 该贪心策略能取到全局最优解。
  • 这样的「排序比较逻辑」应用在集合
nums

上具有「全序关系」。

1. 该贪心策略能取到全局最优解

令我们经过这样的贪心操作得到的贪心解为

ans

,真实最优解为

max

由于真实最优解为全局最大值,而我们的贪心解至少是一个合法解(一个数),因此天然有

ans \leqslant max

接下来我们只需要证明

ans \geqslant max

,即可得

ans = max

(贪心解即为最优解)。

我们使用「反证法」来证明

ans \geqslant max

成立:

假设

ans \geqslant max

不成立,即有

ans < max

ans

max

都是由同样一批数字凑成的,如果有

ans < max

这意味着我们可以将

ans

中的某些低位数字和高位数字互换,使得

ans

更大(调整为

max

),这与我们根据「结果」进行排序的逻辑冲突。

因此

ans < max

必然不成立,得证

ans \geqslant max

成立,结合

ans \leqslant max

可得贪心解为最优。

举个🌰,如果有

ans < max

,那么意味着在

ans

中至少有一对数字互换可以使得

ans

变大,

那么在排序逻辑中

x

所在的整体(可能不只有

x

一个数)应该被排在

y

所在的整体(可能不只有

y

一个数)前面。

2. 全序关系

我们使用符号

@

来代指我们的「排序」逻辑:

  • 如果
a

必须排在

b

的前面,我们记作

a @ b

  • 如果
a

必须排在

b

的后面,我们记作

b @ a

  • 如果
a

既可以排在

b

的前面,也可以排在

b

的后面,我们记作

a\#b

2.1 完全性

具有完全性是指从集合

nums

中任意取出两个元素

a

b

,必然满足

a @ b

b @ a

a\#b

三者之一。

这点其实不需要额外证明,因为由

a

b

拼接的字符串

ab

ba

所在「字典序大小关系中」要么完全相等,要么具有明确的字典序大小关系,导致

a

必须排在前面或者后面。

2.2 反对称性

具有反对称性是指由

a@b

b@a

能够推导出

a\#b

a@b

说明字符串

ab

的字典序大小数值要比字符串

ba

字典序大小数值大。

b@a

说明字符串

ab

的字典序大小数值要比字符串

ba

字典序大小数值小。

这样,基于「字典序本身满足全序关系」和「数学上的

a \geqslant b

a \leqslant b

可推导出

a = b

」。

得证

a@b

b@a

能够推导出

a\#b

2.3 传递性

具有传递性是指由

a@b

b@c

能够推导出

a@c

我们可以利用「两个等长的拼接字符串,字典序大小关系与数值大小关系一致」这一性质来证明,因为字符串

ac

ca

必然是等长的。

接下来,让我们从「自定义排序逻辑」出发,换个思路来证明

a@c

然后我们只需要证明在不同的

i
j

关系之间(共三种情况),

a@c

恒成立即可:

i == j

的时候:

i > j

的时候:

i < j

的时候:

综上,我们证明了无论在何种情况下,只要有

a@b

b@c

的话,那么

a@c

恒成立。

我们之所以能这样证明「传递性」,本质是利用了自定义排序逻辑中「对于确定任意元素

a

b

之间的排序关系只依赖于

a

b

的第一个不同元素之间的大小关系」这一性质。

i

的越界问题

考虑

(1) a = 304, b = 30

(2) a = 301, b = 30

两种情况。

显然,(1) 下我们会得到

a@b

,而 (2) 下我们会得到

b@a

但是,在这种情况下

i

实际上位于

b

界外,那我们还能不能找

i

呢?

b[i]

是多少呢?

实际上是可以的。

我们在比较

a

b

的时候,实际上是在比较

ab

ba

两个字符串,所以实际上我们是在用

a[0]

,

a[1]

,

a[2]

... 去填补

b

本体结束后的空缺。

换而言之 (1) 和 (2) 里的 b 实际上被填补为 303 (填进来

a[0]

再比如 (3) a = 3131248, b = 3131

比较的时候实际上是用

a

开头的 4 位去填补上

b

的空缺,所以

b

实际上相当于 31313131

最后

在外工作的同学,除了记得吃 🥟,记得给家里打个电话

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-12-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 宫水三叶的刷题日记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 写在前面
  • 题目描述
  • 贪心
  • 证明
    • 1. 该贪心策略能取到全局最优解
      • 2. 全序关系
      • 最后
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档