使用 STL 算法查找集合中的前两个不相邻元素

Find First Two Non-Adjacent Elements in a set Using an STL Algorithm

本文关键字:两个 元素 查找 算法 STL 集合 使用      更新时间:2023-10-16

所以我真的很挣扎,即使现在我对我的解决方案也不满意。

我有一个至少包含 0 的set,并且可能包含其他正int s。我需要找到不在set中的第一个正数.

因此,编写一个标准的while循环来完成此操作很容易。

i = foo.begin();
while (i != prev(foo.end()) && *i + 1 == *next(i)){
    ++i;
}
cout << "First number that cannot be formed " << *i + 1 << endl;

但是当我尝试编写循环的 STL 算法版本时,我得到一些失败的东西,如下所示:

auto i = foo.begin();
while (++i != prev(foo.end()) && *prev(i) + 1 == *i);
cout << "First number that cannot be formed " << *prev(i) + 1 << endl;

在以下情况下,这两个循环都正确产生 3

set<int> foo{0, 1, 2, 4};

但在这种情况下,第二个循环错误地产生 3 而不是 4

set<int> foo{0, 1, 2, 3};

如何使用 STL 算法编写此内容并完成第一个循环的行为?

编辑:

看到一些答案后,我想增加难度。我真正想要的是不需要临时变量并且可以放在cout语句中的东西。

循环的问题在于你过早地停止了一个元素。这有效:

while (++i != foo.end() && *prev(i) + 1 == *i);

与第一个循环的区别在于条件*prev(i) + 1 == *i)而不是*i + 1 == *next(i);你检查的范围必须相应地移动。

您也可以使用 std::adjacent_find

auto i = std::adjacent_find(begin(s), end(s), [](int i, int j) { return i + 1 != j; });
if(i == s.end()) {
  std::cout << *s.rbegin() + 1 << std::endl;
} else {
  std::cout << *i + 1 << std::endl;
}

对编辑的回应:一种使其漂亮可内联的方法是

  std::cout << std::accumulate(begin(s), end(s), 0,
                               [](int last, int x) {
                                 return last + 1 == x ? x : last;
                               }) + 1 << 'n';

。但这效率较低,因为它在发现间隙时不会短路。另一种短路方法是

std::cout << *std::mismatch(begin     (s),
                            prev(end  (s)),
                            next(begin(s)),
                            [](int lhs, int rhs) { 
                              return lhs + 1 == rhs;
                            }).first + 1 << 'n';

你试过adjacent_find吗?

#include <algorithm>
#include <iostream>
#include <set>
int main()
{
    std::set<int> foo{0, 1, 2, 4};
    auto it = std::adjacent_find(begin(foo), end(foo),
         [](auto e1, auto e2){ return (e2 - e1) > 1; });
    // precondition: foo is not empty
    if (it == end(foo)) --it;
    std::cout << *it+1;
}

编辑:好的,如果你认为Boost标准足够,你可以这样做,这真是太好了:

#include <algorithm>
#include <boost/iterator/counting_iterator.hpp>
#include <set>
#include <iostream>
int main()
{
    std::set<int> foo{0, 1, 2, 4};
    auto it =
        std::mismatch(
           begin(foo), end(foo),
           boost::counting_iterator<int>(*begin(foo))
        );
    std::cout << *it.second;
}

活生生的例子。

编辑2:我在阅读另一个问题时想到的另一个问题:

int i = 0;
std::find_if(begin(foo), end(foo),
    [&i](int e){ return e != i++; });
std::cout << i;

这只是另一种方式 以前与mismatch

.

你遇到了一个边缘案例。一旦 i == 位置位于集合末尾,您的 while 循环就会失败。在本例中,它以 i == 3 结束。你需要让我越过你的数组的边界才能使它工作。

您可以通过将第 2 行更改为 :

while (++i **<**= prev(foo.end()) && *prev(i) + 1 == *i);

通过使其<=,一旦超过foo末尾的值,我将失败。

以下是需要考虑的其他一些事项:1) 不保证集合可以排序。2) 在设置 foo(0, 1, 1) 的情况下会发生什么;即重复,失败的那个是正确的,但它也是集合末尾的那个?

您需要一个稍微复杂的算法来捕获所有这些情况。