【算法与数据结构】字符串匹配算法

news2025/1/11 5:54:47

文章目录

  • 一、暴力穷解法
  • 二、KMP算法
  • 二、BM算法
  • 三、Sunday算法
  • 四、完整代码

所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解。

一、暴力穷解法

  思路分析:首先判断字符串是否合法,然后利用for循环,取出子字符串利用compare函数进行比较。
  程序如下

class Solution {
public:
	// 复杂度n * m
	int strStr(string haystack, string needle) {
		if (haystack.size() < needle.size()) return -1;	
		if (!needle.size()) return 0; // needle为空返回0
		for (int i = 0; i < haystack.size(); ++i) {
			string substr = haystack.substr(i, needle.size());
			if (!needle.compare(substr)) return i;
		}
		return -1;
	}
};

复杂度分析:

  • 时间复杂度: O ( n ∗ m ) O(n * m) O(nm),假设haystack的长度为n,needle的长度为m,for循环的复杂度为n,当中调用了compare函数,它是逐字符比较的,复杂度为m,因此总复杂度为 O ( n ∗ m ) O(n * m) O(nm)
  • 空间复杂度: O ( 1 ) O(1) O(1)

二、KMP算法

  KMP成功实现了字符串匹配算法从乘法复杂度到线性复杂度的跨越。KMP算法是由这三位学者发明的:Knuth,Morris和Pratt,所以得名KMP算法。KMP算法的主要思想是:当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配。在KMP算法当中,利用next数组记录前缀表(prefix table)。前缀表用来回退,它记录了模式串与文本串不匹配的时候,模式串应该从哪里开始重新匹配。文本串是指要搜寻的字符串,模式串是目标字符串。例如,要在aabaaf当中找aaf,那么aaf就是模式串,aabaaf就是文本串。
  在next数组当中我们保存了模式串前缀的最长公共前后缀长度,以下简称为前缀表。字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串所谓最长公共前后缀,笔者理解为既在前缀中,又在后缀中,且长度最大的字符串。例如对于字符串 aabaa,其前缀有 a, aa, aab, aaba,后缀有a, aa, baa, abaa。最长公共前后缀就是 aa。
  那么为什么前缀表能告诉我们上次匹配的位置呢?考虑一个文本串aabaabaafa和模式串aabaaf。求出前缀表如下:

在这里插入图片描述

  假设我们在f位置,文本字符和模式字符不匹配(aabaa与模式串全部匹配),那么我们就得找f字符的前一个前缀字符串,也就是aabaa,前缀表next当中保存着所有前缀最长公共前后缀长度,aabaa的最长公共前后缀长度为2。也就是说,后缀aa匹配,那么前缀aa也必然和模式串匹配,因此前缀aa不需要再进行对比,直接让模式串的指针按next表跳转到下一个位置,因为next数组当中存储的数值为2,所以模式串跳转到下标为2的地方进行对比。(图片引用自代码随想录28.strStr)。

在这里插入图片描述

其中n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。
复杂度分析:

  • 时间复杂度: O ( n + m ) O(n + m) O(n+m),其中n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。
  • 空间复杂度: O ( m ) O(m) O(m),需要额外的空间存放大小为m的数组。

KMP算法程序

	// KMP算法
    void getNext(int* next, const string& s) {
        int j = -1;
        next[0] = j;
        for (int i = 1; i < s.size(); i++) { // 注意i从1开始
            while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
                j = next[j]; // 向前回退
            }
            if (s[i] == s[j + 1]) { // 找到相同的前后缀
                j++;
            }
            next[i] = j; // 将j(前缀的长度)赋给next[i]
            
        }
    }
    int strStr2(string haystack, string needle) {
        if (needle.size() == 0) {
            return 0;
        }
        int* next = new int[needle.size()];
        //int next[needle.size()]; 
        getNext(next, needle);
        //my_print(next, needle.size(), "前缀表:");
        int j = -1; // // 因为next数组里记录的起始位置为-1
        for (int i = 0; i < haystack.size(); i++) { // 注意i就从0开始
            while (j >= 0 && haystack[i] != needle[j + 1]) { // 不匹配
                j = next[j]; // j 寻找之前匹配的位置
            }
            if (haystack[i] == needle[j + 1]) { // 匹配,j和i同时向后移动
                j++; // i的增加在for循环里
            }
            if (j == (needle.size() - 1)) { // 文本串s里出现了模式串t
                return (i - needle.size() + 1);
            }
        }
        return -1;
    }

二、BM算法

  本节转载自从头到尾彻底理解KMP。
  1977年,德克萨斯大学的Robert S. Boyer教授和J Strother Moore教授发明了一种新的字符串匹配算法:Boyer-Moore算法,简称BM算法。该算法从模式串的尾部开始匹配,且拥有在最坏情况下O(N)的时间复杂度。在实践中,比KMP算法的实际效能高。BM算法定义了两个规则:

  • 坏字符规则:当文本串中的某个字符跟模式串的某个字符不匹配时,我们称文本串中的这个失配字符为坏字符,此时模式串需要向右移动,移动的位数 = 坏字符在模式串中的位置 - 坏字符在模式串中最右出现的位置。此外,如果"坏字符"不包含在模式串之中,则最右出现位置为-1。
  • 好后缀规则:当字符失配时,后移位数 = 好后缀在模式串中的位置 - 好后缀在模式串上一次出现的位置,且如果好后缀在模式串中没有再次出现,则为-1。
      下面举例说明BM算法。例如,给定文本串“HERE IS A SIMPLE EXAMPLE”,和模式串“EXAMPLE”,现要查找模式串是否在文本串中,如果存在,返回模式串在文本串中的位置。
    1. 首先,"文本串"与"模式串"头部对齐,从尾部开始比较。"S"与"E"不匹配。这时,“S"就被称为"坏字符”(bad character),即不匹配的字符,它对应着模式串的第6位。且"S"不包含在模式串"EXAMPLE"之中(相当于最右出现位置是-1),这意味着可以把模式串后移6-(-1)=7位,从而直接移到"S"的后一位。 在这里插入图片描述
    2. 依然从尾部开始比较,发现"P"与"E"不匹配,所以"P"是"坏字符"。但是,"P"包含在模式串"EXAMPLE"之中。因为“P”这个“坏字符”对应着模式串的第6位(从0开始编号),且在模式串中的最右出现位置为4,所以,将模式串后移6-4=2位,两个"P"对齐。
      在这里插入图片描述
      在这里插入图片描述
    3. 依次比较,得到 “MPLE”匹配,称为"好后缀"(good suffix),即所有尾部匹配的字符串。注意,“MPLE”、“PLE”、“LE”、"E"都是好后缀。
      在这里插入图片描述
    4. 发现“I”与“A”不匹配:“I”是坏字符。如果是根据坏字符规则,此时模式串应该后移2-(-1)=3位。问题是,有没有更优的移法?

      在这里插入图片描述
    5. 更优的移法是利用好后缀规则:当字符失配时,后移位数 = 好后缀在模式串中的位置 - 好后缀在模式串中上一次出现的位置,且如果好后缀在模式串中没有再次出现,则为-1。所有的“好后缀”(MPLE、PLE、LE、E)之中,只有“E”在“EXAMPLE”的头部出现,所以后移6-0=6位。可以看出,“坏字符规则”只能移3位,“好后缀规则”可以移6位。每次后移这两个规则之中的较大值。这两个规则的移动位数,只与模式串有关,与原文本串无关。
      在这里插入图片描述
    6. 继续从尾部开始比较,“P”与“E”不匹配,因此“P”是“坏字符”,根据“坏字符规则”,后移 6 - 4 = 2位。因为是最后一位就失配,尚未获得好后缀。
      在这里插入图片描述
        由上可知,BM算法不仅效率高,而且构思巧妙,容易理解。

三、Sunday算法

  KMP算法并不比最简单的c库函数strstr()快多少,而BM算法虽然通常比KMP算法快,但BM算法也还不是现有字符串查找算法中最快的算法,本文最后再介绍一种比BM算法更快的查找算法即Sunday算法。Sunday算法由Daniel M.Sunday在1990年提出,它的思想跟BM算法很相似:只不过Sunday算法是从前往后匹配,在匹配失败时关注的是文本串中参加匹配的最末位字符的下一位字符。

  • 如果该字符没有在模式串中出现则直接跳过,即移动位数 = 匹配串长度 + 1;
  • 否则,其移动位数 = 模式串中最右端的该字符到末尾的距离+1。
    // Sunday算法
    int find_single_char(char c, const string& needle) {   
        for (int i = needle.size() - 1; i >= 0; --i) {  // 找最右端的字符,因此从后往前循环
            if (c == needle[i]) return i; 
        }
        return -1;
    }
    int strStr3(string haystack, string needle) {
        if (haystack.size() < needle.size()) return -1;     // 检查合法性
        if (!needle.size()) return 0;                       // needle为空返回0      
        for (int i = 0; i <= haystack.size() - needle.size(); ) {
            for (int j = 0; j < needle.size(); ++j) {
                if (needle[j] != haystack[i + j]) {     // 匹配失败                
                    int k = find_single_char(haystack[i + needle.size()], needle);   // 文本字符串末尾的下一位字符串
                    if (k == -1)   i += needle.size() + 1;  // 模式串向右移动 模式串长度 + 1 
                    else i += needle.size() - k;            // 向右移动 模式串最右端的该字符到末尾的距离+1
                    break;
                }     
                if (j == needle.size() - 1) return i;   // 匹配成功
            }
        }
        return -1;
    }

  除了上面一个版本的代码之外,笔者还写了另外一个版本的代码。可以观察到,我们通过find_single_char这个函数去查找字符串是否存在这个字符的效率是比较低的。想要快速的查找一个元素是否在一个字符串当中,不得不考虑用哈希表。字符串在编译器内存是使用ASCII码,因此我们建立一个ASCII码表数组,提前计算出模式串的偏移量,代码如下:

    // 查找算法用哈希表代替的Sunday算法   
    int strStr4(string haystack, string needle) {
        if (haystack.size() < needle.size()) return -1;     // 检查合法性
        if (!needle.size()) return 0;                       // needle为空返回0  

        int shift_table[128] = { 0 };       // 128为ASCII码表长度
        for (int i = 0; i < 128; i++) {     // 偏移表默认值设置为 模式串长度 + 1
            shift_table[i] = needle.size() + 1;
        }
        for (int i = 0; i < needle.size(); i++) {
            shift_table[needle[i]] = needle.size() - i;
        }

        int s = 0, j; // 文本串初始位置
        while (s <= haystack.size() - needle.size()) {
            j = 0;
            while (haystack[s + j] == needle[j]) { 
                ++j;
                if (j >= needle.size()) return s;   // 匹配成功
            }
            // 找到主串中当前跟模式串匹配的最末字符的下一个字符
            // 在模式串中出现最后的位置
            // 所需要从(模式串末尾+1)移动到该位置的步数
            s += shift_table[haystack[s + needle.size()]];
        }
        return -1;
    }

复杂度分析:

  • 时间复杂度: 平均时间复杂度为 O ( n ) O(n) O(n),最坏情况时间复杂度为 O ( n ∗ m ) O(n*m) O(nm)
  • 空间复杂度: O ( 1 ) O(1) O(1),常量存储空间。

四、完整代码

  代码当中包含了暴力穷解法、KMP算法、Sunday算法。

# include <iostream>
# include <string>
using namespace std;

void my_print(int* arr, int arr_len, string str) {
    cout << str << endl;
    for (int i = 0; i < arr_len; ++i) {
        cout << arr[i] << ' ';
    }
    cout << endl;
}

class Solution {
public:
	// 暴力穷解
	// 复杂度n * m
	int strStr(string haystack, string needle) {
		if (haystack.size() < needle.size()) return -1;	
		if (!needle.size()) return 0; // needle为空返回0
		for (int i = 0; i < haystack.size(); ++i) {
			string substr = haystack.substr(i, needle.size());
			if (!needle.compare(substr)) return i;
		}
		return -1;
	}

	// KMP算法
    void getNext(int* next, const string& s) {
        int j = -1;
        next[0] = j;
        for (int i = 1; i < s.size(); i++) { // 注意i从1开始
            while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
                j = next[j]; // 向前回退
            }
            if (s[i] == s[j + 1]) { // 找到相同的前后缀
                j++;
            }
            next[i] = j; // 将j(前缀的长度)赋给next[i]
            
        }
    }
    int strStr2(string haystack, string needle) {
        if (needle.size() == 0) {
            return 0;
        }
        int* next = new int[needle.size()];
        //int next[needle.size()]; 
        getNext(next, needle);
        //my_print(next, needle.size(), "前缀表:");
        int j = -1; // // 因为next数组里记录的起始位置为-1
        for (int i = 0; i < haystack.size(); i++) { // 注意i就从0开始
            while (j >= 0 && haystack[i] != needle[j + 1]) { // 不匹配
                j = next[j]; // j 寻找之前匹配的位置
            }
            if (haystack[i] == needle[j + 1]) { // 匹配,j和i同时向后移动
                j++; // i的增加在for循环里
            }
            if (j == (needle.size() - 1)) { // 文本串s里出现了模式串t
                return (i - needle.size() + 1);
            }
        }
        return -1;
    }

    // Sunday算法
    int find_single_char(char c, const string& needle) {   
        for (int i = needle.size() - 1; i >= 0; --i) {  // 找最右端的字符,因此从后往前循环
            if (c == needle[i]) return i; 
        }
        return -1;
    }
    int strStr3(string haystack, string needle) {
        if (haystack.size() < needle.size()) return -1;     // 检查合法性
        if (!needle.size()) return 0;                       // needle为空返回0      
        for (int i = 0; i <= haystack.size() - needle.size(); ) {
            for (int j = 0; j < needle.size(); ++j) {
                if (needle[j] != haystack[i + j]) {     // 匹配失败                
                    int k = find_single_char(haystack[i + needle.size()], needle);   // 文本字符串末尾的下一位字符串
                    if (k == -1)   i += needle.size() + 1;  // 模式串向右移动 模式串长度 + 1 
                    else i += needle.size() - k;            // 向右移动 模式串最右端的该字符到末尾的距离+1
                    break;
                }     
                if (j == needle.size() - 1) return i;   // 匹配成功
            }
        }
        return -1;
    }

    // 查找算法用哈希表代替的Sunday算法   
    int strStr4(string haystack, string needle) {
        if (haystack.size() < needle.size()) return -1;     // 检查合法性
        if (!needle.size()) return 0;                       // needle为空返回0  

        int shift_table[128] = { 0 };       // 128为ASCII码表长度
        for (int i = 0; i < 128; i++) {     // 偏移表默认值设置为 模式串长度 + 1
            shift_table[i] = needle.size() + 1;
        }
        for (int i = 0; i < needle.size(); i++) {
            shift_table[needle[i]] = needle.size() - i;
        }

        int s = 0, j; // 文本串初始位置
        while (s <= haystack.size() - needle.size()) {
            j = 0;
            while (haystack[s + j] == needle[j]) { 
                ++j;
                if (j >= needle.size()) return s;   // 匹配成功
            }
            // 找到主串中当前跟模式串匹配的最末字符的下一个字符
            // 在模式串中出现最后的位置
            // 所需要从(模式串末尾+1)移动到该位置的步数
            s += shift_table[haystack[s + needle.size()]];
        }
        return -1;
    }
};

int main()
{
	//string haystack = "sadbutsad";
	//string needle = "sad";
	//string haystack = "abc";
	//string needle = "c";
    //string haystack = "substring searching algorithm";
    //string needle = "search";
    //string haystack = "hello";
    //string needle = "ll";
    //string haystack = "mississippi";
    //string needle = "issi";
    string haystack = "aabaaaababaababaa";
    string needle = "bbbb";
	int k = 2;
	Solution s1;
	cout << "目标字符串:\n" << "“" << haystack << "”" << endl;
	int result = s1.strStr4(haystack, needle);
	cout << "查找子串结果:\n" << result << endl;
	system("pause");
	return 0;
}

参考博文:

  • 最长公共前后缀
  • 字符串最长公共前缀后缀长度
  • 从头到尾彻底理解KMP
  • 字符串匹配——Sunday算法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/717166.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024考研408-操作系统 第一章-计算机系统概述学习笔记

文章目录 前言一、操作系统的基本概述1.1、操作系统的概念1.2、操作系统的功能1.2.1、操作系统提供的功能及示例1.2.2、操作系统提供的方便易用服务1.2.2.1、操作系统刚普通用户使用&#xff08;GUI用户界面、命令接口&#xff09;1.2.2.2、给程序员提供的程序接口小总结&#…

Cesium中加载WMS、WMTS、WFS三类服务,并进行点击查询

近期工作中需要使用Cesium加载各类服务&#xff0c;并进行点击查询。故针对不同的服务对加载方法和点击查询方法进行了整理。 一、加载方法 1.1加载WMS export function wmsService(url,layer){let wmsnew Cesium.WebMapServiceImageryProvider({url : url,//如http://106.12…

【Spring Boot】Spring Boot配置文件详情

前言 Spring Boot是一个开源的Java框架&#xff0c;用于快速构建应用程序和微服务。它基于Spring Framework&#xff0c;通过自动化配置和约定优于配置的方式&#xff0c;使开发人员可以更快地启动和运行应用程序。Spring Boot提供了许多开箱即用的功能和插件&#xff0c;包括嵌…

【笔记】微机原理及接口技术4 -- ADC/DAC

模数数模转换器 控制系统中的模拟接口 A/D 转换器 把模拟信号转成数字信号&#xff0c;供微处理器使用&#xff1b; D/A 转换器 输出模拟信号&#xff0c;供外部控制装置使用&#xff1b; D/A 原理 D/A 转换器的主要部件是电阻开关网络 通常是由输入的二进制数的各位控制一些…

EasyCVR如何在不修改分辨率的情况下进行H.265自动转码H.264?

EasyCVR视频融合平台基于云边端一体化架构&#xff0c;可支持多协议、多类型设备接入&#xff0c;在视频能力上&#xff0c;平台可实现视频直播、录像、回放、检索、云存储、告警上报、语音对讲、电子地图、集群、H.265转码、智能分析以及平台级联等。 我们在此前的文章中介绍过…

【Python实战】Python采集某大夫文字数据

前言 今天&#xff0c;有一位粉丝找到我&#xff0c;希望我出一期关于某大夫数据采集的文章&#xff0c;今天&#xff0c;我们就来采集某大夫的问诊数据。 环境使用 python 3.9pycharm 模块使用 requests 模块介绍 requests requests是一个很实用的Python HTTP客户端库&…

java 健身营养师网站系统myeclipse定制开发mysql数据库B/S模式java编程计算机网页

一、源码特点 JSP 健身营养师网站系统 是一套完善的系统源码&#xff0c;对理解JSP java 编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。研究的基本内容是基于网上 书店系统&#xff0c;使用JSP作为页面开发工具。Web服…

MMaction2 使用记录1——config介绍

目录 了解config &#xff08;模型训练测试的整体过程配置文件&#xff09; 通过脚本参数修改config Config 文件 结构 config文件的命名规则 动作识别的config系统 了解config &#xff08;模型训练测试的整体过程配置文件&#xff09; 我们使用python文件作为config&a…

FreeRTOS学习笔记—任务挂起和恢复

文章目录 一、任务挂起和恢复API函数1.1 vTaskSuspend()函数1.2 vTaskResume()函数1.3 xTaskResumeFromISR()函数 二、任务挂起和恢复2.1 任务1挂起解挂任务22.2 中断中解挂任务1 三、补充内容3.1 FreeRTOS数据类型3.2 中断优先级分组3.3 错误问题 一、任务挂起和恢复API函数 …

ChatGPT | Word文档如何更好地提取表格内容给ChatGPT

本文来自http://blog.csdn.net/hellogv/ &#xff0c;引用必须注明出处&#xff01; Word文档如何更好地提取表格内容给ChatGPT做知识库&#xff0c;这属于文本预处理工作。 本文只讲思路、测试结果&#xff0c;技术实现用Python和Java都能完成&#xff0c;下一篇文章再贴源码…

Python实用工具--全python制作一个音乐下载器

前言 又来展示一下关于Python的实用小技巧了&#xff0c;这次就来分享分享–如何用Python来制作一个音乐下载器 做这个有什么用啊&#xff0c;我只能说&#xff0c;可以免费下载歌曲啊&#xff0c;这样就能每月保住自己钱包咯 效果展示 基本界面 图片以及文字都是可以自己更…

《动手学深度学习》——线性神经网络

参考资料&#xff1a; 《动手学深度学习》 3.1 线性回归 3.1.1 线性回归的基本元素 样本&#xff1a; n n n 表示样本数&#xff0c; x ( i ) [ x 1 ( i ) , x 2 ( i ) , ⋯ , x d ( i ) ] x^{(i)}[x^{(i)}_1,x^{(i)}_2,\cdots,x^{(i)}_d] x(i)[x1(i)​,x2(i)​,⋯,xd(i)​…

序列化对象

1&#xff1a;对象序列化 以内存为基准&#xff0c;把内存中的对象存储到磁盘文件中去&#xff0c;称为对象序列化。使用到的流是对象字节输出流&#xff1a;ObjectOutputStream 2&#xff1a;对象要序列化&#xff0c;必须实现Serializable序列化接口 2&#xff1a;对象反序…

二十四、HTTPS

文章目录 一、HTTPS&#xff08;一&#xff09;定义&#xff08;二&#xff09;HTTP与HTTPS1.端口不同&#xff0c;是两套服务2.HTTP效率更高&#xff0c;HTTPS更安全 &#xff08;三&#xff09;加密&#xff0c;解密&#xff0c;密钥等概念&#xff08;四&#xff09;为什么要…

【H5】文件下载(javascript)

系列文章 【移动设备】iData 50P 技术规格 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/130604517 【H5】avalon前端数据双向绑定 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/131067187 【H5】安卓自动更新方案&a…

hivesql列转行

原表&#xff1a; 目标表&#xff1a; sql代码&#xff1a; select dp as 日期 ,city_name as 城市, split_part(subject,‘:’,1) as 指标, cast( split_part(subject,‘:’,2) as double ) as 数值 from( select trans_array(2,‘;’,dp,city_name,subject) as (dp,city_na…

探秘高逼格艺术二维码的制作过程-AI绘画文生图

前几天看到几个逼格比较高的二维码&#xff0c;然后自己动手做了一下&#xff0c;给大家看看效果&#xff1a; 1、文生图&#xff08;狮子&#xff09;&#xff1a; 2、文生图&#xff08;城市&#xff09;&#xff1a; 下边将开始介绍怎么做的&#xff0c;有兴趣的可以继续读…

Vault AppRole最佳实现过程

AppRole AppRole身份验证方法允许机器或应用程序使用 Vault 定义的角色进行身份验证。AppRole 的开放式设计支持使用不同的工作流和配置来应对大量应用程序。这种身份验证方法主要是面向自动化工作流程(机器和服务)设计的,对人类操作者不太有用。 “AppRole”代表一组 Vau…

大数据Doris(五十六):RESOTRE数据恢复

文章目录 RESOTRE数据恢复 一、RESTORE数据恢复原理 二、RESTORE 数据恢复语法 三、RESOTRE数据恢复案例 1、在 Doris 集群中创建 mydb_recover 库 2、执行如下命令恢复数据 3、查看 restore 作业的执行情况 四、注意事项 RESOTRE数据恢复 Doris 支持BACKUP方式将当前…

力扣 40. 组合总和 II

题目来源&#xff1a;https://leetcode.cn/problems/combination-sum-ii/description/ C题解&#xff1a; 这道题的难点在于解集中不能包含重复的组合。如果用set去重会造成超时&#xff0c;所以只能在单层递归逻辑中处理。通过识别下一个数与当前数是否相同&#xff0c;来修改…