基于OpenCV C++的形状匹配技术:从Hu矩原理到工业视觉检测实战
2026/7/27 12:53:32 网站建设 项目流程

1. 项目概述:从零构建一个工业级形状匹配引擎

最近在做一个工业视觉检测的项目,核心需求是从一堆杂乱的零件中,精准定位出特定型号的工件。这听起来像是模板匹配的经典场景,但实际做起来,你会发现OpenCV自带的matchTemplate在应对旋转、缩放和部分遮挡时,常常力不从心。于是,我把目光投向了更鲁棒的形状匹配(Shape Matching)技术。网上能找到的代码要么是Python版本的,要么是OpenCV老版本的C++接口,在VS2022和OpenCV 4.5.2这个当下主流的开发环境下,总有些水土不服。所以,我决定自己动手,基于OpenCV的cv::matchShapes和轮廓处理,从头搭建一个C++的形状匹配模块,并把它封装成清晰、可复用的源码。这篇文章,就是这次“踩坑”与“填坑”的全过程记录,适合所有需要在C++环境下实现高精度、抗干扰物体定位的开发者,无论是做自动化、质检还是机器人抓取,相信都能从中找到可以直接“抄作业”的解决方案。

2. 核心原理与方案选型:为什么是轮廓匹配?

在开始敲代码之前,搞清楚“用什么”以及“为什么用”至关重要。形状匹配的核心思想是量化两个形状之间的差异度。OpenCV提供了几种方法,但最常用、最直接的就是基于Hu矩(Hu Moments)的cv::matchShapes函数。

2.1 Hu矩:形状的“数字指纹”

你可以把Hu矩理解为形状的“数字指纹”。它是一种对图像轮廓进行数学描述的特征,具有平移、缩放和旋转不变性。这正是我们梦寐以求的特性:无论目标物体在图像中移动了位置、改变了大小,或者旋转了一个角度,它的Hu矩特征基本保持不变。

OpenCV通过cv::HuMoments函数计算轮廓的七个Hu矩(h1到h7)。cv::matchShapes函数则通过计算两个轮廓Hu矩之间的某种距离(比如对数差),来得到一个匹配分数。分数越接近0,表示两个形状越相似。

2.2 为何不直接用matchTemplate

很多新手会首先想到cv::matchTemplate。它确实简单快速,但它进行的是像素级的灰度值匹配。这意味着:

  1. 对光照极度敏感:光线稍暗或反光,匹配效果就急剧下降。
  2. 无法处理旋转和缩放:模板图必须和搜索图中的目标朝向、大小完全一致。
  3. 计算量大:虽然可以用TM_CCOEFF_NORMED等方法,但在大图中搜索多个尺度和旋转角度时,计算量呈指数增长。

而基于轮廓的形状匹配,只关心物体的边缘形状信息,对纹理、光照变化不敏感,并且天然具备几何不变性(通过Hu矩)。对于工业场景中常见的黑色背景上的亮色工件,或者通过阈值化、边缘提取能稳定获取轮廓的物体,形状匹配是更优的选择。

2.3 我们的技术方案栈

基于以上分析,我确定了本次实现的技术栈:

  • 开发环境:Visual Studio 2022。选择它是因为其对C++标准支持好,调试功能强大,社区资源丰富。
  • 编译工具:MSVC (Microsoft Visual C++) 编译器。
  • 核心库:OpenCV 4.5.2。这是一个长期支持版本,稳定且功能齐全。我们主要使用其imgproc(图像处理)和highgui(高层GUI)模块。
  • 核心方法cv::findContours(查找轮廓) ->cv::drawContours(绘制轮廓) ->cv::matchShapes(形状匹配)。
  • 辅助策略:多尺度与多旋转角度搜索。这是提升实用性的关键,我们将通过图像金字塔和仿射变换来实现。

注意:OpenCV从3.x版本开始,许多函数(如findContours)的返回值格式发生了变化。网上很多老教程的代码在4.5.2上直接编译会报错,这也是促使我重写一份“干净”源码的原因之一。

3. 环境搭建与项目配置

工欲善其事,必先利其器。一个正确的环境配置能避免后续90%的诡异编译错误。

3.1 OpenCV 4.5.2的安装与配置

不建议使用安装器,最好从官网下载预编译好的Windows版本(例如opencv-4.5.2-vc14_vc15.exe)。解压到一个没有中文和空格的路径,比如D:\OpenCV\opencv452

接下来是VS2022中的关键配置步骤,很多教程这里讲得不清不楚:

  1. 创建新项目:选择“控制台应用”,命名为ShapeMatchDemo
  2. 配置包含目录:在项目属性 -> C/C++ -> 常规 -> 附加包含目录中,添加OpenCV的include文件夹路径。通常是你的路径\opencv\build\include
  3. 配置库目录:在链接器 -> 常规 -> 附加库目录中,添加OpenCV的lib文件夹路径。注意,这里需要根据你的编译平台选择。对于VS2022,通常使用vc15或更高版本的库。路径类似你的路径\opencv\build\x64\vc15\lib
  4. 配置附加依赖项:这是最容易出错的一步。在链接器 -> 输入 -> 附加依赖项中,添加你需要链接的.lib文件。
    • Debug模式:通常添加opencv_world452d.lib(如果下载的是world版本,一个lib包含所有模块)或者具体的模块lib,如opencv_core452d.lib;opencv_imgproc452d.lib;opencv_highgui452d.lib
    • Release模式:添加opencv_world452.lib或对应的无d后缀的lib。

    实操心得:我强烈建议在项目属性管理器里,分别为Debug|x64Release|x64创建属性表(.props文件),一次性配置好包含目录、库目录和依赖项。这样新建任何OpenCV项目时,只需导入这个属性表,无需重复配置,一劳永逸。

  5. 配置环境变量:将OpenCV的bin目录(例如你的路径\opencv\build\x64\vc15\bin)添加到系统的Path环境变量中,并重启VS2022。这一步是为了让程序运行时能找到OpenCV的DLL文件。如果不加,编译可能成功,但运行时会弹出“找不到xxx.dll”的错误。

3.2 验证环境:你的第一个OpenCV程序

配置完成后,写一个简单的程序验证一下。

#include <opencv2/opencv.hpp> #include <iostream> int main() { // 创建一个纯黑色的图像 cv::Mat img(500, 500, CV_8UC3, cv::Scalar(0, 0, 0)); // 在图像中心画一个红色的圆 cv::circle(img, cv::Point(250, 250), 100, cv::Scalar(0, 0, 255), -1); // 显示图像 cv::imshow("Test OpenCV", img); cv::waitKey(0); // 等待任意按键 std::cout << "OpenCV environment is set up correctly!" << std::endl; return 0; }

如果能够成功编译并弹出一个显示红色圆圈的窗口,那么恭喜你,环境配置成功。

4. 形状匹配核心源码实现

现在进入核心部分。我们将把功能拆解成几个清晰的函数,方便理解和复用。

4.1 轮廓提取与预处理

形状匹配的第一步是获取高质量的目标轮廓和待检测图像的轮廓。

/** * @brief 从图像中提取主要轮廓 * @param src 输入图像(灰度图或BGR图) * @param contours 输出的轮廓向量 * @param mode 轮廓检索模式,常用 RETR_EXTERNAL(只检测最外层轮廓)或 RETR_LIST(检测所有轮廓) * @param method 轮廓近似方法,常用 CHAIN_APPROX_SIMPLE(压缩水平、垂直和对角线段,仅保留端点) * @return 是否成功提取到轮廓 */ bool extractContours(const cv::Mat& src, std::vector<std::vector<cv::Point>>& contours, int mode = cv::RETR_EXTERNAL, int method = cv::CHAIN_APPROX_SIMPLE) { if (src.empty()) { std::cerr << "Error: Source image is empty!" << std::endl; return false; } cv::Mat gray, binary; // 如果输入是彩色图,转为灰度图 if (src.channels() == 3) { cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); } else { gray = src.clone(); } // 二值化:将灰度图转为黑白图,便于提取轮廓 // 使用自适应阈值或大津法(OTSU)可以更好地应对光照不均 cv::threshold(gray, binary, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU); // 可选:进行形态学操作(如开运算)去除小噪声 cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)); cv::morphologyEx(binary, binary, cv::MORPH_OPEN, kernel); // 查找轮廓 cv::findContours(binary, contours, mode, method); // 过滤掉面积太小的轮廓(可能是噪声) double minArea = 500.0; // 根据实际图像大小调整 auto it = contours.begin(); while (it != contours.end()) { if (cv::contourArea(*it) < minArea) { it = contours.erase(it); } else { ++it; } } return !contours.empty(); }

注意事项cv::findContours函数会修改输入的二进制图像。如果你后续还需要原图,记得先使用cv::Mat binaryCopy = binary.clone();传入克隆体。另外,轮廓近似方法CHAIN_APPROX_SIMPLE能显著减少轮廓点的数量,提高后续匹配效率,除非你需要每个像素点的精确位置,否则推荐使用。

4.2 单次形状匹配

这是最基础的匹配单元,比较两个轮廓的相似度。

/** * @brief 计算两个轮廓的形状匹配分数 * @param contour1 轮廓1 * @param contour2 轮廓2 * @param method 匹配方法,见 cv::ShapeMatchModes,常用 CONTOURS_MATCH_I1, I2, I3 * @return 匹配分数(距离),值越小越相似 */ double matchTwoShapes(const std::vector<cv::Point>& contour1, const std::vector<cv::Point>& contour2, int method = cv::CONTOURS_MATCH_I1) { if (contour1.size() < 5 || contour2.size() < 5) { // Hu矩要求轮廓点不能太少 std::cerr << "Warning: Contour has too few points for Hu moments calculation." << std::endl; return std::numeric_limits<double>::max(); // 返回一个很大的值表示不匹配 } return cv::matchShapes(contour1, contour2, method, 0.0); } // cv::ShapeMatchModes 枚举值解释: // CONTOURS_MATCH_I1: 使用Hu矩的原始公式,对形状差异敏感。 // CONTOURS_MATCH_I2: 使用Hu矩的对数变换,通常效果更好,更稳定。 // CONTOURS_MATCH_I3: 另一种对数变换形式。 // 实测中,I2 在大多数情况下表现最均衡。

4.3 在场景中搜索最佳匹配

实际应用中,我们有一个模板轮廓,需要在一张复杂的场景图中找到它。

/** * @brief 在场景图中搜索与模板轮廓最匹配的位置 * @param templContour 模板轮廓 * @param sceneImage 场景图(BGR或灰度) * @param bestMatchScore 输出最佳匹配分数 * @param bestMatchContour 输出最佳匹配的轮廓 * @param roi 可选的搜索区域(ROI),cv::Rect()表示全图搜索 * @return 是否找到匹配(通常设定一个分数阈值来判断) */ bool findBestMatchInScene(const std::vector<cv::Point>& templContour, const cv::Mat& sceneImage, double& bestMatchScore, std::vector<cv::Point>& bestMatchContour, cv::Rect roi = cv::Rect()) { std::vector<std::vector<cv::Point>> sceneContours; cv::Mat searchArea; // 如果指定了ROI,则裁剪区域 if (roi.area() > 0) { searchArea = sceneImage(roi).clone(); } else { searchArea = sceneImage.clone(); } // 提取场景图中的所有候选轮廓 if (!extractContours(searchArea, sceneContours)) { return false; } bestMatchScore = std::numeric_limits<double>::max(); bestMatchContour.clear(); int bestIndex = -1; // 遍历所有场景轮廓,与模板轮廓进行匹配 for (size_t i = 0; i < sceneContours.size(); ++i) { double score = matchTwoShapes(templContour, sceneContours[i], cv::CONTOURS_MATCH_I2); // 记录最佳匹配 if (score < bestMatchScore) { bestMatchScore = score; bestIndex = static_cast<int>(i); } } if (bestIndex >= 0) { bestMatchContour = sceneContours[bestIndex]; // 如果使用了ROI,需要将轮廓坐标转换回原图坐标系 if (roi.area() > 0) { for (auto& point : bestMatchContour) { point.x += roi.x; point.y += roi.y; } } return true; } return false; }

4.4 处理旋转与缩放:仿射变换与图像金字塔

基础匹配只能处理平移。要应对旋转和缩放,我们需要扩展搜索空间。

策略一:多角度旋转模板

/** * @brief 生成模板轮廓在多个旋转角度下的副本 * @param originalContour 原始轮廓 * @param angleRange 旋转角度范围(度),如 {-30, 30} * @param angleStep 旋转步长(度),如 5 * @return 旋转后的轮廓向量 */ std::vector<std::vector<cv::Point>> generateRotatedTemplates( const std::vector<cv::Point>& originalContour, const std::pair<double, double>& angleRange, double angleStep) { std::vector<std::vector<cv::Point>> rotatedContours; // 计算轮廓的最小外接矩形中心作为旋转中心 cv::RotatedRect minRect = cv::minAreaRect(originalContour); cv::Point2f center = minRect.center; for (double angle = angleRange.first; angle <= angleRange.second; angle += angleStep) { std::vector<cv::Point> rotatedContour; cv::Mat rotationMat = cv::getRotationMatrix2D(center, angle, 1.0); // 1.0表示不缩放 // 对轮廓中的每个点应用旋转变换 cv::transform(originalContour, rotatedContour, rotationMat); rotatedContours.push_back(rotatedContour); } return rotatedContours; }

策略二:多尺度图像金字塔我们不在代码里生成缩放后的轮廓,而是对场景图进行缩放,然后在不同尺度的图像上搜索。

/** * @brief 使用图像金字塔进行多尺度形状匹配 * @param templContour 模板轮廓(在原始模板图像尺度下) * @param sceneImage 场景图 * @param scaleRange 缩放比例范围,如 {0.5, 2.0} 表示从0.5倍到2.0倍 * @param scaleStep 缩放步长,如 1.1 (每次放大10%) * @param bestScore 输出最佳分数 * @param bestContour 输出最佳轮廓(坐标在原始场景图尺度下) * @param bestScale 输出最佳匹配尺度 */ bool multiScaleShapeMatch(const std::vector<cv::Point>& templContour, const cv::Mat& sceneImage, const std::pair<double, double>& scaleRange, double scaleStep, double& bestScore, std::vector<cv::Point>& bestContour, double& bestScale) { bestScore = std::numeric_limits<double>::max(); bestContour.clear(); bestScale = 1.0; for (double scale = scaleRange.first; scale <= scaleRange.second; scale *= scaleStep) { // 1. 缩放场景图 cv::Mat scaledScene; cv::resize(sceneImage, scaledScene, cv::Size(), scale, scale, cv::INTER_LINEAR); // 2. 在缩放后的场景图中搜索 double currentScore; std::vector<cv::Point> currentContour; if (findBestMatchInScene(templContour, scaledScene, currentScore, currentContour)) { // 3. 将找到的轮廓坐标和分数转换回原始尺度进行比较 // 注意:分数本身受尺度影响较小,但轮廓坐标需要还原 if (currentScore < bestScore) { bestScore = currentScore; bestScale = scale; // 缩放轮廓坐标回原图尺度 bestContour.clear(); for (const auto& pt : currentContour) { bestContour.emplace_back(cv::Point(static_cast<int>(pt.x / scale), static_cast<int>(pt.y / scale))); } } } } return !bestContour.empty(); }

实操心得:同时搜索旋转和尺度会导致计算量爆炸(组合爆炸)。在实际项目中,如果先验知识允许,尽量固定一个维度。例如,如果工件在传送带上只有轻微旋转,可以只搜索旋转;如果相机高度固定,可以只搜索尺度。或者,可以采用由粗到精的策略:先用大的步长(如角度步长10度,尺度步长1.5)快速搜索,在找到的粗略位置附近,再用小步长进行精细搜索。

5. 完整示例与可视化

将上述模块组合起来,形成一个完整的、带可视化效果的示例程序。

#include <opencv2/opencv.hpp> #include <iostream> #include <vector> #include <limits> // 这里插入前面定义的 extractContours, matchTwoShapes, findBestMatchInScene 等函数... int main() { // 1. 加载模板图像和场景图像 cv::Mat templImage = cv::imread("template.png", cv::IMREAD_COLOR); // 模板,例如一个六角螺母 cv::Mat sceneImage = cv::imread("scene.jpg", cv::IMREAD_COLOR); // 场景,包含多个零件的图像 if (templImage.empty() || sceneImage.empty()) { std::cerr << "Could not load images!" << std::endl; return -1; } // 2. 从模板图像中提取轮廓(假设模板图中只有一个目标物体) std::vector<std::vector<cv::Point>> templContours; if (!extractContours(templImage, templContours, cv::RETR_EXTERNAL)) { std::cerr << "Failed to extract template contour!" << std::endl; return -1; } std::vector<cv::Point> targetContour = templContours[0]; // 取第一个,也是最大的轮廓 // 3. 在场景中搜索最佳匹配(基础版本,仅平移) double matchScore; std::vector<cv::Point> matchedContour; if (findBestMatchInScene(targetContour, sceneImage, matchScore, matchedContour)) { std::cout << "Best match found! Score: " << matchScore << std::endl; // 4. 可视化结果 cv::Mat resultVis = sceneImage.clone(); // 绘制找到的轮廓 cv::drawContours(resultVis, std::vector<std::vector<cv::Point>>{matchedContour}, -1, cv::Scalar(0, 255, 0), 3); // 计算并绘制最小外接矩形,更直观 cv::Rect boundingBox = cv::boundingRect(matchedContour); cv::rectangle(resultVis, boundingBox, cv::Scalar(255, 0, 0), 2); // 在矩形上方显示匹配分数 std::string scoreText = "Score: " + std::to_string(matchScore).substr(0, 6); cv::putText(resultVis, scoreText, cv::Point(boundingBox.x, boundingBox.y - 10), cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 0, 255), 2); // 显示原图和结果图 cv::imshow("Template", templImage); cv::imshow("Scene", sceneImage); cv::imshow("Matching Result", resultVis); cv::waitKey(0); // 5. 判断是否匹配成功(需要根据实际场景设定阈值) double threshold = 0.1; // Hu矩匹配分数阈值,需要大量实验确定 if (matchScore < threshold) { std::cout << "Match successful! Target located." << std::endl; } else { std::cout << "Match score too high, might not be the correct target." << std::endl; } } else { std::cout << "No match found in the scene." << std::endl; } return 0; }

6. 性能优化与高级技巧

当处理高分辨率图像或需要实时检测时,基础版本的性能可能成为瓶颈。以下是一些优化思路:

6.1 轮廓近似与点集简化

extractContours函数中,我们已经使用了CHAIN_APPROX_SIMPLE。还可以在匹配前对轮廓进行进一步简化,使用cv::approxPolyDP函数。

/** * @brief 使用Douglas-Peucker算法简化轮廓 * @param contour 输入轮廓 * @param epsilon 近似精度,值越大简化越厉害 * @return 简化后的轮廓 */ std::vector<cv::Point> simplifyContour(const std::vector<cv::Point>& contour, double epsilon) { std::vector<cv::Point> approx; cv::approxPolyDP(contour, approx, epsilon, true); return approx; } // 在匹配前调用:auto simpleContour = simplifyContour(originalContour, 1.0); // epsilon 参数需要根据轮廓周长或经验值调整,例如 epsilon = 0.01 * cv::arcLength(contour, true)

6.2 分级匹配策略

不要一开始就在全图、全尺度、全角度进行精细匹配。

  1. 粗定位:先用低分辨率图像(通过cv::pyrDown)或大的轮廓近似epsilon进行快速匹配,找到可能的目标区域(ROI)。
  2. 精匹配:只在粗定位得到的ROI内,使用原始分辨率和高精度轮廓进行最终匹配和角度/尺度微调。

6.3 利用多线程

如果确实需要搜索多个角度和尺度,可以使用C++11的std::asyncstd::thread将不同角度/尺度的匹配任务并行化。

#include <future> #include <vector> std::vector<std::future<double>> futures; std::vector<double> scores(rotatedTemplates.size()); for (size_t i = 0; i < rotatedTemplates.size(); ++i) { futures.push_back(std::async(std::launch::async, [&, i]() { return matchTwoShapes(targetContour, rotatedTemplates[i], cv::CONTOURS_MATCH_I2); })); } // ... 收集结果

6.4 结合其他特征

形状匹配对轮廓完整性要求高。如果目标容易被部分遮挡,可以结合其他特征:

  • 颜色特征:在匹配区域内统计颜色直方图,与模板颜色分布进行对比。
  • 关键点特征:在轮廓内部使用ORB、SIFT等特征点进行辅助匹配(OpenCV 4.5.2的xfeatures2d模块需要单独编译,SIFT/SURF已移至主仓库但部分专利算法需注意使用许可)。

7. 常见问题排查与调试技巧

在实际开发中,你肯定会遇到各种问题。下面是我总结的一些常见“坑”和解决方法。

7.1 匹配分数始终很高或匹配失败

  • 问题cv::matchShapes返回的分数总是大于1,或者根本无法找到轮廓。
  • 排查步骤
    1. 可视化轮廓:在extractContours函数后,立即用cv::drawContours将找到的轮廓画出来,看看是否成功提取到了你期望的目标轮廓。很可能阈值化参数不对,导致轮廓断裂或包含大量噪声。
    2. 检查二值化:尝试不同的阈值化方法,如自适应阈值cv::adaptiveThreshold,或者先进行高斯模糊cv::GaussianBlur再阈值化,以获得更干净的边缘。
    3. 检查轮廓过滤:调整minArea参数。如果设得太大,可能把目标滤掉了;如果设得太小,会留下太多噪声轮廓,干扰匹配。
    4. 验证Hu矩计算:确保参与匹配的两个轮廓都包含足够多的点(通常>5个)。对于非常简单的形状(如接近直线的轮廓),Hu矩的区分度会下降。

7.2 匹配结果不稳定,同一物体分数波动大

  • 问题:同一物体在不同图像中,匹配分数差异显著。
  • 可能原因与解决
    1. 光照变化:形状匹配虽对光照有一定鲁棒性,但极端光照会影响边缘提取。确保打光均匀,或采用更稳定的边缘检测算子(如Canny)代替全局阈值。
    2. 轮廓起点不一致cv::matchShapes使用的Hu矩具有旋转不变性,但理论上轮廓的起点不同会影响高阶矩。不过在实际应用中,OpenCV的matchShapes实现已经考虑了归一化,这个问题影响较小。如果怀疑是此问题,可以尝试在匹配前对轮廓点进行重新排序(例如按极角排序),但会增加计算量。
    3. 图像噪声:加强预处理,如使用中值滤波cv::medianBlur去除椒盐噪声,或使用形态学闭运算cv::MORPH_CLOSE连接断开的边缘。

7.3 程序运行时崩溃或弹出DLL错误

  • 问题:编译成功,但运行时报错或崩溃。
  • 解决
    1. DLL缺失:最常见。确保系统Path环境变量中包含了OpenCV的bin目录(包含opencv_world452.dll等),并且重启了VS2022。也可以将所需的DLL文件直接复制到你的项目.exe文件所在的目录下。
    2. Debug/Release不匹配:在Debug模式下运行,却链接了Release版的lib(没有d后缀),或者反之。仔细检查项目属性中“附加依赖项”的配置是否与当前编译模式一致。
    3. 运行时库不匹配:在项目属性 -> C/C++ -> 代码生成 -> 运行时库,确保设置正确(通常Debug用/MDd,Release用/MD)。这与OpenCV编译时使用的运行时库有关。

7.4 多尺度/多角度搜索速度太慢

  • 问题:加入循环后,程序运行缓慢,无法满足实时性要求。
  • 优化方向
    1. 减少搜索空间:利用先验知识。例如,如果工件放在平面上,其旋转轴是固定的,可能只有绕Z轴的旋转。如果相机垂直向下,则只有平面旋转,没有尺度变化(物体大小固定)。
    2. 降低图像分辨率:首先在缩小的图像上进行全局搜索,定位到大致区域后,再在原图对应区域进行精细搜索和角度微调。
    3. 使用ROI:如果目标出现的大致位置是固定的(如传送带中央),就不要在全图搜索。
    4. 并行计算:如6.3节所述,利用多线程并行处理不同的角度或尺度。

7.5 阈值如何设定?

  • 问题:匹配分数多少算“匹配成功”?
  • 方法没有通用阈值。这需要通过大量实验,收集“正样本”(包含目标的图像)和“负样本”(不包含目标或包含相似干扰物的图像)的匹配分数分布来确定。
    1. 计算所有正样本的匹配分数,观察其范围(例如0.01到0.05)。
    2. 计算所有负样本的匹配分数,观察其范围# 1. 两数之和

题目

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

思路

  • 使用哈希表,将数组中的元素作为key,下标作为value
  • 遍历数组,对于每一个元素,计算target - nums[i]的值,判断这个值是否在哈希表中
  • 如果在,返回当前元素的下标和哈希表中对应元素的下标
  • 如果不在,将当前元素和下标存入哈希表

代码

class Solution { public: vector<int> twoSum(vector<int>& nums, int target) { unordered_map<int,int> map; for(int i = 0; i < nums.size(); i++) { // 遍历当前元素,并在map中寻找是否有匹配的key auto iter = map.find(target - nums[i]); if(iter != map.end()) { // 找到了 return {iter->second,i}; } // 没有找到匹配的key 将当前元素作为key 下标作为value 存入map map.insert(pair<int,int>(nums[i],i)); } return {}; } };

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询