题目描述(中等难度)

179. Largest Number - 图1

给一个数组,将这些数任意排列,组成一个值最大的数。

解法一

直觉上,我们应该先选最高位大的数。有 9 开头的数字先选 9,然后选 8 开头的数,再选 7 开头的… 以此类推。所以我们需要一个函数来判断最高位是多少。

  1. private int getHighestPosition(int num) {
  2. while (num / 10 > 0) {
  3. num /= 10;
  4. }
  5. return num;
  6. }

举个例子,比如对于 5,914,67,先选 914,再选 67,再选 5,组成 914675,因为数字的高位越大越好,每次尽量选高位的大的数从而保证了最后构成的数一定是最大的。

接下来的一个问题,如果最高位的数字一样呢?又分成两种情况。

如果两个数字长度相等,比如 3430 , 那么很明显,选择较大的即可。

如果两个数字长度不相等,比如 330,此时先选择 3 还是先选择 30 呢?

我们只需要把它两拼接在一起直接去比较,也就是比较 330303,很明显是 330 大,所以我们先选择 3

所以我们可以封装一个比较函数。

  1. private int compare(int n1, int n2) {
  2. int len1 = (n1 + "").length();
  3. int len2 = (n2 + "").length();
  4. //长度相等的情况
  5. if (len1 == len2) {
  6. if (n1 > n2) {
  7. return 1;
  8. } else if (n1 < n2) {
  9. return -1;
  10. } else {
  11. return 0;
  12. }
  13. }
  14. //长度不等的情况
  15. int combination1 = (int) (n1 * Math.pow(10, len2)) + n2;
  16. int combination2 =(int) (n2 * Math.pow(10, len1)) + n1;
  17. if (combination1 > combination2) {
  18. return 1;
  19. } else if (combination1 < combination2) {
  20. return -1;
  21. } else {
  22. return 0;
  23. }
  24. }

通过上边的分析,我们可以利用 HashMap 去存每一组数字,key 就是 9,8,7...0 分别代表开头数字是多少。而 value 就去存链表,每个链表的数字根据上边分析的规则将它们「从大到小」排列即可。

所以我们还需要一个插入元素到链表的方法。对于链表,我们的头结点不去存储值,而 head.next 才是我们第一个存储的值。

  1. //将 node 通过插入排序的思想,找到第一个比他小的节点然后插入到它的前边
  2. private void insert(MyNode head, MyNode node) {
  3. while (head != null && head.next != null) {
  4. int cur = head.next.val;
  5. int insert = node.val;
  6. if (compare(cur, insert) == -1) {
  7. node.next = head.next;
  8. head.next = node;
  9. return;
  10. }
  11. head = head.next;
  12. }
  13. head.next = node;
  14. }

然后对于 3,30,34,5,9,我们就会有下边的结构。

  1. 9: head -> 9
  2. 8:
  3. 7:
  4. 6:
  5. 5: head -> 5
  6. 4:
  7. 3: head -> 34 -> 3 -> 30
  8. 2:
  9. 1:
  10. 0:

然后我们只需要依次遍历这些数字组成一个字符串即可,即9534330

然后把上边所有的代码合起来即可。

  1. class MyNode {
  2. int val;
  3. MyNode next;
  4. MyNode(int val) {
  5. this.val = val;
  6. }
  7. }
  8. public String largestNumber(int[] nums) {
  9. HashMap<Integer, MyNode> map = new HashMap<>();
  10. for (int i = 9; i >= 0; i--) {
  11. map.put(i, new MyNode(-1));
  12. }
  13. //依次插入每一个数
  14. for (int i = 0; i < nums.length; i++) {
  15. int key = getHighestPosition(nums[i]);
  16. //得到头指针
  17. MyNode head = map.get(key);
  18. MyNode MyNode = new MyNode(nums[i]);
  19. //插入到当前链表的相应位置
  20. insert(head, MyNode);
  21. }
  22. //遍历所有值
  23. StringBuilder sb = new StringBuilder();
  24. for (int i = 9; i >= 0; i--) {
  25. MyNode head = map.get(i).next;
  26. while (head != null) {
  27. sb.append(head.val);
  28. head = head.next;
  29. }
  30. }
  31. String res = sb.toString();
  32. //考虑 "000" 只有 0 的特殊情况
  33. return res.charAt(0) == '0' ? "0" : res;
  34. }
  35. private void insert(MyNode head, MyNode node) {
  36. while (head != null && head.next != null) {
  37. int cur = head.next.val;
  38. int insert = node.val;
  39. if (compare(cur, insert) == -1) {
  40. node.next = head.next;
  41. head.next = node;
  42. return;
  43. }
  44. head = head.next;
  45. }
  46. head.next = node;
  47. }
  48. private int compare(int n1, int n2) {
  49. int len1 = (n1 + "").length();
  50. int len2 = (n2 + "").length();
  51. if (len1 == len2) {
  52. if (n1 > n2) {
  53. return 1;
  54. } else if (n1 < n2) {
  55. return -1;
  56. } else {
  57. return 0;
  58. }
  59. }
  60. int combination1 = (int) (n1 * Math.pow(10, len2)) + n2;
  61. int combination2 = (int) (n2 * Math.pow(10, len1)) + n1;
  62. if (combination1 > combination2) {
  63. return 1;
  64. } else if (combination1 < combination2) {
  65. return -1;
  66. } else {
  67. return 0;
  68. }
  69. }
  70. private int getHighestPosition(int num) {
  71. while (num / 10 > 0) {
  72. num /= 10;
  73. }
  74. return num;
  75. }

解法二

仔细想一下上边的想法,我们通过每个数字的最高位人为的把所有数字分成 10 类,然后每一类做了一个插入排序。其实我们也可以不进行分类,直接对所有数字进行排序。

我们直接调用系统的排序方法,传一个我们自定义的比较器即可。看一下我们之前的比较函数是否可以用。

  1. private int compare(int n1, int n2) {
  2. int len1 = (n1 + "").length();
  3. int len2 = (n2 + "").length();
  4. if (len1 == len2) {
  5. if (n1 > n2) {
  6. return 1;
  7. } else if (n1 < n2) {
  8. return -1;
  9. } else {
  10. return 0;
  11. }
  12. }
  13. int combination1 = (int) (n1 * Math.pow(10, len2)) + n2;
  14. int combination2 = (int) (n2 * Math.pow(10, len1)) + n1;
  15. if (combination1 > combination2) {
  16. return 1;
  17. } else if (combination1 < combination2) {
  18. return -1;
  19. } else {
  20. return 0;
  21. }
  22. }

之前我们只考虑了最高位相等的情况,如果最高位不同的话检查一下上边的代码是否还可以用。比如对于 93,234,然后根据上边代码我们会比较 9323423493,然后我们会选择 93,发现代码不需要修改。

此外,因为我们要从大到小排列,所以前一个数字大于后一个数字的时候,我们应该返回 -1

  1. public String largestNumber(int[] nums) {
  2. //自带的比较器不能使用 int 类型,所以我们把它转为 Integer 类型
  3. Integer[] n = new Integer[nums.length];
  4. for (int i = 0; i < nums.length; i++) {
  5. n[i] = nums[i];
  6. }
  7. Arrays.sort(n, new Comparator<Integer>() {
  8. @Override
  9. public int compare(Integer n1, Integer n2) {
  10. int len1 = (n1 + "").length();
  11. int len2 = (n2 + "").length();
  12. if (len1 == len2) {
  13. if (n1 > n2) {
  14. return -1;
  15. } else if (n1 < n2) {
  16. return 1;
  17. } else {
  18. return 0;
  19. }
  20. }
  21. int combination1 = (int) (n1 * Math.pow(10, len2)) + n2;
  22. int combination2 = (int) (n2 * Math.pow(10, len1)) + n1;
  23. if (combination1 > combination2) {
  24. return -1;
  25. } else if (combination1 < combination2) {
  26. return 1;
  27. } else {
  28. return 0;
  29. }
  30. }
  31. });
  32. StringBuilder sb = new StringBuilder();
  33. for (int i = 0; i < nums.length; i++) {
  34. sb.append(n[i]);
  35. }
  36. String res = sb.toString();
  37. return res.charAt(0) == '0' ? "0" : res;
  38. }

分析一下时间复杂度,首先取决于我们使用的排序算法,如果是解法一的插入排序,那么就是 O(n²),如果是快速排序,那么就是 O(nlog(n),此外我们的比较函数因为要求出每个数字的长度,我们需要遍历一遍数字,记做 O(k),所以总的时间复杂度对于快排的话就是 O(nklon(n))

解法三

上边的解法严格来说其实还是有些问题的。在比较两个数字大小的时候,当长度不相等的时候,我们把两个数字合并起来。如果数字特别大,强行把它们合并起来是会溢出的。

所以我们可以把数字转为 String ,把字符串合并起来,然后对字符串进行比较。

此外,在比较函数中我们单独分别判断了数字长度相等和不相等的情况,其实长度相等的情况也是可以合并到长度不相等的情况中去的。

  1. public String largestNumber(int[] nums) {
  2. Integer[] n = new Integer[nums.length];
  3. for (int i = 0; i < nums.length; i++) {
  4. n[i] = nums[i];
  5. }
  6. Arrays.sort(n, new Comparator<Integer>() {
  7. @Override
  8. public int compare(Integer n1, Integer n2) {
  9. String s1 = n1 + "" + n2;
  10. String s2 = n2 + "" + n1;
  11. //compareTo 方法
  12. //如果参数是一个按字典顺序排列等于该字符串的字符串,则返回值为0;
  13. //如果参数是按字典顺序大于此字符串的字符串,则返回值小于0;
  14. //如果参数是按字典顺序小于此字符串的字符串,则返回值大于0。
  15. return s2.compareTo(s1);
  16. }
  17. });
  18. StringBuilder sb = new StringBuilder();
  19. for (int i = 0; i < nums.length; i++) {
  20. sb.append(n[i]);
  21. }
  22. String res = sb.toString();
  23. return res.charAt(0) == '0' ? "0" : res;
  24. }

只要找到了选取数字的原则,这道题也就转换成一道排序题了。

windliang wechat

添加好友一起进步~

如果觉得有帮助的话,可以点击 这里 给一个 star 哦 ^^

如果想系统的学习数据结构和算法,强烈推荐一个我之前学过的课程,可以点击 这里 查看详情