①归并排序、快速排序 、堆排序、计数排序[算法、代码模板、面试题]

简介: ①归并排序、快速排序 、堆排序、计数排序[算法、代码模板、面试题]


①归并排序、快速排序 、堆排序、计数排序

🚀归并排序

⚪步骤

归并排序

归并排序是一种分治法(Divide and Conquer)的经典排序算法,它的基本思想是将原始数组划分成较小的数组,然后递归地对这些小数组进行排序,最后再将排好序的小数组合并成一个整体有序的数组。下面是**归并排序的详细过程: **


详细步骤

分解(Divide):

  • 将数组一分为二,找到中间位置。
  • 递归地对左右两个子数组进行分解,直到每个子数组只有一个元素。


排序(Conquer):

  • 当每个子数组只包含一个元素时,认为它已经有序。


合并(Merge):

  • 将两个有序的子数组合并成一个有序的数组。
  • 创建一个临时数组,依次比较两个子数组的元素,将较小的元素放入临时数组。
  • 如果其中一个子数组的元素已经全部放入临时数组,将另一个子数组的剩余部分直接放入临时数组。
  • 将临时数组复制回原始数组的相应位置,完成合并。


⚪实现

算法实现

public class mergeSortTest {
  //main函数,用于测试
  public static void main(String[] args) {
    //手动创建数组
    int[] arr = new int[] {1,5,88,3,-8,7,256,-8,6,15,-96};
    //使用归并排序
    process(0,arr.length-1,arr);
    //遍历排序后数组
    for(int i = 0;i < arr.length;++i) {
      System.out.print(arr[i] + " ");
    }
  }
  //分解 + 合并(分解合并过程中已完成排序):
  public static void process(int L, int R, int[] arr) {
    if(L == R) return;            //下标重合,结束
    int mid = L + ((R - L) >> 1); //获取中间下标mid、 (R - L) >> 1 等同于 (R - L) / 2
    process(L , mid, arr);        //分解左子数组
    process(mid + 1, R, arr);     //分解右子数组
    merge(L, mid, R, arr);        //合并
  }
  //合并:
  public static void merge(int L, int mid, int R, int[] arr) {
    int[] help = new int[R - L + 1];  //帮组数组
    int p1 = L;                       //左子数组起始下标
    int p2 = mid + 1;                 //右子数组起始下标
    int i = 0;                        //帮组数组起始下标
    //合并,两个子数组元素按顺序比较,较小的元素放入帮组数组,重复此步骤。
    while(p1 <= mid && p2 <= R) {
      help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
    }
    while(p1 <= mid) {
      help[i++] = arr[p1++];
    }
    while(p2 <= R) {
      help[i++] = arr[p2++];
    }
    //将帮助数组的元素赋值回元素组。
    for(i = 0;i < help.length; ++i) {
      arr[i + L] = help[i];
    }
  }
}


⚪复杂度

复杂度分析

  • 时间复杂度: O(n log n) - 归并排序始终都是O(n log n)的,无论最坏情况还是平均情况。
  • 空间复杂度: O(n) - 归并排序需要一个与原始数组同样大小的辅助数组来进行合并操作,因此空间复杂度为O(n)。




🚀快速排序

⚪步骤

快速排序

快速排序(Quick Sort)是一种常用的基于分治思想的排序算法。它的基本思想是通过一趟排序将待排序的记录分割成独立的两部分,其中一部分的所有记录均比另一部分的记录小,然后分别对这两部分记录继续进行排序,以达到整个序列有序的目的。快速排序的核心是选择一个基准元素,将小于等于基准的元素放在左边,大于基准的元素放在右边,然后对左右两个子序列分别递归地应用相同的方法。


详细步骤

  1. 选择基准元素:
  • 从待排序数组中选择一个元素作为基准元素。通常选择第一个元素、最后一个元素或者中间元素。(本文等概率随机选取一个元素作为基准,避免最坏复杂度的发生)
  1. 分区(Partition):
  • 将数组中小于等于基准的元素放在基准的左边,大于基准的元素放在右边。这一步通常称为分区操作。
  • 使用两个指针,一个在数组的起始位置,一个在数组的结束位置,分别向中间移动,交换不符合条件的元素,直到两个指针相遇。
  1. 递归排序:
  • 对基准左右两侧的子数组进行递归排序。重复以上步骤,直到每个子数组的大小为1或0。


⚪实现

算法实现

public class Qsort {
    //main函数,用于测试
  public static void main(String[] args) {
    //手动创建数组,用于测试
    int[] arr = new int[] {1,5,99,-29,698,-111,0,63,88,3,3,7,1,-8,7,256};
    //若数组为空,长度<2等情况,无需排序
    if(arr == null || arr.length < 2) {
      return;
    }
    //使用快速排序,传入数组头部和尾部下标
    quickSort(0, arr.length-1, arr);
    for(int i = 0;i < arr.length;++i) {
      System.out.print(arr[i] + " ");
    }
  }
  //快速排序函数:
  public static void quickSort(int L, int R, int[] arr) {
    //下表不越界情况下:进行基准选取、划分、递归排序
    if(L < R) {
      //使用random函数,随机选取一个元素作为基准,使得复杂度总是O(n log n)
      swap(R, L + (int)(Math.random() * (R - L + 1)), arr);
      //进行划分,获取到两个边界值:小于基准的边界less,大于基准的边界more
      //p[0] = less + 1 ,p[1] = more - 1
      int[] p = partition(L, R, arr);
      //分别对划分后的子数组继续进行上述操作,是为递归排序
      quickSort(L, p[0] - 1, arr);
      quickSort(p[1] + 1, R, arr);
    }
  }
  //划分函数:
  public static int[] partition(int L, int R, int[] arr) {
    //定义小于基准的边界less,大于基准的边界more
    int less = L - 1;
    int more = R;
    //从数组起始下标L开始遍历,直到超过大于基准的边界more,即下标越界
    while(L < more) {
      //当前元素小于基准,与less边界外的元素交换,less边界扩展1位,下一元素作为当前元素
      if(arr[L] < arr[R]) {        
        swap(++less, L++, arr);
      //当前元素大于基准,与more边界外的元素交换,more边界扩展1位,交换后,当前元素未被比较,无需后移
      }else if(arr[L] > arr[R]) {
        swap(--more, L, arr);
      //等于基准,无需交换,直接后移
      }else {
        ++L;
      }
    }
    //划分完成后,将数组末尾的基准元素与more边界位置元素交换,真正的more边界变为more + 1
    swap(more, R, arr);
    //将边界存入数组返回:
    return new int[] {less + 1, more};
  }
  //交换函数:
  public static void swap(int a, int b, int[] arr) {
    int tmp = arr[a];
    arr[a] = arr[b];
    arr[b] = tmp;
  }
}


⚪复杂度

复杂度分析

  • 时间复杂度: 平均情况下为O(n log n),最坏情况为O(n^2)。快速排序的性能高度依赖于选择的基准元素。
  • 空间复杂度: O(log n) - 快速排序是一种原地排序算法,只需要常数级别的额外空间用于递归调用的栈。




🚀堆排序

⚪步骤

堆排序

堆排序(Heap Sort)是一种基于二叉堆数据结构的排序算法,它利用堆的性质进行排序。堆是一个完全二叉树,可以分为最大堆和最小堆两种类型。在最大堆中,父节点的值大于或等于其子节点的值;而在最小堆中,父节点的值小于或等于其子节点的值。

以下是堆排序的详细步骤:


详细步骤

  1. 建堆(Heapify):
  • 将待排序的数组构建成一个堆。建堆的过程是从最后一个非叶子节点开始,依次向上调整各个子树,使其满足堆的性质。
  1. 排序:
  • 交换堆顶元素(最大或最小元素)和堆的最后一个元素,然后将堆的大小减 1。
  • 再次调整堆,使其满足堆的性质。
  • 重复上述步骤,直到堆的大小为 1,排序完成。


⚪实现

代码实现

public class heapSortTest {
  //main函数、测试用
  public static void main(String[] args) {
    int[] arr = new int[]{1,5,88,9,-88,-99,-685,78,4,3,3,7,1,-8,7,256};
    heapSort(arr);
    for(int i = 0;i < arr.length;++i) {
      System.out.print(arr[i] + " ");
    }
  }
  // 堆排序,排序函数
  public static void heapSort(int[] arr) {
    if(arr == null || arr.length < 2) return;
    for(int i = arr.length - 1; i >= 0; --i) {
      heapify(arr, i, arr.length - 1);
    }
    int heapSize = arr.length;
    swap(arr, 0, --heapSize);
    while(heapSize > 0) {
      heapify(arr, 0, heapSize);
      swap(arr, 0, --heapSize);
    }
  }
  //heapify,建堆函数
  public static void heapify(int[] arr, int index, int heapSize) {
    //获取左孩子下标
    int left = 2 * index + 1;
    //left < heapSize : 当前节点存在左孩子
    while(left < heapSize) {
      // 获取左孩子、右孩子中最大值
      int largest = left + 1 < heapSize && arr[left] < arr[left + 1] ? left + 1 : left;
      // 较大孩子元素与父节点元素比较,更新较大值下标
      largest = arr[index] < arr[largest] ? largest : index;
      // 父节点最大,无法下移,直接停止
      if(index == largest) break;
      // 父节点小于较大孩子,下移
      swap(arr, index, largest);
      // 维护父节点和左孩子
      index = largest;
      left = 2 * index + 1;
    }
  }
  //交换函数
  public static void swap(int[] arr, int a, int b) {
    int temp = arr[a];
    arr[a] = arr[b];
    arr[b] = temp;
  }
}


⚪复杂度

复杂度分析

  • 时间复杂度: 建堆操作的时间复杂度为O(n),排序操作的时间复杂度为O(n log n)。因此,堆排序的总体时间复杂度为O(n log n)。
  • 空间复杂度: 堆排序是一种原地排序算法,只需使用常数级别的额外空间,因此空间复杂度为O(1)。




🚀912. 排序数组

[⚪点击跳转【LeetCode 912. 排序数组】](912. 排序数组 - 力扣(LeetCode))

题目

给你一个整数数组 nums,请你将该数组升序排列。

示例 1:

输入:nums = [5,2,3,1]
输出:[1,2,3,5]

示例 2:

输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]

提示:

  • 1 <= nums.length <= 5 * 104
  • -5 * 104 <= nums[i] <= 5 * 104


解法一:归并排序

//使用归并排序
class Solution {
    public int[] sortArray(int[] nums) {
        int length = nums.length;
        mergeSort(nums,0,nums.length - 1);
        return nums;
    }
    public void mergeSort(int[] arr,int l,int r){
        //子数组长度为1,停止
        if(l == r) return;
        //取中点
        int mid = l + ((r - l) >> 1);
        //递归排序左子数组
        mergeSort(arr,l,mid);
        //递归排序右子数组
        mergeSort(arr,mid + 1,r);
        //合并两个排序好的数组
        merge(arr,l,mid,r);
    }
    public void merge(int[] arr,int l,int mid,int r){
        //help数组
        int[] help = new int[r - l + 1];
        //help数组下标
        int i = 0;
        //左子数组头p1
        int p1 = l;
        //右子数组头p2
        int p2 = mid + 1;
        //合并两个有序数组,存入help数组
        while(p1 <= mid && p2 <= r){
            help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
        }
        while(p1 <= mid){
            help[i++] = arr[p1++];
        }
        while(p2 <= r){
            help[i++] = arr[p2++];
        }
        //将help数组的元素赋值回目标数组
        for(int j = 0;j < help.length;++j){
            arr[l + j] = help[j];
        }
    }
}


解法二:快速排序

class Solution {
    //快排
    public int[] sortArray(int[] nums) {
        if(nums == null || nums.length < 2) return nums; //空或长度1,无需排序
        qSort(nums,0,nums.length - 1); //快排
        return nums;
    }
    public static void qSort(int[] arr,int l,int r){
        //数组头尾不冲突
        if(l < r){
            //利用random等概率随机获取基准 与尾部元素交换位置
            swap(arr,r,l + (int)(Math.random() * (r - l + 1)));
            //得到长度二的数组,两个元素分别代表大于基准元素与小于基准元素的边界
            int[] p = partition(arr,l,r);
            qSort(arr,l,p[0]);
            qSort(arr,p[1],r);
        }
    }
    //划分函数
    public static int[] partition(int[] arr,int l,int r){
        int less = l - 1; //小于基准的边界
        int more = r;     //大于基准的边界
        while(l < more){
            if(arr[l] < arr[r]){
                swap(arr,++less,l++);
            }else if(arr[l] > arr[r]){
                swap(arr,--more,l);
            }else{
                ++l;
            }
        }
        swap(arr,more,r);
        return new int[]{less,more + 1};
    }
    //交换
    public static void swap(int[] arr,int a,int b){
        int tmp = arr[a];
        arr[a] = arr[b];
        arr[b] = tmp;
    }
}


解法三:堆排序

class Solution {
    public int[] sortArray(int[] nums) {
        heapSort(nums);
        return nums;
    }
    public static void heapSort(int[] arr){
        if(arr == null || arr.length < 2) return;
        for(int i = arr.length - 1; i >= 0; --i){
            heapify(arr, i, arr.length);
        }
        int heapSize = arr.length;
        swap(arr, 0, --heapSize);
        while(heapSize > 0){
            heapify(arr, 0, heapSize);
            swap(arr, 0, --heapSize);
        }
    }
    public static void heapify(int[] arr, int index, int heapSize){
        int left = 2 * index + 1;
        while(left < heapSize){
            int largest = left + 1 < heapSize && arr[left] < arr[left+1] ? left + 1 : left;
            largest = arr[index] < arr[largest] ? largest : index;
            if(largest == index) break;
            swap(arr, index, largest);
            index = largest;
            left = 2 * index + 1;
        }
    }
    public static void swap(int[] arr, int a, int b){
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
}




🚀315. 计算右侧小于当前元素的个数

⚪点击跳转:315. 计算右侧小于当前元素的个数

给你一个整数数组 nums ,按要求返回一个新数组 counts 。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。

示例 1:

输入:nums = [5,2,6,1]
输出:[2,1,1,0] 
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素

示例 2:

输入:nums = [-1]
输出:[0]

示例 3:

输入:nums = [-1,-1]
输出:[0,0]

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104


利用归并排序性质,完成计数

// 利用归并排序的特性, 计算 nums[i] 右侧小于 nums[i] 的元素的数量。
class Solution {
    static int[] counts; // 题目要求的counts数组
    static int[] index;  // 下标数组,记录nums元素的移动,即:元素移动,下标做同样操作。
    public List<Integer> countSmaller(int[] nums) {
        List<Integer> list = new ArrayList<>(); //创建集合,因为:函数返回值为List<Integer>
        if(nums == null) return list;           //数组为空,直接返回空集合
        if(nums.length == 1){                   //数组长度=1
            list.add(0);      //右侧没有小于nums[i]的元素,所以nums[i]=0,添加到集合中
            return list;      //直接返回
        }
        //初始化counts和index
        int n = nums.length;
        this.counts = new int[n];
        this.index = new int[n];
        //为下标数组元素 附上 下标值
        for(int i = 0;i < n; ++i){
            index[i] = i;
        }
        //进行归并排序,在排序过程中完成计数
        mergeSort(nums, 0, n - 1);
        //将得到的符合规则的counts数组元素赋值给集合再返回,因为:函数返回值为List<Integer>
        for(int count : counts) list.add(count);
        return list;
    }
    //归并排序:
    public static void mergeSort(int[] arr, int l, int r){
        if(l == r) return;
        int mid = l + ((r - l) >> 1);
        mergeSort(arr, l, mid);
        mergeSort(arr, mid + 1, r);
        merge(arr, l, mid, r);
    }
    //合并有序子序列
    public static void merge(int[] arr, int l, int mid, int r){
        int[] help = new int[r - l + 1];   //元素的help数组,用于合并有序子序列
        int[] helpI = new int[r - l + 1];  //下标的help数组,用于记录合并有序子序列时元素的移动
        int i = 0;        //下标
        int p1 = l;       //左子序列头元素下标
        int p2 = mid + 1; //右子序列头元素下标
        while(p1 <= mid && p2 <= r){
            //按照逆序,进行归并排序的合并
            if(arr[p1] > arr[p2]){
                //因为逆序,所以p2到R范围内的元素是降序的,即:
                //当前情况下nums[i] 右侧小于 nums[i] 的元素的数量 = r - p2 + 1
                //在排序过程中,累加所有情况下的r - p2 + 1,就能的到总数。
                counts[index[p1]] += r - p2 + 1;
                help[i] = arr[p1];
                helpI[i++] = index[p1++];
            }else{
                help[i] = arr[p2];
                helpI[i++] = index[p2++];
            }
        }
        while(p1 <= mid){
            help[i] = arr[p1];
            helpI[i++] = index[p1++];
        }
        while(p2 <= r){
            help[i] = arr[p2];
            helpI[i++] = index[p2++];
        }
        //将help数组中的元素 赋值回原数组中,完成合并。
        for(i = 0;i < help.length; ++i){
            arr[i + l] = help[i];
            index[i + l] = helpI[i];
        }
    }
}




🚀561. 数组拆分

⚪点击跳转:561. 数组拆分

给定长度为 2n 的整数数组 nums ,你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从 1nmin(ai, bi) 总和最大。

返回该 最大总和

示例 1:

输入:nums = [1,4,3,2]
输出:4
解释:所有可能的分法(忽略元素顺序)为:
1. (1, 4), (2, 3) -> min(1, 4) + min(2, 3) = 1 + 2 = 3
2. (1, 3), (2, 4) -> min(1, 3) + min(2, 4) = 1 + 2 = 3
3. (1, 2), (3, 4) -> min(1, 2) + min(3, 4) = 1 + 3 = 4
所以最大总和为 4

示例 2:

输入:nums = [6,2,6,5,1,2]
输出:9
解释:最优的分法为 (2, 1), (2, 5), (6, 6). min(2, 1) + min(2, 5) + min(6, 6) = 1 + 2 + 6 = 9

提示:

  • 1 <= n <= 104
  • nums.length == 2 * n
  • -104 <= nums[i] <= 104


题解(归并排序)

//归并排序
class Solution {
    public int arrayPairSum(int[] nums) {
        //排序数组(这里使用归并排序,还推荐使用堆排序、快速排序)
        mergeSort(nums, 0, nums.length - 1);
        //根据分析,有序的数组分成n对,就能满足题意
        int ans = 0;
        for(int i = 0;i < nums.length; i += 2){
            //将每队的min(ai, bi)累加起来,得到总和
            ans += nums[i];
        }
        //返回结果
        return ans;
    }
    public static void mergeSort(int[] arr, int l, int r){
        if(l == r) return;
        int mid = l + ((r - l) >> 1);
        mergeSort(arr, l, mid);
        mergeSort(arr, mid + 1, r);
        merge(arr, l, mid, r);
    }
    public static void merge(int[] arr, int l,int mid, int r){
        int[] help = new int[r - l + 1];
        int p1 = l;
        int p2 = mid + 1;
        int i = 0;
        while(p1 <= mid && p2 <= r){
            help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
        }
        while(p1 <= mid){
            help[i++] = arr[p1++];
        }
        while(p2 <= r){
            help[i++] = arr[p2++];
        }
        for(i = 0;i < help.length; ++i){
            arr[i + l] = help[i];
        }
    }
}


题解(堆排序)

class Solution {
    public int arrayPairSum(int[] nums) {
        int n = nums.length;
        heapSort(nums, n); //堆排序
        int ans = 0;
        for(int i = 0;i < n; i += 2){
            ans += nums[i];
        }
        return ans;
    }
    public static void heapSort(int[] arr, int n){
        if(arr == null || n < 2) return;
        for(int i = n - 1; i >= 0; --i){
            heapify(arr, i, n);
        }
        int heapSize = n;
        swap(arr, 0, --heapSize);
        while(heapSize > 0){
            heapify(arr, 0, heapSize);
            swap(arr, 0, --heapSize);
        }
    }
    public static void heapify(int[] arr, int index, int heapSize){
        int left = 2 * index + 1;
        while(left < heapSize){
            int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
            largest = arr[index] > arr[largest] ? index : largest;
            if(index == largest) break; //无法下移,停止
            swap(arr, index, largest);  //可以下移,交换
            index = largest;            //交换完后完成下移
            left = 2 * index + 1;       //维护左孩子
        }
    }
    public static void swap(int[] arr, int a, int b){
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
}


题解(快速排序)【速度最快】

class Solution {
    public int arrayPairSum(int[] nums) {
        int n = nums.length;
        quickSort(nums, 0, n - 1);
        int ans = 0;
        for(int i = 0;i < n; i += 2){
            ans += nums[i];
        }
        return ans;
    }
    public static void quickSort(int[] arr, int l, int r){
        if(l < r){
            swap(arr, r, l + (int)(Math.random() * (r - l + 1)));
            int[] p = partition(arr, l, r);
            quickSort(arr, l, p[0]);
            quickSort(arr,p[1], r);
        }
    }
    public static int[] partition(int[] arr, int l, int r){
        int less = l - 1;
        int more = r;
        while(l < more){
            if(arr[l] < arr[r]){
                swap(arr, ++less, l++);
            }else if(arr[l] > arr[r]){
                swap(arr, --more, l);
            }else{
                ++l;
            }
        }
        swap(arr, more, r);
        return new int[]{less, more + 1};
    }
    public static void swap(int[] arr, int a, int b){
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
}




🚀1122. 数组的相对排序(计数排序)

⚪点击跳转:1122. 数组的相对排序

给你两个数组,arr1arr2arr2 中的元素各不相同,arr2 中的每个元素都出现在 arr1 中。

arr1 中的元素进行排序,使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 的末尾。

示例 1:

输入:arr1 = [2,3,1,3,2,4,6,7,9,2,19], arr2 = [2,1,4,3,9,6]
输出:[2,2,2,1,4,3,3,9,6,7,19]

示例 2:

输入:arr1 = [28,6,22,8,44,17], arr2 = [22,28,8,6]
输出:[22,28,8,6,17,44]

提示:

  • 1 <= arr1.length, arr2.length <= 1000
  • 0 <= arr1[i], arr2[i] <= 1000
  • arr2 中的元素 arr2[i]各不相同
  • arr2 中的每个元素 arr2[i] 都出现在 arr1


题解(计数排序)

计数排序是一种非比较性的整数排序算法,适用于待排序元素的范围较小的情况。该算法的基本思想是统计每个元素的出现次数,然后根据这些统计信息将元素放回正确的位置。

class Solution {
    //计数排序
    public int[] relativeSortArray(int[] arr1, int[] arr2) {
        //题目给定元素大小不超过1000,就可以设置一个1001长度的数组来进行计数排序
        int[] arr = new int[1001];         //计数数组
        int[] ans = new int[arr1.length];  //排序数组
        int index = 0;                     //排序数组的下标
        //遍历arr1,遍历到元素,就使arr数组对应下标的值+1
        for(int num : arr1){
            arr[num] += 1;
        }
        //遍历arr2数组,先将与arr2相对顺序元素放入排序数组
        for(int num : arr2){
            for(int i = 0;i < arr[num]; ++i){
                ans[index++] = num;
            }
            arr[num] = 0; //维护计数数组
        }
        //遍历计数数组,将剩下的元素排序好并放入排序数组
        for(int k = 0;k < 1001; ++k){
            for(int i = 0;i < arr[k]; ++i){
                ans[index++] = k;
            }
        }
        return ans;
    }
}




🚀268. 丢失的数字(计数排序)

⚪点击跳转:268. 丢失的数字

给定一个包含 [0, n]n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。

示例 1:

输入:nums = [3,0,1]
输出:2
解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。

示例 2:

输入:nums = [0,1]
输出:2
解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。

示例 3:

输入:nums = [9,6,4,2,3,5,7,0,1]
输出:8
解释:n = 9,因为有 9 个数字,所以所有的数字都在范围 [0,9] 内。8 是丢失的数字,因为它没有出现在 nums 中。

示例 4:

输入:nums = [0]
输出:1
解释:n = 1,因为有 1 个数字,所以所有的数字都在范围 [0,1] 内。1 是丢失的数字,因为它没有出现在 nums 中。

提示:

  • n == nums.length
  • 1 <= n <= 104
  • 0 <= nums[i] <= n
  • nums 中的所有数字都 独一无二


题解(计数排序)

class Solution {
    public int missingNumber(int[] nums) {
        int ans = -1;               //没有出现在数组中的那个数。
        int n = nums.length;        //题目提到的n
        int[] arr = new int[n + 1]; //计数数组,用于记录元素出现的次数
        for(int num : nums){        //使用计数数组,记录并排序nums数组元素
            arr[num] += 1;
        }
        for(int i = 0;i <= n; ++i){ //遍历计数数组,值为0表示未出现
            if(arr[i] == 0){
                ans = i;
                break;
            }
        }
        return ans;
    }
}




🚀215. 数组中的第K个最大元素

⚪点击跳转:215. 数组中的第K个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 **k** 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入: [3,2,1,5,6,4], k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4

提示:

  • 1 <= k <= nums.length <= 105
  • -104 <= nums[i] <= 104


题解(快速排序)

class Solution {
    public int findKthLargest(int[] nums, int k) {
        quickSort(nums, 0, nums.length - 1);
        int ans = Integer.MIN_VALUE;
        for(int i = 0;i <= nums.length - k; ++i){
            ans = nums[i];
        }
        return ans;
    }
    public static void quickSort(int[] arr, int l, int r){
        if(l < r){
            swap(arr, r, l + (int)(Math.random() * (r - l)));
            int[] p = partition(arr, l, r);
            quickSort(arr, l, p[0]);
            quickSort(arr, p[1], r);
        }
    }
    public static int[] partition(int[] arr, int l, int r){
        int less = l - 1;
        int more = r;
        while(l < more){
            if(arr[l] < arr[r]){
                swap(arr, ++less, l++);
            }else if(arr[l] > arr[r]){
                swap(arr, --more, l);
            }else{
                ++l;
            }
        }
        swap(arr, more, r);
        return new int[]{less, more + 1};
    }
    public static void swap(int[] arr, int a, int b){
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
}




🚀347. 前 K 个高频元素

⚪点击跳转:347. 前 K 个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:

输入: nums = [1], k = 1
输出: [1]

提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的


题解(优先队列[堆排序] + hash表)

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        PriorityQueue<int[]> pq = new PriorityQueue<>((a,b) -> (b[1] - a[1]));
        Map<Integer, Integer> map = new HashMap<>();
        int[] ans = new int[k];
        for(int num : nums){
            if(!map.containsKey(num)){
                map.put(num,1);
            }else{
                map.put(num,map.get(num).intValue() + 1);
            }
        }
        for(Map.Entry<Integer, Integer> entry : map.entrySet()){
            pq.offer(new int[]{entry.getKey(), entry.getValue()});
        }
        for(int i = 0;i < k; ++i){
            ans[i] = pq.poll()[0];
        }
        return ans;
    }
}




🚀LCR 159. 库存管理 III(计数排序)

⚪点击跳转:LCR 159. 库存管理 III

仓库管理员以数组 stock 形式记录商品库存表,其中 stock[i] 表示对应商品库存余量。请返回库存余量最少的 cnt 个商品余量,返回 顺序不限

示例 1:

输入:stock = [2,5,7,4], cnt = 1
输出:[2]

示例 2:

输入:stock = [0,2,3,6], cnt = 2
输出:[0,2] 或 [2,0]

提示:

  • 0 <= cnt <= stock.length <= 10000 0 <= stock[i] <= 10000


题解(计数排序)

class Solution {
    public int[] inventoryManagement(int[] stock, int cnt) {
        int[] help = new int[10001];
        for(int num : stock){
            help[num] += 1;
        }
        int[] ans = new int[cnt];
        int index = 0;
        a:for(int i = 0;i < 10001; ++i){
            for(int j = 0;j < help[i]; ++j){
                if(index < cnt){
                    ans[index++] = i;
                }else{
                    break a;
                }
            }
        }
        return ans;
    }
}


题解(快速排序)

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        int[] ans = new int[k];
        qSort(arr,0,arr.length - 1);
        for(int i = 0;i < k;++i){
            ans[i] = arr[i];
        }
        return ans;
    }
    public static void qSort(int[] arr,int l,int r){
        if(l < r){
            swap(arr,r,l + (int)(Math.random() * (r - l + 1)));
            int[] p = partition(arr,l,r);
            qSort(arr,l,p[0]-1);
            qSort(arr,p[1]+1,r);
        }
    }
    public static int[] partition(int[] arr,int l,int r){
        int less = l - 1;
        int more = r;
        while(l < more){
            if(arr[l] < arr[r]){
                swap(arr,++less,l++);
            }else if(arr[l] > arr[r]){
                swap(arr,l,--more);
            }else{
                ++l;
            }
        }
        swap(arr,r,more);
        return new int[]{less+1,more};
    }
    public static void swap(int[] arr,int a,int b){
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
}




🚀LCR 170. 交易逆序对的总数

⚪点击跳转:LCR 170. 交易逆序对的总数

在股票交易中,如果前一天的股价高于后一天的股价,则可以认为存在一个「交易逆序对」。请设计一个程序,输入一段时间内的股票交易记录 record,返回其中存在的「交易逆序对」总数。

示例 1:

输入:record = [9, 7, 5, 4, 6]
输出:8
解释:交易中的逆序对为 (9, 7), (9, 5), (9, 4), (9, 6), (7, 5), (7, 4), (7, 6), (5, 4)。

限制:

0 <= record.length <= 50000


归并排序 题解

class Solution {
    int ans = 0;
    public int reversePairs(int[] record) {
        if(record == null || record.length < 2) return ans;
        mergeSort(record, 0, record.length - 1);
        return ans;
    }
    public void mergeSort(int[] arr, int l, int r){
        if(l == r) return;
        int mid = l + ((r - l) >> 1);
        mergeSort(arr, l, mid);
        mergeSort(arr, mid + 1, r);
        merge(arr, l, mid, r);
    }
    public void merge(int[] arr, int l, int mid, int r){
        int[] help = new int[r - l + 1];
        int p1 = l;
        int p2 = mid + 1;
        int i = 0;
        while(p1 <= mid && p2 <= r){
            if(arr[p1] > arr[p2]){
                ans += r - p2 + 1;
                help[i++] = arr[p1++];
            }else{
                help[i++] = arr[p2++];
            }
        }
        while(p1 <= mid){
            help[i++] = arr[p1++];
        }
        while(p2 <= r){
            help[i++] = arr[p2++];
        }
        for(i = 0;i < help.length; ++i){
            arr[i + l] = help[i];
        }
    }
}




目录
相关文章
|
7天前
|
搜索推荐 C语言
【排序算法】快速排序升级版--三路快排详解 + 实现(c语言)
本文介绍了快速排序的升级版——三路快排。传统快速排序在处理大量相同元素时效率较低,而三路快排通过将数组分为三部分(小于、等于、大于基准值)来优化这一问题。文章详细讲解了三路快排的实现步骤,并提供了完整的代码示例。
28 4
|
30天前
|
算法 Java 数据库
美团面试:百亿级分片,如何设计基因算法?
40岁老架构师尼恩分享分库分表的基因算法设计,涵盖分片键选择、水平拆分策略及基因法优化查询效率等内容,助力面试者应对大厂技术面试,提高架构设计能力。
美团面试:百亿级分片,如何设计基因算法?
|
1月前
|
算法 前端开发 Java
数据结构与算法学习四:单链表面试题,新浪、腾讯【有难度】、百度面试题
这篇文章总结了单链表的常见面试题,并提供了详细的问题分析、思路分析以及Java代码实现,包括求单链表中有效节点的个数、查找单链表中的倒数第k个节点、单链表的反转以及从尾到头打印单链表等题目。
32 1
数据结构与算法学习四:单链表面试题,新浪、腾讯【有难度】、百度面试题
|
25天前
|
机器学习/深度学习 算法 Java
机器学习、基础算法、python常见面试题必知必答系列大全:(面试问题持续更新)
机器学习、基础算法、python常见面试题必知必答系列大全:(面试问题持续更新)
|
30天前
|
算法 Java 数据库
美团面试:百亿级分片,如何设计基因算法?
40岁老架构师尼恩在读者群中分享了关于分库分表的基因算法设计,旨在帮助大家应对一线互联网企业的面试题。文章详细介绍了分库分表的背景、分片键的设计目标和建议,以及基因法的具体应用和优缺点。通过系统化的梳理,帮助读者提升架构、设计和开发水平,顺利通过面试。
美团面试:百亿级分片,如何设计基因算法?
|
1月前
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
20 1
|
1月前
|
算法 Java 数据中心
探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式
【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。
67 2
|
1月前
|
存储 搜索推荐 算法
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
|
24天前
|
算法 安全 数据安全/隐私保护
基于game-based算法的动态频谱访问matlab仿真
本算法展示了在认知无线电网络中,通过游戏理论优化动态频谱访问,提高频谱利用率和物理层安全性。程序运行效果包括负载因子、传输功率、信噪比对用户效用和保密率的影响分析。软件版本:Matlab 2022a。完整代码包含详细中文注释和操作视频。
|
8天前
|
算法 数据挖掘 数据安全/隐私保护
基于FCM模糊聚类算法的图像分割matlab仿真
本项目展示了基于模糊C均值(FCM)算法的图像分割技术。算法运行效果良好,无水印。使用MATLAB 2022a开发,提供完整代码及中文注释,附带操作步骤视频。FCM算法通过隶属度矩阵和聚类中心矩阵实现图像分割,适用于灰度和彩色图像,广泛应用于医学影像、遥感图像等领域。