web-dev-qa-db-fra.com

Trouver la valeur la plus proche / la plus proche dans une liste triée

Je me demandais s'il était possible de trouver l'élément le plus proche dans un List pour un élément ce n'est pas là.

Par exemple, si nous avions les valeurs [1,3,6,7] et que nous recherchons l'élément le plus proche de 4, il devrait renvoyer 3, car 3 est le plus grand nombre du tableau, c'est-à-dire inférieur à 4.

J'espère que cela a du sens, car l'anglais n'est pas ma langue maternelle.

14
drBet

Si le tableau est trié, vous pouvez effectuer une recherche binaire modifiée dans O( log n ):

    public static int search(int value, int[] a) {

        if(value < a[0]) {
            return a[0];
        }
        if(value > a[a.length-1]) {
            return a[a.length-1];
        }

        int lo = 0;
        int hi = a.length - 1;

        while (lo <= hi) {
            int mid = (hi + lo) / 2;

            if (value < a[mid]) {
                hi = mid - 1;
            } else if (value > a[mid]) {
                lo = mid + 1;
            } else {
                return a[mid];
            }
        }
        // lo == hi + 1
        return (a[lo] - value) < (value - a[hi]) ? a[lo] : a[hi];
    }
31
David Soroko

Vous avez besoin Array.binarySearch, documents .

Renvoie: index de la clé de recherche, si elle est contenue dans le tableau; sinon, (- (point d'insertion) - 1). Le point d'insertion est défini comme le point auquel la clé serait insérée dans le tableau: l'indice du premier élément supérieur à la clé, ou a.length si tous les éléments du tableau sont inférieurs à la clé spécifiée.

14
Andrey

Envisager d'utiliser NavigableSet, en particulier higher et lower.

5
Steve Kuo

Une autre solution O (log n) facile à comprendre utilisant la recherche binaire:

public class Solution {
    static int findClosest(int arr[], int n, int target)
    {
        int l=0, h=n-1, diff=Integer.MAX_VALUE, val=arr[0];
        while(l<=h)
        {
            int mid=l+(h-l)/2;
            if(Math.abs(target-arr[mid])<diff)
            {
                diff= Math.abs(target-arr[mid]);
                val=arr[mid];
            }
            if(arr[mid]<target)
                l=mid+1;
            else
                h=mid-1;
        }
        return val;

    }

    public static void main(String[] args) {
        System.out.println(findClosest(new int[]{1,3,6,7}, 4, 3));
    }
}
0
NewUser

En pensant du haut de ma tête, si vous avez besoin de trouver toutes les valeurs les plus proches dans une liste triée, vous pouvez trouver a la valeur la plus proche, puis trouver toutes les valeurs à la même distance de la cible. Ici, j'utilise la recherche binaire 3 fois:

  • Premier à trouver a valeur la plus proche
  • Deuxième pour trouver la valeur la plus proche de gauche
  • Troisième pour trouver la valeur la plus proche de droite

En Python:

def closest_value(arr, target):
  def helper(arr, target, lo, hi, closest_so_far):
    # Edge case
    if lo == hi:
      mid = lo
      if abs(arr[mid] - target) < abs(arr[closest_so_far] - target):
        closest_so_far = mid
      return closest_so_far

    # General case
    mid = ((hi - lo) >> 1) + lo

    if arr[mid] == target:
      return mid

    if abs(arr[mid] - target) < abs(arr[closest_so_far] - target):
      closest_so_far = mid

    if arr[mid] < target:
      # Search right
      return helper(arr, target, min(mid + 1, hi), hi, closest_so_far)
    else:
      # Search left
      return helper(arr, target, lo, max(mid - 1, lo), closest_so_far)


  if len(arr) == 0:
    return -1
  return helper(arr, target, 0, len(arr) - 1, arr[0])


arr = [0, 10, 14, 27, 28, 30, 47]

attempt = closest_value(arr, 26)
print(attempt, arr[attempt])
assert attempt == 3

attempt = closest_value(arr, 29)
print(attempt, arr[attempt])
assert attempt in (4, 5)


def closest_values(arr, target):
  def left_helper(arr, target, abs_diff, lo, hi):
    # Base case
    if lo == hi:
      diff = arr[lo] - target
      if abs(diff) == abs_diff:
        return lo
      else:
        return lo + 1

    # General case
    mid = ((hi - lo) >> 1) + lo
    diff = arr[mid] - target
    if diff < 0 and abs(diff) > abs_diff:
      # Search right
      return left_helper(arr, target, abs_diff, min(mid + 1, hi), hi)
    Elif abs(diff) == abs_diff:
      # Search left
      return left_helper(arr, target, abs_diff, lo, max(mid - 1, lo))
    else:
      # Search left
      return left_helper(arr, target, abs_diff, lo, max(mid - 1, lo))


  def right_helper(arr, target, abs_diff, lo, hi):
    # Base case
    if lo == hi:
      diff = arr[lo] - target
      if abs(diff) == abs_diff:
        return lo
      else:
        return lo - 1

    # General case
    mid = ((hi - lo) >> 1) + lo
    diff = arr[mid] - target
    if diff < 0 and abs(diff) > abs_diff:
      # Search right
      return right_helper(arr, target, abs_diff, min(mid + 1, hi), hi)
    Elif abs(diff) == abs_diff:
      # Search right
      return right_helper(arr, target, abs_diff, min(mid + 1, hi), hi)
    else:
      # Search left
      return right_helper(arr, target, abs_diff, lo, max(mid - 1, lo))


  a_closest_value = closest_value(arr, target)
  if a_closest_value == -1:
    return -1, -1

  n = len(arr)
  abs_diff = abs(arr[a_closest_value] - target)
  left = left_helper(arr, target, abs_diff, 0, a_closest_value)
  right = right_helper(arr, target, abs_diff, a_closest_value, n - 1)
  return left, right


arr = [0, 10, 14, 27, 27, 29, 30]

attempt = closest_values(arr, 28)
print(attempt, arr[attempt[0] : attempt[1] + 1])
assert attempt == (3, 5)

attempt = closest_values(arr, 27)
print(attempt, arr[attempt[0] : attempt[1] + 1])
assert attempt == (3, 4)
0
richizy

Il semble que le moyen le plus simple consiste simplement à parcourir la liste triée en vérifiant chaque élément.

List<Integer> ints = new ArrayList<>();
ints.add(1);
ints.add(3);
ints.add(6);
ints.add(7);

Collections.sort(ints);

int target = 4;
int nearest = 0;

for (int i : ints)
{
    if (i <= target) {
        nearest = i;
    }
}

System.out.println(nearest);

Cela génère le plus grand élément de la liste qui est inférieur ou égal à target.

0
starf

La réponse d'Andrey est correcte. Je développe juste un peu.
Pas besoin de réinventer la roue lorsque vous pouvez utiliser la recherche binaire intégrée.

Vous pouvez trouver les indices avec:

int leftIndex = (-Collections.binarySearch(allItems, key) - 2);
int rightIndex = (-Collections.binarySearch(allItems, key) - 1);

L'élément de la liste devra implémenter comparable . Des types simples comme String et Integer implémentent déjà cela. Voici un exemple https://www.javatpoint.com/Comparable-interface-in-collection-framework .

Selon votre cas d'utilisation, vous voudrez peut-être faire index = Math.max(0, index) après la recherche binaire juste pour être sûr.

0
Markymark