Lua实现字符串相关算法(1)(最长回文子串,Manacher算法,中心扩展算法,动态规划算法)

题目

给你一个字符串 s,找到 s 中最长的回文子串。

示例1:

输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。

示例2:

输入:s = “cbbd”
输出:“bb”

题目分析

回文串:通俗的讲就是正着读反着读都一样的字符串
子串:不同于子序列,子串是字符串连续的子集,子序列是子集
最长:满足前两个要求的最长回文子串

解法一:暴力算法

设字符串S,子串S[i…j],暴力枚举S[i…j]的所有组合并判断是否为回文串。
时间复杂度分析:首先暴力枚举i,j时间复杂度为O(n2),判断是否为回文串时间复杂度为O(n),所以时间复杂度为O(n^3)
空间复杂度为:O(1)
时间复杂度太高不建议写

解法二:动态规划

这里我们考虑优化回文串的判断,将回文串的判断优化到O(1)
动态规划的核心思想是从小的问题转移到大的问题,或者说记忆之前的操作的结果,让当前的操作利用之前的操作结果,是典型的空间换时间的方法
这里分析怎样利用之前的操作结果,我们设dp[i][j]为S[i…j]是否为回文串,
首先当i==j时 dp[i][j]==true
当i,j越界或者i>j时 dp[i][j]==false
然后对串进行判断
当s[i]!=s[j] dp[i][j]==false 很显然当头部和尾部字符不相等的时候s[i…j]不是回文串
当s[i]==s[j] dp[i][j]==dp[i+1]dp[j-1] 这里如果头尾字符相等,就需要判断中间字串是否是回文串,这里就利用到了之前操作的结果
代码如下:

--动态规划算法   时间复杂度O(n^2)   空间复杂度O(n^2)可优化
function longestPalindromeDP(str)
	left = 1
	right = 1
	
	row = #str
	col = #str
	dp = {} --dp[i][j]表示str.sub(i,j)能否组成回文串

	table.insert(dp,1)
	--初始化
	for i=1,row do
		for j=1,col do
			if(i==j) then
				table.insert(dp,true)--单个字符肯定是回文串
			else
				table.insert(dp,false)
			end
		end
	end

	
	for j=2,col do
		for i=1,j do
			if(string.sub(str,i,i)~=string.sub(str,j,j)) then
				dp[i*col+j] = false
			else
				if(j-i<3) then
					dp[i*col+j] = true
				else
					dp[i*col+j] = dp[(i+1)*col+j-1]
				end
			end

			if(dp[i*col+j]==true) then
				if(j-i>right-left) then
					left = i
					right = j
				end
			end
		end
	end
	return string.sub(str,left,right)
end

解法三:中心扩展算法

中心扩展算法优化 的是i,j的枚举,将O(n^2)优化到O(n)
在这里,我们不用枚举头和尾,我们枚举子串的中心,修改回文串判断算法,从中心向外扩展计算回文串最大长度
但是这里有问题,当字串长度为奇数时有一个中心,但是长度为偶数时有两个中心,设计回文串算法的时候要注意
代码如下:

--中心扩散算法   时间复杂度O(n^2)   空间复杂度O(1)
function expandAroundCenter(str,left,right)
	while(left>=1 and right<=#str and string.sub(str,left,left)==string.sub(str,right,right)) do
		left = left - 1
		right = right + 1
	end
	return left+1,right-1
end

function longestPalindrome(str)
	len = #str
	left = 0
	right = 0
	for i=1,len do
		left1,right1 = expandAroundCenter(str,i,i)
		left2,right2 = expandAroundCenter(str,i,i+1)
		if(right1-left1 >right-left) then
			left=left1
			right=right1
		end
		if(right2-left2 >right-left) then
			left=left2
			right=right2
		end
	end
	return string.sub(str,left,right)
end

解法四:Manacher算法(马拉车算法)

这种算法结合了动态规划的算法与中心扩展算法,并结合了回文串特有的对称性质
1这里引入几个概念,臂长,最大中心点,最大右边界
臂长:回文串长度的一半
最大右边界:(已经遍历过的点的位置+该点臂长)的最大值
最大中心点:使得右边界最大的点

设最大右边界maxRight,最大中心点maxCenter,当前遍历的点的位置为i
这时如果按照中心扩展算法就会从i一个个的去扩展遍历,时间复杂度为O(n),但事实上我们可以用到我们之前操作的结果(动态规划的思想),
如果 i<maxRight,说明点i在最大中心点maxCenter的臂长范围内,这就有一个好处,根据回文串的对称性,我们可以根据i关于maxCenter的对称点来让优化计算i的臂长,为什么呢?
因为我们可以根据对称点的臂长(已经计算过)来让以i为中心的扩展少遍历一些重复的字符(对称性)
具体少多少呢答案为:min(对称点的臂长,maxRight到i的距离),这是因为我们只能保证在最大右边界内两边字符一定对称。

这里我们就完成了中心扩展算法的优化(跳过重复字符的判断,利用对称性和记忆化),但是有一个注意点就是这里只试用与奇数串(中心点只有一个,中心点有两个的太过复杂)

所以我们要对原串进行预处理,将其补充成奇数串

代码如下:

--Manacher算法(马拉车算法)是中心扩展算法和动态规划的结合版  时间复杂度O(n) 空间复杂度O(n)
function expendManacher(str,left,right)
	
	while(left>=1 and right<=#str and string.sub(str,left,left)==string.sub(str,right,right)) do
		left = left - 1
		right = right + 1
	end
	return (right-left-2)//2
end

function min(numa,numb)
	if(numa<numb) then
		return numa
	else
		return numb
	end
end

function longestPalindromeManacher(str)
	--字符串预处理,插入特殊字符,将str的长度处理成奇数个
	local newtable = {"#"}
	for i=1,#str do
		table.insert(newtable,string.sub(str,i,i))
		table.insert(newtable,"#")
	end
	local newstr = table.concat(newtable)
	
	arm_len = {}
	local maxRight=0
	local maxCenter=0
	local left=1
	local right=1
	for i=1,#newstr do
		local curr_arm_len = 0

		if(i<maxRight) then
			local mirror = maxCenter*2-i
			local ignore_arm_len = min(arm_len[mirror],maxRight-i)
			curr_arm_len = expendManacher(newstr,i-ignore_arm_len,i+ignore_arm_len)
		else
			curr_arm_len = expendManacher(newstr,i,i)
		end
		table.insert(arm_len,curr_arm_len)
		if(i+curr_arm_len>maxRight) then
			maxCenter=i
			maxRight=i+curr_arm_len
		end
		if(curr_arm_len*2+1>right-left)then
			left=i-curr_arm_len
			right=i+curr_arm_len
		end
	end
	anstable={}
	for i=left,right do
		if(string.sub(newstr,i,i)~="#") then
			table.insert(anstable,string.sub(newstr,i,i))
		end
	end
	return table.concat(anstable)
end

完整测试代码

a={"asddsa","axdvvdxaax","ababaabbaababa"}

--中心扩散算法   时间复杂度O(n^2)   空间复杂度O(1)
function expandAroundCenter(str,left,right)
	while(left>=1 and right<=#str and string.sub(str,left,left)==string.sub(str,right,right)) do
		left = left - 1
		right = right + 1
	end
	return left+1,right-1
end

function longestPalindrome(str)
	len = #str
	left = 0
	right = 0
	for i=1,len do
		left1,right1 = expandAroundCenter(str,i,i)
		left2,right2 = expandAroundCenter(str,i,i+1)
		if(right1-left1 >right-left) then
			left=left1
			right=right1
		end
		if(right2-left2 >right-left) then
			left=left2
			right=right2
		end
	end
	return string.sub(str,left,right)
end

--动态规划算法   时间复杂度O(n^2)   空间复杂度O(n^2)可优化
function longestPalindromeDP(str)
	left = 1
	right = 1
	
	row = #str
	col = #str
	dp = {} --dp[i][j]表示str.sub(i,j)能否组成回文串

	table.insert(dp,1)
	--初始化
	for i=1,row do
		for j=1,col do
			if(i==j) then
				table.insert(dp,true)--单个字符肯定是回文串
			else
				table.insert(dp,false)
			end
		end
	end

	
	for j=2,col do
		for i=1,j do
			if(string.sub(str,i,i)~=string.sub(str,j,j)) then
				dp[i*col+j] = false
			else
				if(j-i<3) then
					dp[i*col+j] = true
				else
					dp[i*col+j] = dp[(i+1)*col+j-1]
				end
			end

			if(dp[i*col+j]==true) then
				if(j-i>right-left) then
					left = i
					right = j
				end
			end
		end
	end
	return string.sub(str,left,right)
end

--Manacher算法(马拉车算法)是中心扩展算法和动态规划的结合版  时间复杂度O(n) 空间复杂度O(n)
function expendManacher(str,left,right)
	
	while(left>=1 and right<=#str and string.sub(str,left,left)==string.sub(str,right,right)) do
		left = left - 1
		right = right + 1
	end
	return (right-left-2)//2
end

function min(numa,numb)
	if(numa<numb) then
		return numa
	else
		return numb
	end
end

function longestPalindromeManacher(str)
	--字符串预处理,插入特殊字符,将str的长度处理成奇数个
	local newtable = {"#"}
	for i=1,#str do
		table.insert(newtable,string.sub(str,i,i))
		table.insert(newtable,"#")
	end
	local newstr = table.concat(newtable)
	
	arm_len = {}
	local maxRight=0
	local maxCenter=0
	local left=1
	local right=1
	for i=1,#newstr do
		local curr_arm_len = 0

		if(i<maxRight) then
			local mirror = maxCenter*2-i
			local ignore_arm_len = min(arm_len[mirror],maxRight-i)
			curr_arm_len = expendManacher(newstr,i-ignore_arm_len,i+ignore_arm_len)
		else
			curr_arm_len = expendManacher(newstr,i,i)
		end
		table.insert(arm_len,curr_arm_len)
		if(i+curr_arm_len>maxRight) then
			maxCenter=i
			maxRight=i+curr_arm_len
		end
		if(curr_arm_len*2+1>right-left)then
			left=i-curr_arm_len
			right=i+curr_arm_len
		end
	end
	anstable={}
	for i=left,right do
		if(string.sub(newstr,i,i)~="#") then
			table.insert(anstable,string.sub(newstr,i,i))
		end
	end
	return table.concat(anstable)
end

for i=1,#a do
	print("中心扩展",longestPalindrome(a[i]))
	print("动态规划",longestPalindromeDP(a[i]))
	print("Manacher算法",longestPalindromeManacher(a[i]))
	print()
end
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值