剑指 Offer 19. 正则表达式匹配 (golang版)

  • 题目描述
请实现一个函数用来匹配包含'. ''*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a""ab*ac*a"匹配,但与"aa.a""ab*a"均不匹配。

示例 1:
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。

示例 2:
输入:
s = "aa"
p = "a*"
输出: true
解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。

示例 3:
输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。

示例 4:
输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。

示例 5:
输入:
s = "mississippi"
p = "mis*is*p*."
输出: false
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母以及字符 . 和 *,无连续的 '*'。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zheng-ze-biao-da-shi-pi-pei-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
  • 思路:
    1、不能考虑太复杂,否则会陷入死胡同中,总会有一些情况考虑不到
    2、极端情况跟下,字符串s和匹配模式p,p为".*"可以匹配所有字串,s和p都为空也是匹配,s为空但p不为空,不可能匹配
    3、一般情况,“*”代表前面的字符串出现1次活多次,需要考虑到每一种情况并去验证,即出现0次,则p的下标直接+2跳过,出现1次,出现多次
    4、递归解决逻辑会清晰一些,注意要保存每次匹配的结果避免递归效率低下,不然会报超时,注意边界

  • 代码

func isMatch(s string, p string) bool {
	//临时切片,保存递归时当前步骤的结果,默认值0,1代表匹配,2匹配失败
	tmp := make([][]int,len(s)+1)
	for i := range tmp {
		tmp[i] = make([]int,len(p)+1)
	}
	//匹配模式p为".*"可以匹配所有字符,直接返回true
	//s和p都为空字符串,直接返回true
	if p == ".*" || (len(s) == 0 && len(p) == 0) {
		return true
	}
	//s为空,p不为空,直接返回false
	if  len(s)>0 && len(p)==0 {
		return false
	}
	return match(s,p,len(s),len(p),0,0,tmp)
}

func match(s string, p string,lenS int, lenP int, i int, j int,tmp [][]int) bool {
	//值为2代表之前已有步骤判断过该位置处不匹配,直接返回结果
	if tmp[i][j] == 2 {
		return false
	}
	//字符串匹配,返回true
	if i == lenS && j == lenP {
        tmp[i][j] = 1
		return true
	}
	/* 当下一个字符为“*”时,有三种状况
	*  1: "*"前的字符匹配0次,j跳过该字符及“*”字符,j=j+2
	*  2: "*"前的字符匹配1次,比对s[i]和p[j]的值后向前移动
	*  3: "*"前字符串匹配多次
	*/
	if j<lenP-1 && string(p[j+1])=="*" {
		if match(s,p,lenS,lenP,i,j+2,tmp) || (i<lenS && (string(s[i])==string(p[j]) || string(p[j])==".") && match(s,p,lenS,lenP,i+1,j+2,tmp)) || (i<lenS && (string(s[i])==string(p[j]) || string(p[j])==".") && match(s,p,lenS,lenP,i+1,j,tmp)){
			tmp[i][j] = 1
			return true
		}else{
			tmp[i][j] = 2
			return false
		}
	}else if i<lenS && j<lenP && (string(s[i])==string(p[j]) || string(p[j])==".") {
		if match(s,p,lenS,lenP,i+1,j+1,tmp) {
			tmp[i][j] = 1
			return true
		}else{
			tmp[i][j] = 2
			return false
		}
	}
	return false
}
  • 完整版测试代码
package main

import (
	"fmt"
)

func main() {
	test()
}


func isMatch(s string, p string) bool {
	//临时切片,保存递归时当前步骤的结果,默认值0,1代表匹配,2匹配失败
	tmp := make([][]int,len(s)+1)
	for i := range tmp {
		tmp[i] = make([]int,len(p)+1)
	}
	//匹配模式p为".*"可以匹配所有字符,直接返回true
	//s和p都为空字符串,直接返回true
	if p == ".*" || (len(s) == 0 && len(p) == 0) {
		return true
	}
	//s为空,p不为空,直接返回false
	if  len(s)>0 && len(p)==0 {
		return false
	}
	return match(s,p,len(s),len(p),0,0,tmp)
}

func match(s string, p string,lenS int, lenP int, i int, j int,tmp [][]int) bool {
	//值为2代表之前已有步骤判断过该位置处不匹配,直接返回结果
	if tmp[i][j] == 2 {
		return false
	}
	//字符串匹配,返回true
	if i == lenS && j == lenP {
		return true
	}
	/* 当下一个字符为“*”时,有三种状况
	*  1: "*"前的字符匹配0次,j跳过该字符及“*”字符,j=j+2
	*  2: "*"前的字符匹配1次,比对s[i]和p[j]的值后向前移动
	*  3: "*"前字符串匹配多次
	*/
	if j<lenP-1 && string(p[j+1])=="*" {
		if match(s,p,lenS,lenP,i,j+2,tmp) || (i<lenS && (string(s[i])==string(p[j]) || string(p[j])==".") && match(s,p,lenS,lenP,i+1,j+2,tmp)) || (i<lenS && (string(s[i])==string(p[j]) || string(p[j])==".") && match(s,p,lenS,lenP,i+1,j,tmp)){
			tmp[i][j] = 1
			return true
		}else{
			tmp[i][j] = 2
			return false
		}
	}else if i<lenS && j<lenP && (string(s[i])==string(p[j]) || string(p[j])==".") {
		if match(s,p,lenS,lenP,i+1,j+1,tmp) {
			tmp[i][j] = 1
			return true
		}else{
			tmp[i][j] = 2
			return false
		}
	}
	return false
}


func test(){
	s := "aa"
	p := "a"
	if isMatch(s,p) != false {
		println("N",s,p)
	}

	s = "aa"
	p = "a*"
	if isMatch(s,p) != true {
		println("N",s,p)
	}

	s = "ab"
	p = ".*"
	if isMatch(s,p) != true {
		println("N",s,p)
	}

	s = "aab"
	p = "c*a*b"
	if isMatch(s,p) != true {
		println("N",s,p)
	}

	s = "mississippi"
	p = "mis*is*p*."
	if isMatch(s,p) != false {
		println("N",s,p)
	}

	s = "aa"
	p = "aaa"
	if isMatch(s,p) != false {
		println("N",s,p)
	}

	s = "aaa"
	p = "a*"
	if isMatch(s,p) != true {
		println("N",s,p)
	}

	s = "aaa"
	p = "a*a"
	if isMatch(s,p) != true {
		println("N",s,p)
	}

	s = "aaa"
	p = "ab*a*c*a"
	if isMatch(s,p) != true {
		println("N",s,p)
	}

	s = "a"
	p = "ab*a"
	if isMatch(s,p) != false {
		println("N",s,p)
	}

	s = "a"
	p = "ab*"
	if isMatch(s,p) != true {
		println("N",s,p)
	}

	s = "bbbba"
	p = ".*a*a"
	if isMatch(s,p) != true {
		fmt.Println("N",s,p)
	}

	s = ""
	p = "c*"
	if isMatch(s,p) != true {
		println("N",s,p)
	}

	s = "aaaaaaaaaaaaab"
	p = "a*a*a*a*a*a*a*a*a*a*c"
	if isMatch(s,p) != false {
		println("N",s,p)
	}
}
<think> 首先,用户的问题是:“我想查找正则表达式匹配第二个元素的方法 请问正则表达式如何匹配第二个元素”。用户想用正则表达式匹配第二个元素。 从系统指令中,我需要: - 使用中文回答。 - 行内数学表达式用$...$。 - 独立公式用$$...$$。 - LaTeX语法正确。 - 生成相关问题。 - 引用时添加引用标识。 - 参考站内引用。 提供的引用: - [^1]: 正则表达式的基本介绍和Python re模块。 - [^2]: 正则表达式匹配,包括'.'和'*',来自剑指Offer,思路。 - [^3]: 正则表达式非贪婪匹配的例子。 - [^4]: 正则表达式匹配开始和结束位置。 用户的问题是关于匹配第二个元素。这听起来像是在文本中匹配第二个出现的元素,比如在列表中或序列中。 在正则表达式中,没有直接匹配“第二个元素”的内置功能;你需要指定你想要匹配的元素类型。可能是匹配第二个字符、第二个单词、第二个匹配项等。 用户说“第二个元素”,可能需要更具体的上下文。但在引用中,[^3]提到了匹配非贪婪模式,这可能有帮助。 关键点:用户可能想匹配序列中的第二个实例。例如,在字符串中匹配第二个特定的字符或模式。 正则表达式方法: - 使用分组和量词来指定位置。 - 使用捕获组。 - 使用非贪婪匹配(如引用[^3])。 - 使用位置锚点(如引用[^4])。 例如: - 匹配第二个'a':你可以写一个模式来匹配第一个'a'但不捕获,然后匹配第二个'a'。 - 通用方法:使用.*?来非贪婪匹配第一个元素,然后匹配第二个元素。 回忆正则表达式语法: - . 匹配任意字符。 - * 匹配零或多个。 - ? 用于非贪婪匹配。 - \d 匹配数字,等等。 匹配第二个具体元素: 假设用户想匹配序列中的第二个实例。 例如,在字符串 "apple banana cherry" 中,匹配第二个单词。 步骤: 1. 定义元素:元素可以是字符、单词或其他模式。 2. 使用正则表达式定位第二个出现。 例如,匹配第二个单词: - 模式:\b\w+\b 匹配一个单词。 - 匹配第二个:你可以使用非贪婪匹配来跳过第一个:.*?\b(\w+)\b.*?\b(\w+)\b ,捕获第二个组。 在正则表达式中,要匹配第二个特定模式,你可以使用捕获组。 用户可能意味着在列表或序列中,但正则表达式是字符串匹配,所以假设是文本匹配。 从引用[^2]:'.' 任意字符,'*' 重复。 引用[^3]:非贪婪匹配。 引用[^4]:匹配开始和结束。 针对匹配第二个元素: - 一个常见方法是使用模式来匹配第一个元素(可选捕获),然后匹配第二个元素。 - 或者使用正向后看或负向后看,但复杂。 简单方法: - 匹配整个字符串并提取第二个匹配项。 - 在代码中,使用正则表达式引擎的findall或类似函数获取所有匹配,然后取第二个。 但在纯正则表达式模式下,需要指定位置。 例如,匹配第二个数字: - 模式:.*?(\d).*?(\d) 但非贪婪以确保只匹配到第二个。 更好的模式:\d.*?(\d) 匹配第一个数字,然后非贪婪匹配到第二个数字,但捕获第二个。 标准方式:使用非贪婪量词。 例如,匹配第二个'a': - 模式:a.*?(a).*?是非贪婪,匹配尽可能少,所以第二个'a'被捕获。 在字符串:"abc a def a" 中,匹配第二个'a': - 模式:a.*?(a)匹配从第一个'a'到第二个'a',捕获第二个'a'。 验证:假设字符串 "a1 a2 a3" - a.*?(a)匹配 "a1 a2" ,捕获 "a2" ,因为 .*? 最小匹配。 使用环视:^(?:.*?a){1}.*?(a) 但复杂。 简单回答:使用非贪婪匹配来定位。 在引用[^3],它显示了非贪婪匹配的例子。 生成相关问题: - 系统指令:生成相关问题。 - 在回答结束时添加 §§相关问题§§ 。 回答结构: - 清晰、逐步解释。 - 使用中文。 - 代码示例可能,因为引用中有Python实现。 用户的问题是“正则表达式如何匹配第二个元素”,所以提供方法。 步骤: 1. 解释用户可能的意思:元素可以是字符、单词、模式等。 2. 提供通用方法。 3. 给出具体例子。 4. 代码示例(可选)。 5. 引用相关引用。 引用: - [^3] 谈到非贪婪匹配。 - [^4] 谈到位置匹配。 所以,在回答中引用这些。 最终相关问题: 例如: 1. 如何匹配最后一个元素? 2. 如何匹配第n个元素? 3. 正则表达式中的贪婪匹配和非贪婪匹配有什么区别? 确保回答真实可靠。 </think> ### 正则表达式匹配第二个元素的方法 在正则表达式中,匹配“第二个元素”通常是指在文本序列中匹配第二次出现的目标模式(如字符、单词或特定子串)。由于正则表达式本身没有直接匹配“第n个元素”的内置语法,我们需要通过组合位置锚点、非贪婪匹配和捕获组来实现。这取决于元素的具体定义(例如单个字符、单词或其他模式)。以下是逐步解释和方法: #### 1. **理解“元素”和匹配原理** - “元素”可以是任何目标模式,如特定字符(例如字母'a')、单词(如'apple')或数字(如'123')。 - 正则表达式匹配基于位置:起始位置用 `^`,结束位置用 `$`,并可通过量词(如`*`或`?`)控制匹配次数。 - 匹配第二个元素的核心思想是: - 使用非贪婪匹配(`.*?`)跳过第一个元素(尽可能少地匹配字符),这样可以精确定位到第二个出现的位置。 - 结合捕获组(`(...)`)来提取第二个元素。 - 引用[^3]提到非贪婪匹配(在量词后加`?`)可以将默认的贪婪行为改为最小匹配,这在跳过第一个元素时非常有用[^3]。引用[^4]强调了位置锚点(如`^`)的重要性,确保匹配从特定位置开始[^4]。 #### 2. **通用匹配方法** 无论元素类型如何,通用正则表达式模式为: ```regex 第一个元素的模式.*?(第二个元素的模式) ``` - **解释**: - `第一个元素的模式`:定义要跳过的第一个元素(可以是具体字符或通配符)。 - `.*?`:非贪婪匹配匹配任意字符,但尽可能少匹配),用于跳过从起点到第二个元素之间的内容。 - `(第二个元素的模式)`:捕获组,用于匹配并提取第二个元素。 - **关键点**: - 非贪婪 `.*?` 确保匹配提前终止,避免过度跳过元素。 - 如果目标是匹配整个字符串中的第二个出现(不指定起始位置),可以省略起始锚点。 #### 3. **具体示例** 以下是常见场景的示例(使用 Python 的 `re` 模块,因为引用[^1]提到 Python 实现): - **示例 1:匹配第二个特定字符(如第二个 'a')** - 模式:`a.*?(a)`(匹配第一个 'a' 后,非贪婪跳过字符,直到捕获第二个 'a') - 字符串:"apple banana cherry" - 正则表达式:`a.*?(a)` - 匹配结果:捕获组捕获 "a"(位于 "banana" 中)。 - Python 代码: ```python import re text = "apple banana cherry" pattern = r'a.*?(a)' # 使用非贪婪匹配 match = re.search(pattern, text) if match: print(match.group(1)) # 输出捕获的第二个 'a' ``` - 输出:`a` - **示例 2:匹配第二个单词(单词边界用 `\b`)** - 模式:`\b\w+\b.*?\b(\w+)\b`(匹配第一个单词后,非贪婪跳过字符,直到捕获第二个单词) - 字符串:"apple banana cherry" - 正则表达式:`\b\w+\b.*?\b(\w+)\b` - 匹配结果:捕获组捕获 "banana"。 - Python 代码: ```python import re text = "apple banana cherry" pattern = r'\b\w+\b.*?\b(\w+)\b' # \w+ 匹配单词,\b 是单词边界 match = re.search(pattern, text) if match: print(match.group(1)) # 输出捕获的第二个单词 ``` - 输出:`banana` - **示例 3:匹配第二个数字(如第二个数字序列)** - 模式:`\d+.*?(\d+)`(匹配第一个数字序列后,非贪婪跳过字符,直到捕获第二个数字序列) - 字符串:"id: 123, value: 456" - 正则表达式:`\d+.*?(\d+)` - 匹配结果:捕获组捕获 "456"。 - 解释:此模式利用了引用[^2]中的 '.'(任意字符)和 '*'(重复),但添加了非贪婪量词 '?' 来控制匹配范围[^2]。 #### 4. **注意事项** - **性能优化**:非贪婪匹配(`.*?`)在大型文本中可能比贪婪匹配高效,因为它避免了回溯问题(引用[^3])[^3]。 - **通用化**:如果需要匹配第 n 个元素,可以扩展模式为:`(第一个元素的模式.*?){n-1}(第n个元素的模式)`(使用重复组)。 - **边界条件**:如果文本中元素少于两个,匹配会失败(使用 `re.search` 的返回值检查)。 - **兼容性**:此方法在大多数正则表达式引擎(如 Python、JavaScript)中有效。确保模式正确转义特殊字符(如 '.' 需写为 `\.`)。 ### 总结 匹配第二个元素的关键是结合非贪婪匹配和捕获组:先用模式跳过第一个元素,再捕获第二个元素。例如,在 Python 中,使用 `re.search` 和捕获组提取结果。如果您提供具体元素类型(如第二个单词或数字),我可以给出更精准的模式。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值