Comment trouver le kème plus grand élément en Java

Comment trouver le Kth Largest Element in Java

1. introduction

Dans cet article, nous présenterons différentes solutions pour trouver l'élément le plus grandkth dans une séquence de nombres uniques. Nous utiliserons un tableau d'entiers pour nos exemples.

Nous parlerons également de la complexité temporelle moyenne et dans le pire des cas de chaque algorithme.

2. Solutions

Explorons maintenant quelques solutions possibles - l'une utilisant un tri simple et deux utilisant l'algorithme de sélection rapide dérivé du tri rapide.

2.1. Tri

Quand on pense au problème, peut-êtrethe most obvious solution that comes to mind isto sort the array.

Définissons les étapes requises:

  • Trier le tableau en ordre croissant

  • Comme le dernier élément du tableau serait le plus grand élément, l'élément le plus grandkth serait à l'indexxth, oùx = length(array) – k

Comme nous pouvons le constater, la solution est simple mais nécessite le tri de l’ensemble du tableau. Par conséquent, la complexité temporelle seraO(n*logn):

public int findKthLargestBySorting(Integer[] arr, int k) {
    Arrays.sort(arr);
    int targetIndex = arr.length - k;
    return arr[targetIndex];
}

Une autre approche consiste à trier le tableau par ordre décroissant et à simplement renvoyer l'élément sur l'index(k-1)th:

public int findKthLargestBySortingDesc(Integer[] arr, int k) {
    Arrays.sort(arr, Collections.reverseOrder());
    return arr[k-1];
}

2.2. Sélection rapide

Ceci peut être considéré comme une optimisation de l'approche précédente. En cela, nous choisissons lesQuickSort pour le tri. En analysant l'énoncé du problème, nous nous rendons compte quewe don’t actually need to sort the entire array — we only need to rearrange its contents so that the kth element of the array is the kth largest or smallest.

Dans QuickSort, nous sélectionnons un élément de pivot et le déplaçons dans la bonne position. Nous partitionnons également le tableau autour de lui. In QuickSelect, the idea is to stop at the point where the pivot itself is the kth largest element.

Nous pouvons optimiser davantage l'algorithme si nous ne nous reproduisons pas à la fois pour les côtés gauche et droit du pivot. Nous n'avons besoin de récidiver que pour l'un d'entre eux en fonction de la position du pivot.

Examinons les idées de base de l'algorithme QuickSelect:

  • Choisissez un élément pivot et partitionnez le tableau en conséquence

    • Choisissez l'élément le plus à droite comme pivot

    • Modifiez le tableau de manière à ce que l'élément pivot soit placé à sa juste place - tous les éléments inférieurs au pivot seraient à des index plus bas et les éléments supérieurs au pivot seraient placés à des indices plus élevés que le pivot.

  • Si le pivot est placé à l'élémentkth du tableau, quittez le processus, car pivot est l'élément le plus grandkth

  • Si la position du pivot est supérieure àk,, continuez le processus avec le sous-tableau de gauche, sinon, répétez le processus avec le sous-tableau de droite

Nous pouvons également écrire une logique générique qui peut être utilisée pour trouver le plus petit élémentkth. Nous allons définir une méthodefindKthElementByQuickSelect() qui retournera l'élémentkth dans le tableau trié.

Si nous trions le tableau dans l'ordre croissant, l'élémentkth d'un tableau sera le plus petit élémentkth. Pour trouver le plus grand élémentkth, nous pouvons passerk= length(Array) – k.

Mettons en œuvre cette solution:

public int
  findKthElementByQuickSelect(Integer[] arr, int left, int right, int k) {
    if (k >= 0 && k <= right - left + 1) {
        int pos = partition(arr, left, right);
        if (pos - left == k) {
            return arr[pos];
        }
        if (pos - left > k) {
            return findKthElementByQuickSelect(arr, left, pos - 1, k);
        }
        return findKthElementByQuickSelect(arr, pos + 1,
          right, k - pos + left - 1);
    }
    return 0;
}

Maintenant, implémentons la méthodepartition, qui sélectionne l'élément le plus à droite comme pivot, le place à l'index approprié et partitionne le tableau de telle manière que les éléments aux index inférieurs doivent être inférieurs à l'élément pivot.

De même, les éléments d'indices plus élevés seront supérieurs à l'élément pivot:

public int partition(Integer[] arr, int left, int right) {
    int pivot = arr[right];
    Integer[] leftArr;
    Integer[] rightArr;

    leftArr = IntStream.range(left, right)
      .filter(i -> arr[i] < pivot)
      .map(i -> arr[i])
      .boxed()
      .toArray(Integer[]::new);

    rightArr = IntStream.range(left, right)
      .filter(i -> arr[i] > pivot)
      .map(i -> arr[i])
      .boxed()
      .toArray(Integer[]::new);

    int leftArraySize = leftArr.length;
    System.arraycopy(leftArr, 0, arr, left, leftArraySize);
    arr[leftArraySize+left] = pivot;
    System.arraycopy(rightArr, 0, arr, left + leftArraySize + 1,
      rightArr.length);

    return left + leftArraySize;
}

Il existe une approche itérative plus simple pour réaliser le partitionnement:

public int partitionIterative(Integer[] arr, int left, int right) {
    int pivot = arr[right], i = left;
    for (int j = left; j <= right - 1; j++) {
        if (arr[j] <= pivot) {
            swap(arr, i, j);
            i++;
        }
    }
    swap(arr, i, right);
    return i;
}

public void swap(Integer[] arr, int n1, int n2) {
    int temp = arr[n2];
    arr[n2] = arr[n1];
    arr[n1] = temp;
}

Cette solution fonctionne en moyenne enO(n). Cependant, dans le pire des cas, la complexité temporelle sera deO(n^2).

2.3. QuickSelect avec partition aléatoire

Cette approche est une légère modification de l'approche précédente. Si le tableau est presque / complètement trié et si nous choisissons l’élément le plus à droite comme pivot, la partition des sous-tableaux gauche et droit sera très inégale.

Cette méthode suggèrepicking the initial pivot element in a random manner.. Nous n'avons cependant pas besoin de changer la logique de partitionnement.

Au lieu d'appelerpartition, nous appelons la méthoderandomPartition, qui sélectionne un élément aléatoire et l'échange avec l'élément le plus à droite avant d'appeler finalement la méthodepartition.

Implémentons la méthoderandomPartition:

public int randomPartition(Integer arr[], int left, int right) {
    int n = right - left + 1;
    int pivot = (int) (Math.random()) * n;
    swap(arr, left + pivot, right);
    return partition(arr, left, right);
}

Cette solution fonctionne mieux que dans le cas précédent dans la plupart des cas.

La complexité temporelle attendue de QuickSelect aléatoire est deO(n).

Cependant, la pire complexité temporelle resteO(n^2).

3. Conclusion

Dans cet article, nous avons discuté de différentes solutions pour trouver l'élément le plus grand (ou le plus petit)kth dans un tableau de nombres uniques. La solution la plus simple consiste à trier le tableau et à renvoyer l'élémentkth. Cette solution a une complexité temporelle deO(n*logn).

Nous avons également discuté de deux variantes de Quick Select. Cet algorithme n'est pas simple mais il a une complexité temporelle deO(n) dans des cas moyens.

Comme toujours, le code complet de l'algorithme peut être trouvéover on GitHub.