文章目录
关于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::string
,find 函数找到字符或子串时,返回的是该字符或子串在 std::string 中的位置(索引) -
对于顺序容器(如
std::vector, std::list
等),它们没有成员函数 find。但你可以使用非成员函数 std::find 来在这些容器中查找元素。std::find 找到元素时返回的是一个迭代器,该迭代器指向找到的元素。
std::find与容器find成员函数的区别
std::find
和 std::set
或 std::map
中的 find
方法有几个重要的区别:
- 应用对象:
std::find
是一个全局函数,可以应用于所有的序列容器,如std::vector
,std::list
,std::array
,std::deque
等。它是<algorithm>
头文件中的一部分。而std::set
或std::map
的find
是成员函数,只能用于这些关联容器。 - 性能:
std::find
的时间复杂度为 O(n),它通过遍历容器中的每个元素进行查找。而对于std::set
或std::map
,这些容器内部通常使用平衡二叉树(例如红黑树)或哈希表来存储数据,所以find
方法的时间复杂度为 O(log n) 或 O(1),查找效率更高。 - 使用方式:
std::find
需要两个迭代器作为查找范围,如std::find(vec.begin(), vec.end(), value)
;而std::set
或std::map
的find
只需要查找的键值,如set.find(key)
。 - 返回值:
std::find
返回一个指向找到元素的迭代器,如果没找到则返回结束迭代器(如vec.end()
)。std::set
或std::map
的find
返回一个指向找到的元素的迭代器,如果没找到则返回结束迭代器(如set.end()
)。对于std::map
,返回的迭代器指向一个键值对。
如果容器是 std::set
或 std::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::find
或std::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::find
或 std::map::find
来在关联容器中查找元素。std::set::find
或 std::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。