LeetCode747至少是其他数字两倍的最大数:回顾十大排序算法

前言

  • 大家好,我是新人简书博主:「 个人主页」主要分享程序员生活、编程技术、以及每日的LeetCode刷题记录,欢迎大家关注我,一起学习交流,谢谢!
    • 正在坚持每日更新LeetCode每日一题,发布的题解有些会参考其他大佬的思路(参考资料的链接会放在最下面),欢迎大家关注我 ~ ~ ~
    • 今天是坚持写题解的17天(haha,从21年圣诞节开始的),大家一起加油!
  • 每日一题:LeetCode:747.至少是其他数字两倍的最大数
    • 时间:2022-01-13
    • 力扣难度:Easy
    • 个人难度:Easy
    • 数据结构:数组
    • 算法:模拟、排序

2022-01-13:LeetCode:747.至少是其他数字两倍的最大数

1. 题目描述

  • 题目:原题链接

    • 给你一个整数数组 nums ,其中总是存在 唯一的 一个最大整
    • 请你找出数组中的最大元素并检查它是否 至少是数组中每个其他数字的两倍。
    • 如果是,则返回 最大元素的下标 ,否则返回 -1
  • 输入输出规范

    • 输入:整数数组nums
    • 输出:最大数的索引
  • 输入输出示例

    • 输入:[3,6,1,0]
    • 输出:1

2. 方法:简单模拟

  • 思路

    • 本题要求数组中的最大值是否比任意一个元素的两倍都要大,最直接的思路就是一次遍历找到最大值,再一次遍历比较是否比两倍还大
    • 实际上,只要最大数比次大数的两倍大,就一定比其他元素的两倍大,因此可以优化成一次遍历
    • 值得注意的是:本题输入的数组只有一个元素时,默认返回0即可,即满足最大数比其他的两倍大
  • 复杂度分析:n 是数组的长度

    • 时间复杂度:O(n)
    • 空间复杂度:O(1)
  • 题解:直接模拟

    public int dominantIndex(int[] nums) {
        if(nums == null || nums.length == 0) return -1;
        int n = nums.length;
        if(n == 1) return 0;
        int max = nums[0];
        int secondMax = Integer.MIN_VALUE;
        int index = 0;
        for(int i = 1; i < n; i++) {
            if(nums[i] >= max){
                secondMax = max;
                max = nums[i];
                index = i;
            }else if(nums[i] >= secondMax) secondMax = nums[i];
        }
        // System.out.print(1 >= 0 << 1);
        return max >= secondMax << 1 ? index : -1;
    }
    

3. 扩展:排序算法

  • 本题非常简单,因此我们可以扩展一下程序员基本技能:排序算法

    • 十大排序算法:冒泡、快排、插入、选择、归并排序堆排序、希尔排序、基数排序、计数排序桶排序
    • 排序的对象:数组、链表等
十大排序算法.png
  • 冒泡 & 选择 & 插入

    • 作为最简单的三种排序(都属于比较排序),这里简单讲一下三者的特点与区别,以升序为例
    • 冒泡排序:进行 n 轮,每轮逐个对比元素大小,将大的元素后移,即每一轮会将最大元素移到数组末尾,同时数组局部也发生排序
    • 选择排序:进行 n 轮,每轮通过比较,寻找最大的元素,记录其索引,将其与末尾元素交换,同样是每一轮会将最大元素移到数组末尾,但是数组局部不会发生改变
    • 插入排序:进行 n 轮,每轮取出未排序的一个元素(按顺序),然后比较其与前面以及排好序的元素,找到其要插入的位置并插入,不会取找到最大最小元素,数组局部不会发生改变
    • 此外,本文主要介绍最常见、使用最广泛的几种排序:快排、归并、堆排序
  • 快速排序:QuickSort:partition & sort

    • 思想:分治

      • 提前设置一个基准,在每轮排序时通过该基准将序列分成两部分,大于基准的和小于基准的各自在一边
      • 以此达到二分的效果,最终分成的多段序列都各自有序
      • 基准可以取序列中任意一个元素,如开头、中间、结尾
    • 复杂度

      • 时间复杂度:O(nlogn),最坏情况下O(n^2)
      • 空间复杂度:O(logn),主要是递归函数的栈空间
      • 排序不稳定,性能受输入数据的影响,因此需要优化
    • 快排优化:随机乱序避免二分失效

      • 实际上,由于快排对序列的分布要求较高,即最坏情况下会退化回O(n^2)的时间复杂度
      • 所以针对难搞的数据集,要先对数据随机乱序,再取基准进行快排。
    • 源码:取开头为基准,双向遍历

      public class QuickSorter {
          private static Random random = new Random();
      
          public static void main(String[] args) throws IOException {
              BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
              String[] strings = br.readLine().split(" ");
              int[] nums = new int[strings.length];
              for (int i = 0; i < strings.length; i++) {
                  nums[i] = Integer.parseInt(strings[i]);
              }
              new QuickSorter().quickSort(nums, 0, nums.length - 1);
              System.out.println(Arrays.toString(nums));
          }
      
          private void quickSort(int[] nums, int l, int r) {
              if (l < r) {
                  int idx = partition(nums, l, r);
                  quickSort(nums, l, idx - 1);
                  quickSort(nums, idx + 1, r);
              }
          }
      
          private int partition(int[] nums, int l, int r) {
              int idx = random.nextInt(r - l) + l;
              swap(nums, l, idx);
              int pivot = nums[l];
              while (l < r) {
                  while (l < r && nums[r] >= pivot) --r;
                  nums[l] = nums[r];
                  while (l < r && nums[l] <= pivot) ++l;
                  nums[r] = nums[l];
              }
              nums[l] = pivot; // 注意交换基准元素和边界元素
              return l;
          }
      
          private void swap(int[] nums, int i, int j) {
              // 位运算
              nums[i] = nums[i] ^ nums[j] ^ (nums[j] = nums[i]);
              // int temp = nums[i];
              // nums[i] = nums[j];
              // nums[j] = temp;
          }
      }
      
    • 源码:取开头为基准,单向遍历

      private int partition(int[] nums, int l, int r) {
          int idx = random.nextInt(r - l) + l;
          swap(nums, l, idx);
          int pivot = nums[l];
          int mark = l;
          for (int i = l + 1; i <= r; i++) {
              if(nums[i] < pivot) {
                  mark++;
                  swap(nums, mark, i);
              }
          }
          swap(nums, l , mark); // 注意交换基准元素和边界元素
          return mark;
      }
      
  • 归并排序:MergeSort:divide & merge

    • 思想:分治多路归并(多叉树)

      • 归并排序也是通过分治的思想,将序列不断分解为多个子序列,然后再进行排序,各个子序列有序后,合并得到的就是有序序列
      • 一般的情况下都是用二路归并,即类似二分的方式从序列中点来分解、合并序列,而一些大文件排序的场景,可能会用到多路归并
      • 与快排不同的是,快排在按照基准partition分区的过程中,也会进行一定的排序(大的一边、小的一边),而归并排序是完全将序列划分到不可分时才进行排序
    • 复杂度分析

      • 时间复杂度:O(nlogn),合并的复杂度为O(n),而分治的过程是一个完全二叉树,深度为O(logn),两者嵌套,总的复杂度O(nlogn)
      • 空间复杂度:O(n),需要一个辅助数组
      • 排序稳定,性能不受输入数据的影响
    • 源码

      public class MergeSorterArray {
      
          public static void main(String[] args) throws IOException {
              BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
              String[] strings = br.readLine().split(" ");
              int[] nums = new int[strings.length];
              for (int i = 0; i < strings.length; i++) {
                  nums[i] = Integer.parseInt(strings[i]); // 7354613
              }
              new MergeSorterArray().mergeSort(nums, 0, nums.length - 1, new int[nums.length]);
              System.out.println(Arrays.toString(nums));
          }
      
      
          public void mergeSort(int[] nums, int left, int right, int[] save) {
              if (left < right) {
                  int mid = (right - left) / 2 + left;
                  mergeSort(nums, left, mid, save);
                  mergeSort(nums, mid + 1, right, save);
                  merge(nums, left, mid, right, save);
              }
          }
      
          private void merge(int[] nums, int left, int mid, int right, int[] save) {
              int i = left, j = mid + 1; // 双指针,用来合并数组
              int index = 0;
              while (i <= mid && j <= right) {
                  if (nums[i] < nums[j]) {
                      save[index++] = nums[i++];
                  } else {
                      save[index++] = nums[j++];
                  }
              }
              while (i <= mid) {
                  save[index++] = nums[i++];
              }
              while (j <= right) {
                  save[index++] = nums[j++];
              }
              // 将排序后的辅助数组copy回原数组
              index = 0;
              while (left <= right) {
                  nums[left++] = save[index++];
              }
          }
      }
      
  • 堆排序:HeapSort

    • 思想:最小堆的构建与调整

      • 堆排序是一种选择排序,是利用完全二叉树的性质来优化排序的一种算法
      • 主要分为构建初始堆交换堆顶和末尾元素重建堆三个方面
      • 其中,要注意完全二叉树的叶子节点的特性,以及重建堆的时候有一半的树无需操作
      • 值得注意的是,堆排序无需真的实现树结构,只需要用数组模拟小顶堆即可
    • 复杂度分析

      • 时间复杂度:O(nlogn),构建初始堆的复杂度为O(n),而重建堆的复杂度为O(nlogn),两者平行,总的复杂度O(nlogn)
      • 空间复杂度:O(1),无需额外空间
      • 排序不稳定,性能不受输入数据的影响
    • 源码

      public class HeapSorter {
      
          public static void main(String[] args) throws IOException {
              BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
              String[] strings = br.readLine().split(" ");
              int[] nums = new int[strings.length];
              for (int i = 0; i < strings.length; i++) {
                  nums[i] = Integer.parseInt(strings[i]); // 7354613
              }
              new HeapSorter().heapSort(nums, nums.length);
              System.out.println(Arrays.toString(nums));
          }
      
          public void heapSort(int[] nums, int n) {
              buildHeap(nums, n);
              for (int i = n - 1; i >= 0; i--) {
                  swap(nums, i, 0); // 交换堆顶和末尾的元素,最大值
                  heapify(nums, i, 0); // 从根节点开始调整整个树
              }
          }
      
          // 创建大顶堆
          private void buildHeap(int[] nums, int n) {
              int lastIndex = n - 1;
              int parentIndex = (lastIndex - 1) / 2;
              for (int i = parentIndex; i >= 0; i--) {
                  heapify(nums, n, i);
              }
              System.out.println(Arrays.toString(nums));
          }
      
          private void heapify(int[] nums, int n, int i) {
              if (i >= n) return;
              int left = 2 * i + 1, right = 2 * i + 2; // 左子节点和后子节点的索引
              int max = i; // 最大元素的索引,初始化为目前要调整的子树的根节点
              // 比较交换局部最大值
              if (left < n && nums[left] > nums[max]) max = left;
              if (right < n && nums[right] > nums[max]) max = right;
              if (max != i) { // 如果max变化了
                  swap(nums, i, max);
                  heapify(nums, n, max); // 继续取调整节点值被提升到 i 节点后,新的需要调整的子树
              }
          }
      
          private void swap(int[] nums, int i, int j) {
              int temp = nums[i];
              nums[i] = nums[j];
              nums[j] = temp;
          }
      }
      

趁热打铁:面试高频


最后

如果本文有所帮助的话,欢迎大家可以给个三连「点赞」&「收藏」&「关注」 ~ ~ ~
也希望大家有空的时候光临我的其他平台,上面会更新Java面经、八股文、刷题记录等等,欢迎大家光临交流,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容