【问题标题】:Find the nearest/closest value in a sorted List [duplicate]在排序列表中查找最近/最接近的值[重复]
【发布时间】:2015-07-26 12:45:54
【问题描述】:

我想知道是否可以在排序后的List 中为列表中不在的元素找到最接近的元素。

例如,如果我们有值 [1,3,6,7] 并且我们正在寻找最接近 4 的元素,它应该返回 3,因为 3 是数组中的最大数,小于 4 .

我希望这是有道理的,因为英语不是我的母语。

【问题讨论】:

  • 并且在这种情况下对arraylist进行了排序...
  • 您想要最接近的数字(可能更大)还是小于查询值的最接近的数字?
  • 那么,拥有 3 和 5 的结果是什么(在你的情况下)。您选择较低的,因为没有较高的闭合器......但是如果您有这两个呢?
  • 遍历列表直到找到第一个大于x 的值或到达列表末尾。然后检查最后两个值。
  • @blazetopher 小于,例如在这种情况下 3 小于 4...

标签: java binary-search


【解决方案1】:

由于集合已排序,您可以在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];
    }

由于上面的大部分代码都是二进制搜索,您可以利用 std 库中提供的 binarySearch(...) 并检查 insertion point 的值:

    public static int usingBinarySearch(int value, int[] a) {
        if (value <= a[0]) { return a[0]; }
        if (value >= a[a.length - 1]) { return a[a.length - 1]; }

        int result = Arrays.binarySearch(a, value);
        if (result >= 0) { return a[result]; }

        int insertionPoint = -result - 1;
        return (a[insertionPoint] - value) < (value - a[insertionPoint - 1]) ?
                a[insertionPoint] : a[insertionPoint - 1];
    }

【讨论】:

  • 尽管当元素应该是结果时有一个小“错误”,恰好是最后一个元素之前的元素,结果是最后一个插入的......任何想法为什么会这样?例如在数组中,如果数字是 10 并且数组是 {1,3,5,7,9,11},则结果为 11,这是错误的,但如果数组是 {1,3,5,7, 9,11,12} 结果是 9,这是正确的!有什么想法吗?
  • 好吧,在{1,3,5,7,9,11} 中,9 和 11 都同样接近 10,所以你不能真正拥有 最接近的元素
  • 错误的实现!! search(7, new int[]{4, 8, 12}) 返回 4。应该返回 8。
  • 此代码中的错误是最后一个返回语句。在循环结束时,lo 指向 ceil,hi 指向键的地板(由于 while 条件检查)。因此正确的返回语句将是:return (a[lo] - value) &lt; (value - a[hi]) ? a[lo] : a[hi];
  • 差不多 - 还需要考虑 hi a.length 的情况
【解决方案2】:

有两种方法可以达到效果-

  1. lower_bound
  2. 二分查找

我更喜欢使用 lower_bound 方法,它很短:)

int pos=lower_bound(v.begin(),v.end(),value);
if(pos!=0&&target!=v[pos])
    if(abs(v[pos]-value)>abs(value-v[pos-1]))
        pos=pos-1;

对于二进制搜索,您可以参考上述答案,因为我的代码看起来像是他们代码的复制粘贴。请注意,我已在此处全局声明了数组。

binarysearch(int low,int high,int val)
{
    if(val<=arr[0])
        return 0;
    if(val>=arr[n-1])
        return arr[n-1];
    while(low<=high)
    {
        int mid=(low+high)/2;
        if(v[mid]>val)
            return binarysearch(low,mid-1,val);
        else if(v[mid]<val)
            return binarysearch(mid+1,high,val);
        else return mid;
    }
    if(abs(val-v[low])>=abs(val-v[high]))
        return high;
    else 
        return low;
}

【讨论】:

    【解决方案3】:

    安德烈的回答是正确的。只是稍微扩展一下。
    当您可以使用内置的二分搜索时,无需重新发明轮子。

    您可以通过以下方式找到索引:

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

    列表中的项目需要实现ComparableStringInteger 等简单类型已经实现了这一点。这是一个例子https://www.javatpoint.com/Comparable-interface-in-collection-framework

    根据您的用例,为了安全起见,您可能希望在二进制搜索之后执行index = Math.max(0, index)

    【讨论】:

      【解决方案4】:

      另一个使用二进制搜索的 O(log n) 易于理解的解决方案:

      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));
          }
      }
      

      【讨论】:

        【解决方案5】:

        突然想到,如果你需要在一个排序列表中找到所有最接近的值,你可以找到 a 最接近的值,然后找到距离相同距离的所有值目标。在这里,我使用了 3 次二进制搜索:

        • 首先找到一个最接近的值
        • 第二次找到最左边最接近的值
        • 第三个找到最接近的值

        在 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)
        

        【讨论】:

          【解决方案6】:

          考虑使用NavigableSet,尤其是higherlower

          【讨论】:

          • 有例子吗?
          【解决方案7】:

          似乎最简单的方法就是遍历排序列表,检查每个项目。

          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);
          

          这会输出列表中小于或等于target 的最大项目。

          【讨论】:

          • 感谢您的建议,但我忘了提一下,我想(我必须)只使用 arraylist、它的方法,当然还有其他帮助方法(我为示例)
          • 它是一个排序列表。这效率不高
          【解决方案8】:

          你需要Array.binarySearchdocs

          返回:搜索键的索引,如果它包含在数组中; 否则,(-(插入点)- 1)。插入点定义为 将键插入数组的点:索引 大于键的第一个元素的长度,如果所有元素都为 a.length 数组中的值小于指定的键。

          【讨论】:

          • 是的,谢谢你的建议,但我想是否有办法不使用类似的东西。例如,我尝试修改 binarysearch 方法..但到目前为止我没有运气
          • 另外,请注意Array.binarySearch 在目标数组中有重复项时不稳定。
          猜你喜欢
          • 2015-08-03
          • 1970-01-01
          • 2021-11-20
          • 2017-12-06
          • 2018-08-12
          • 2017-06-25
          • 2020-02-13
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多