【问题标题】:How to remove elements from arraylist in linear time如何在线性时间内从arraylist中删除元素
【发布时间】:2023-03-24 21:45:01
【问题描述】:

很好的建议,但其中一些是不允许的(作为流),是非常有限的任务。我一直在寻找 Leo 的算法。

我正在尝试比较 arraylist 元素上的特定字母,并且需要从 arraylist 中删除每个较大的字母。这必须在线性时间内完成,因此remove() 不是一个选项。我该怎么做?

int deleted = 0;
int n = 0;

while (n < A.size()) {
    if (A.get(n).compareTo(x) > 0) {
        //removing here
        removed = removed + 1;
    }
    n++;
}

return removed;

A 是一个带有随机字母的数组列表,x 也是一个随机字母。我需要从A 中删除每个大于给定x 字母的元素。 Remove() 不是一个选项,因为我需要在线性时间内执行此操作,而不是 n^2。

【问题讨论】:

  • 列表排序了吗?
  • @WillemVanOnsem 不,它没有排序,我无法排序。
  • 请添加一个真实的minimal reproducible example,并附上示例数据,我们可以理想地运行这些数据来重现您打算解决的问题。
  • @Dici 这是作业,我坚持使用arraylist
  • @k5_:ArrayList 具有动态大小,摊销成本使得添加 n 个元素是线性的(参见cs.stackexchange.com/questions/63752/…)因此,只需添加元素。

标签: java arraylist time-complexity


【解决方案1】:

您可以在线性时间内将元素添加到另一个列表。例如:

ArrayList<Integer> result = new ArrayList<Integer>();
for(int n = 0; n < A.size(); n++) {
    if(A.get(n).compareTo(x) <= 0) {
        result.add(A.get(n));
    }
}
return result;

或@Dici 所说的流:

A.stream().filter(n -> n.compareTo(x) <= 0).collect(Collectors.toList());

您可以稍后交换列表,或清除原始列表,然后将 result 中的值复制回该列表中,这也需要线性时间。

虽然使用另一种数据结构来存储数据也可能是有益的。

【讨论】:

  • 你也可以使用流的解决方案,它更整洁!此外,据我所知,没有任何版本的 Java 支持原始泛型,尽管他们最终希望这样做。
【解决方案2】:

我们可以使用SortedSet 来获取元素少于给定字符串的集合,这可以通过使用SortedSet.headSet(String key) 方法来实现:

List<String> list = new ArrayList<>();
list.add("d");
list.add("l");
list.add("e");
list.add("z");
list.add("x");

SortedSet<String> set = new TreeSet<>(list);
String x = "f"; //string to compare
List<String> elementsLessThanX = new ArrayList<>(set.headSet("f")); 
System.out.println(elementsLessThanX);

输出:

[d, e]

这绝对不是恒定时间,但比 O(n^2) 好。此实现不会修改原始列表。

【讨论】:

    【解决方案3】:

    也许这就是你需要的?

    ArrayList<String> b = a.stream().filter(l -> l.compareTo(x) <= 0)
                                    .collect(Collectors.toCollection(ArrayList::new));
    

    【讨论】:

    • 它没有做正确的事情,OP想要删除所有大于给定字符的元素,而不是那些与它不同的元素
    【解决方案4】:

    最便宜的解决方案是遍历列表一次,同时增加一个表示匹配条件的元素数量的索引。每次找到一个元素时,都会将其设置在该索引处,并且该索引会递增。最后,您只需删除该索引右侧的所有内容。这样做很便宜,因为在数组列表末尾删除是常数时间。

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(Arrays.asList(1, 3, 6, 4, 7, 0, 2));
        filterLargerElementsInPlace(list, 4, Comparator.naturalOrder());
        System.out.println(list); // [1, 3, 0, 2]
    }
    
    public static <T> void filterLargerElementsInPlace(List<T> list, T max, Comparator<T> cmp) {
        int i = 0;
        for (T elem : list) {
            if (cmp.compare(elem, max) < 0) {
                // mutating the list as we traverse it, but it's safe as i is always less than the current index
                list.set(i++, elem);
            }
        }
        while (list.size() > i) list.remove(list.size() - 1);
    }
    

    【讨论】:

      【解决方案5】:

      如果您想在线性时间内从列表本身中删除元素而不是创建新列表,那么您可以这样做。

      int i = 0;
      int j = 0;
      
      while (j < A.size()) {
          if (A.get(j).compareTo(x) > 0) {
              j++;
          } else if (i < j) {
              A.set(i++, A.get(j++));
          } else {
              i++;
              j++;
          }
      }
      int oldSize = A.size();
      int newSize = oldSize - j + i;
      A.subList(newSize, oldSize).clear();
      

      这基本上在列表中运行一次,向下移动元素以覆盖需要过滤掉的元素。然后在最后三行中将列表缩小到大小。

      【讨论】:

      • 如果您有兴趣,请查看我的实现。这是相同的想法,但可能更整洁。
      • @Dici 您的实现并不总是有效,因为如果您在使用Iterator 运行它们时修改某些列表(就像您的 for-each 循环一样),它们会引发异常。跨度>
      • 我在一个数组列表上测试过,听起来不错。最坏的情况也可以使用 for i ... 循环来完成,我认为没有任何列表会抱怨这一点。公平地说,这个实现只适用于随机访问列表,我并没有试图让它对任何列表都有效。但在一般情况下,你可能是对的。您是否知道任何不喜欢这样的标准列表实现?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-24
      • 2018-11-05
      • 2018-07-19
      相关资源
      最近更新 更多