【问题标题】:Find 2 closest previous values and 2 closest next values in a sorted array list在排序数组列表中查找 2 个最接近的先前值和 2 个最接近的下一个值
【发布时间】:2018-08-12 11:13:13
【问题描述】:

这是一种修改后的二分搜索,它从排序数组列表中返回最接近给定值的元素。我该如何调整它以便它可以返回 2 个最近的前一个元素和 2 个最近的下一个元素?

private static Long search(long value, ArrayList<Long> a) {

    if(value < a.get(0)) {
        return a.get(0);
    }
    if(value > a.get(a.size()-1)) {
        return a.get(a.size()-1);
    }

    int lo = 0;
    int hi = a.size() - 1;

    while (lo <= hi) {
        int mid = (hi + lo) / 2;
        if (value < a.get(mid)) {
            hi = mid - 1;
        } else if (value > a.get(mid)) {
            lo = mid + 1;
        } else {
            return a.get(mid);
        }
    }
    return (a.get(lo) - value) < (value - a.get(hi)) ? a.get(lo) : a.get(hi);
}

例如,假设我的数组列表由以下元素组成:

[101、201、301、401、501、601、701、801、901、1001]

我得到了 730 的值。我希望搜索返回一个由 4 个元素组成的数组,其中 601、701 作为前一个值,801 和 901 作为下一个值。

【问题讨论】:

  • 总是有两个前一个值和两个后一个值吗?如果value 已经在List 中,输出应该是什么?
  • 我在下面写了一个答案 - 如果有帮助,请告诉我。
  • 对于我的实际数据集来说,已经在列表中的值真的很少见。但是,如果发生这种情况,它应该忽略该值并返回 2 个前一个值和 2 个下一个值。对于我的示例,如果给定值 501,我希望列表返回 [301, 401, 601, 701]。

标签: java search arraylist binary-search


【解决方案1】:

根据lohimid 值之间的差异,此代码使用一种非常简单的方法将元素添加到结果列表中。

在边缘情况下,返回的元素数量可以小于 4。例如,如果左侧或右侧都没有元素(由于列表边界),则返回的列表大小可以是 2 或 3取决于最接近的值的位置。

如果这是你想要的,那么这里是代码:

private static List<Long> search(long value, List<Long> a) {
    if (a.size() < 3) return new ArrayList<>(a);
    List<Long> result = new ArrayList<>();
    if (value < a.get(0)) {
        result.add(a.get(0));
        result.add(a.get(1));
        return result;
    }
    if (value > a.get(a.size() - 1)) {
        result.add(a.get(a.size() - 2));
        result.add(a.get(a.size() - 1));
        return result;
    }

    int lo = 0;
    int hi = a.size() - 1;
    int match = -1;

    while (lo <= hi) {
        int mid = (hi + lo) / 2;
        if (value < a.get(mid)) {
            hi = mid - 1;
        } else if (value > a.get(mid)) {
            lo = mid + 1;
        } else {
            match = mid;
            break;
        }
    }

    if (match >= 0) {
        if (match > 1) result.add(a.get(match - 2));
        if (match > 0) result.add(a.get(match - 1));
        if (match < a.size() - 1) result.add(a.get(match + 1));
        if (match < a.size() - 2) result.add(a.get(match + 2));
    } else if (a.get(lo) < value) {
        result.add(a.get(hi));
        result.add(a.get(lo));
        if (lo < a.size() - 1) result.add(a.get(lo + 1));
        if (lo < a.size() - 2) result.add(a.get(lo + 2));
    } else if (a.get(hi) > value) {
        if (hi > 1) result.add(a.get(hi - 2));
        if (hi > 0) result.add(a.get(hi - 1));
        result.add(a.get(hi));
        result.add(a.get(lo));
    } else {
        if (hi > 0) result.add(a.get(hi - 1));
        result.add(a.get(hi));
        result.add(a.get(lo));
        if (lo < a.size() - 1) result.add(a.get(lo + 1));
    }

    return result;
}

【讨论】:

  • @thelaw 根据您的评论编辑以忽略完全匹配
  • 谢谢!这正是我所需要的。
  • @thelaw 我很高兴能帮上忙!
【解决方案2】:

有一种简单的方法可以扩展您的原始代码以适应返回一个包含四个值的列表,而且相对容易:

    private static List<Long> search(long value, ArrayList<Long> a) {

    List<Long> returnList = new ArrayList<>();
    if (null == a) {
        return null;
    }
    if (a.isEmpty()) {
        return a;
    }
    if (a.size()==1) {
        return a;
    }
    if(value < a.get(0)) {
        returnList.add(a.get(0));
        returnList.add(a.get(1));
        return returnList;
    }
    if(value > a.get(a.size()-1)) {
        returnList.add(a.get(a.size()-1));
        returnList.add(a.get(a.size()-2));
        return returnList;
    }

    int lo = 0;
    int hi = a.size() - 1;

    while (lo <= hi) {
        int mid = (hi + lo) / 2;
        if (value < a.get(mid)) {
            hi = mid - 1;
        } else if (value > a.get(mid)) {
            lo = mid + 1;
        } else {
            if (mid>1) returnList.add(a.get(mid-2));
            if (mid>0) returnList.add(a.get(mid-1));
            if (mid<=a.size()-1) returnList.add(a.get(mid+1));
            if (mid<=a.size()-2) returnList.add(a.get(mid+2));
            return returnList;
        }
    }
    if ((a.get(lo) - value) < (value - a.get(hi))) {
        if (lo > 0) returnList.add(a.get(lo-1));
        returnList.add(a.get(lo));
        if (lo<=a.size()-1) returnList.add(a.get(lo+1));
        if (lo<=a.size()-2) returnList.add(a.get(lo+2)); 
    } else {
        if (lo > 1) returnList.add(a.get(hi-2));
        if (lo > 0) returnList.add(a.get(hi-1));
         ReturnList.add(a.get(hi));
        if (hi<=a.size()-1) returnList.add(a.get(hi+1));
    }
    return returnList;
}

但是,如果我们要扩展代码,可能是进行一些小的重构以使其更具可读性的最佳时机。

    private static List<Long> addNumbersAroundIndex (int index, Long searchedNum, int numOfElements, List<Long> list) {
    List<Long> returnList = new ArrayList<Long>();
    int before = numOfElements/2;
    int after = numOfElements - before;

    if (list.get(index)>searchedNum) {
        after --;

    } else if(list.get(index)<searchedNum) {

        before--;
    }
    for (int i=before; i>0; i--) {

        if (index>i+1) returnList.add(list.get(index-i));
    }
    if (!list.get(index).equals(searchedNum)) {
        returnList.add(list.get(index));
    }
    for (int i=1; i< after+1; i++) {

        if (index+i<=list.size()-1) returnList.add(list.get(index+i));
    }
    return returnList;
}

private static List<Long> search(long value, int numOfElements, ArrayList<Long> a) {

    if (null == a) {
        return null;
    }
    if (a.isEmpty()) {
        return a;
    }
    if (a.size()==1) {
        return a;
    }
    if(value < a.get(0)) {
        return addNumbersAroundIndex(0, value, numOfElements,a);
    }
    if(value > a.get(a.size()-1)) {
        return addNumbersAroundIndex(a.size()-1, value,numOfElements,a);
    }

    int lo = 0;
    int hi = a.size() - 1;

    while (lo <= hi) {
        int mid = (hi + lo) / 2;
        if (value < a.get(mid)) {
            hi = mid - 1;
        } else if (value > a.get(mid)) {
            lo = mid + 1;
        } else {
            return addNumbersAroundIndex(mid,value,numOfElements,a);
        }
    }
    if ((a.get(lo) - value) < (value - a.get(hi))) {
        return addNumbersAroundIndex(lo,value,numOfElements,a);
    } else {
        return addNumbersAroundIndex(hi,value,numOfElements,a);
    }
}

用你的一些原始列表对其进行测试:

public static void main(String[] args) {
    ArrayList<Long> list = new ArrayList(Arrays.asList(101l, 201l, 301l, 401l, 501l, 601l, 701l, 801l, 901l, 1001l));
    System.out.println(search(1020l,5,list));

}

[801, 901, 1001]

System.out.println(search(730l,4,list));

[601, 701, 801, 901]

System.out.println(search(601l,5,list));

[401, 501, 701, 801, 901]

【讨论】:

  • 非常感谢您的回答。这真的很有帮助。但是,也许我没有在我的问题中明确表示我总是希望它返回 2 个先前的值和 2 个下一个值。例如,如果我在您的代码中输入 700,则列表返回 [601, 701, 801, 901]。我希望它返回 [501, 601, 701, 801]。 501 和 601 是前一个值,而 701 和 801 是下一个值。如果值已经在列表中,请检查我的问题中的 cmets。
  • @thelaw 我已将其修复为您的规格。它可以按您的预期工作。
猜你喜欢
  • 2015-07-26
  • 2021-02-16
  • 2019-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-01
  • 1970-01-01
相关资源
最近更新 更多