std::find操作与stl中find成员函数的区别

关于C++的容器,一般可以分为序列容器(如vector, list, deque, array, forward_list, string)和关联容器(如set, multiset, map, multimap, unordered_set, unordered_multiset, unordered_map, unordered_multimap)。

一部分容器带有成员函数find,另一部分容器不具有成员函数find,但可以使用std::find进行查找

带有成员函数find的容器

使用成员函数find的容器:通常出现在关联容器无序关联容器中,这些容器是按键值进行组织的,因此它们提供了一个高效的find成员函数来快速查找特定的键值

例如:set, multiset, map, multimap, unordered_set, unordered_multiset, unordered_map, unordered_multimap,以及特殊的string。例如:

  • 关联容器
std::set<int> s = {1, 2, 3};
auto it = s.find(2);
  • string类
std::string str = "Hello, World!";
auto pos = str.find('W');

返回值

  • 关联容器的 find:

对于关联容器(如 std::set, std::map 等),find 成员函数会返回一个迭代器,指向找到的元素如果没有找到元素,则返回 end() 迭代器。比如在以下的例子中:

std::set<int> s = {1, 2, 3};
auto it = s.find(2);

s.find(2) 会返回一个迭代器,指向集合中的数字 2。可以通过 *it 来获取该元素。如果查找的元素不存在,如 s.find(4),则返回的迭代器等于 s.end()

  • string 的 find:

对于 std::string 的 find 成员函数,它会返回找到的子串(或字符)在字符串中的起始位置。如果没找到,返回 std::string::npos。比如在以下的例子中:

std::string str = "Hello, World!";
auto pos = str.find('W');

str.find('W')返回数字 6,因为 ‘W’ 是字符串 “Hello, World!” 的第 6个字符(索引从 0 开始)。如果查找的字符不存在,比如 str.find('z'),则返回的值等于 std::string::npos

不具有成员函数find但可以使用std::find的容器

这主要是序列容器,如vector, list, deque, array, forward_list。这些容器中的元素是按照它们的插入顺序进行组织的,因此没有提供一个高效的find成员函数

但是,可以使用泛型算法std::find来在这些容器中查找元素。例如:

std::vector<int> v = {1, 2, 3};
auto it = std::find(v.begin(), v.end(), 2);
std::list<int> l = {1, 2, 3};
auto it = std::find(l.begin(), l.end(), 2);

返回值

std::find 是一个模板函数,用于在给定的范围内查找元素。它返回一个迭代器,指向找到的元素。如果没有找到元素,则返回 end 迭代器。例如:

std::vector<int> v = {1, 2, 3};
auto it = std::find(v.begin(), v.end(), 2);

std::find(v.begin(), v.end(), 2)返回一个迭代器,指向向量中的数字 2。可以通过 *it 来获取该元素。如果查找的元素不存在,如 std::find(v.begin(), v.end(), 4),则返回的迭代器等于 v.end()

用法总结

  • 几乎所有的C++容器都可以使用std::find进行查找,但是只有关联容器和string提供了成员函数find

  • find函数都是查找到对应元素并返回指向该元素的迭代器/元素本身索引值如果查找不到,除了string之外的容器都是返回s.end(),但是string是返回string::npos.

  • 对于关联容器(如 std::set, std::map 等),find 函数找到元素时返回的是一个迭代器,该迭代器指向找到的元素,而不是索引。关联容器中元素的顺序并不是按照它们在容器中的插入顺序,而是按照元素的值来确定。因此,对于关联容器来说,"索引"的概念并不适用

  • 对于 std::stringfind 函数找到字符或子串时,返回的是该字符或子串在 std::string 中的位置(索引)

  • 对于顺序容器(如 std::vector, std::list 等),它们没有成员函数 find。但你可以使用非成员函数 std::find 来在这些容器中查找元素。std::find 找到元素时返回的是一个迭代器,该迭代器指向找到的元素

std::find与容器find成员函数的区别

std::findstd::setstd::map 中的 find 方法有几个重要的区别:

  1. 应用对象:std::find 是一个全局函数,可以应用于所有的序列容器,如 std::vectorstd::liststd::arraystd::deque 等。它是 <algorithm> 头文件中的一部分。std::setstd::mapfind 是成员函数,只能用于这些关联容器
  2. 性能:std::find 的时间复杂度为 O(n),它通过遍历容器中的每个元素进行查找。而对于 std::setstd::map,这些容器内部通常使用平衡二叉树(例如红黑树)或哈希表来存储数据,所以 find 方法的时间复杂度为 O(log n) 或 O(1),查找效率更高
  3. 使用方式:std::find 需要两个迭代器作为查找范围,如 std::find(vec.begin(), vec.end(), value);而 std::setstd::mapfind 只需要查找的键值,如 set.find(key)
  4. 返回值:std::find 返回一个指向找到元素的迭代器,如果没找到则返回结束迭代器(如 vec.end())。std::setstd::mapfind 返回一个指向找到的元素的迭代器,如果没找到则返回结束迭代器(如 set.end())。对于 std::map,返回的迭代器指向一个键值对

如果容器是 std::setstd::map,那么使用它们的 find 方法通常更高效。如果容器是一个序列容器,如 std::vector,那么应该使用 std::find

std::find示例:

  • std::find 的使用:
#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    int value = 3;

    auto it = std::find(vec.begin(), vec.end(), value);

    if(it != vec.end())
        std::cout << "Value found: " << *it << std::endl;
    else
        std::cout << "Value not found" << std::endl;

    return 0;
}

在上述代码中,我们在一个 std::vector 中查找值为 3 的元素,如果找到,我们打印出该元素。

容器内成员函数find示例:

  • std::set::findstd::map::find 的使用:
#include <set>
#include <iostream>

int main() {
    std::set<int> myset = {1, 2, 3, 4, 5};
    int key = 3;

    auto it = myset.find(key);//set/map容器可以直接调用find,也不需要传入多个参数

    if(it != myset.end())
        std::cout << "Key found: " << *it << std::endl;
    else
        std::cout << "Key not found" << std::endl;

    return 0;
}

在上述代码中,我们在一个 std::set 中查找键值为 3 的元素,如果找到,我们打印出该键值。

我们可以使用 std::find 来在任何序列容器(如 std::vector)中查找元素,可以使用 std::set::findstd::map::find 来在关联容器中查找元素std::set::findstd::map::find查找效率更高,因为它们利用了容器内部的数据结构(通常为平衡二叉树或哈希表)。

应用示例1:

  • leetcode 6915.合并后数组的最大元素
//错误写法,vector 没有 find 方法
class Solution {
public:
    long long maxArrayValue(vector<int>& nums) {
        //std::sort(nums.begin(), nums.end());
        for (int i = nums.size() - 1; i > 0; --i) {
            if (nums[i] <= nums[i+1] ) {
                nums[i+1] = nums[i] + nums[i+1];
                //这里用erase实际上是没必要的,直接用nums[i+1]取代即可,根本不需要erase
                nums.erase(nums.find(nums[i]));
            }
        }
        return *max_element(nums.begin(), nums.end());
    }
};

在上述代码中,vector 没有 find 方法,因此在调用 nums.find(nums[i]) 的时候会出现编译错误。在删除 vector 中的元素时,应当使用迭代器。此外,当修改 vector 的大小(例如通过调用 erase)时,需要特别注意不能超出其边界。

如果一定要在vector里面用find,可以这么写:

#include <vector>
#include <algorithm>

class Solution {
public:
    long long maxArrayValue(std::vector<int>& nums) {
        for (int i = nums.size() - 1; i > 0; --i) {
            if (nums[i] <= nums[i-1] ) {
                nums[i-1] = nums[i] + nums[i-1];
                //先用迭代器,存储指向nums[i]位置的迭代器
                auto it = std::find(nums.begin(), nums.end(), nums[i]);
                //如果找到了,在erase中传入迭代器
                if(it != nums.end())
                    nums.erase(it);
            }
        }
        return *max_element(nums.begin(), nums.end());
    }
};

但是,值得注意的是,如果 nums[i] 的值在 vector 中不唯一,std::find 会返回第一个匹配项的迭代器,这可能不是我们想删除的元素。这种情况下,建议直接使用 nums.erase(nums.begin() + i); 来删除特定索引的元素

关于erase的补充

在vector中用erase(),推荐用下面的写法:(find找到与nums[i]相等的第一个元素就会返回,不一定是nums[i]本身)

  • erase传入的是迭代器,所以一定要nums.begin()+i
#include <vector>
#include <algorithm>

class Solution {
public:
    long long maxArrayValue(std::vector<int>& nums) {
        for (int i = nums.size() - 1; i > 0; --i) {
            if (nums[i] <= nums[i-1] ) {
                nums[i-1] = nums[i] + nums[i-1];
                //erase的用法
                nums.erase(nums.begin() + i);
            }
        }
        return *max_element(nums.begin(), nums.end());
    }
};

为了删除当前元素,我们使用了 nums.begin() + i 来创建一个指向当前元素的迭代器,然后将其传递给 erase 方法。最后,我们使用 max_element 函数找出数组中的最大元素并返回它。

注意,使用 erase 方法会使 vector 中后面的所有元素向前移动,这会消耗 O(n) 的时间(其中 n 是被删除元素后面的元素数量)。因此,该解决方案的时间复杂度可能达到 O(n^2)。在处理大数组时,这可能会导致性能问题。为了避免这个问题,你可能需要采用不同的策略,例如使用一个额外的数据结构来跟踪哪些元素被删除。

应用示例2:

leetcode 2800.包含三个字符的最短字符串(复用思路与三元问题思想)

复用逻辑

复用部分的代码:

    // 生成一个既包含 a 又包含 b 的字符串
    string combine2(string a, string b) {
        // 从使用 b 的所有字符开始,逐步减少使用的字符数量
        for (int i = 0; i <= b.size(); ++i) {
            // 拼接 a 和 b 的后缀,生成一个新的字符串
            string t = a + b.substr(b.size() - i);
            // 如果这个新的字符串包含 b,则它一定也包含 a,所以返回这个字符串
            // 如果 a 和 b 完全不重叠,则返回 a 和 b 的拼接
            if (t.find(b) != string::npos) return t;  
        }
        //随意返回一个数值即可
        return "";  
    }

这段复用逻辑,实际上是在判断,从不包含b任何字符,到包含b所有字符这个范围内,b开头的元素能不能和a末尾的元素实现复用。一旦能够复用(在倒着拼接的新字符串t里出现了完整的b),那么就可以直接返回t。

例子:a=“abbc” b=“bb”,那么最开始string t = a + b.substr(b.size() - i);,也就是t=a+b.substr(2),实际上此时b.substr(2)是空的,也就是最开始是在判断a里面是不是本来就包含了b

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值