从零学算法5

news2025/2/2 21:03:12

5.给你一个字符串 s,找到 s 中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
示例 1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 2:
输入:s = “cbbd”
输出:“bb”

  • 暴力解法。无非就是双重循环,截取出所有子串,判断是否为回文串,然后取最大值。稍微优化一下,如果此时子串长度小于当前最长的回文子串 max 就直接跳过,当前子串长度为 j - i +1
  •   String s;
      public String longestPalindrome(String s) {
          this.s = s;
          int n = s.length();
          // start:最长回文子串起始下标
          int max = 0,start = 0;
          for(int i=0;i<n;i++){
              for(int j=i;j<n;j++){
                  if(j-i+1 < max)continue;
                  if(isPalindrome(i,j)){
                      max = Math.max(max, j-i+1);
                      start = i;
                  }
              }
          }
          return s.substring(start, start+max);
      }
      public boolean isPalindrome(int i,int j){
      // 根据回文串对称的性质从左右两端往中间找,有不一样的就不是
          while(i<j){
              if(s.charAt(i++)!=s.charAt(j--))return false;
          }
          return true;
      }
    
  • 中心扩散法。遍历 s 的每个下标,以其为中心点向两边扩散,看是否为相同字符,就能得到以该点为中心的回文串。但是回文串不一定为奇数长度,即中心点处长度不一定为 1,比如 abccba,中心点处其实为 cc,所以就在扩散时定义初始左右起点为这两个字符的下标即可。
  •   public String longestPalindrome(String s) {
          int n = s.length();
          int max = 0,start = 0;
          for(int mid=0;mid<n;mid++){
              if(n-mid<max/2)break;
              int i=mid,j=mid;
              //有相同的点就移动初始右端点
              // 为什么不需要移动左端点?看下面 mid = j 部分
              while(j<n-1 && s.charAt(j)==s.charAt(j+1))j++;
              // i~j 这一段的点作为中心处已经在这一轮考虑过,所以之后直接跳过即可
              // 同时这也保证了下一轮 i 初始化为 mid 时,i 的左边不会和 i 处字符重复
              // 相当于在上一轮就移动了左端点,遍历顺序为从左到右,所以首轮左处无端点无需处理
              mid = j;
              // 向两端延伸求最大回文串长度
              while(i>0 && j<n-1 && s.charAt(i-1)==s.charAt(j+1)){
                  i--;
                  j++;
              }
      		// 更新最大值和最大回文子串起始下标
              if(j-i+1>max){
                  max = j-i+1;
                  start=i;
              }
          }
          return s.substring(start, start+max);
      }
    
  • 动态规划法。根据上面中心扩散法判断回文串的方式,可以大致看到动态规划的雏形。假定 boolean dp[left][right] 表示子串 s[left:right] 是否为回文串。那么从 dp[left][right] 为 true 开始,如果 s.charAt(left-1)==s.charAt(right+1),就可以得到 dp[left-1][right+1] 为 true,即 dp[l][r] = dp[l+1][r-1] && s.charAt(l-1)==s.charAt(r+1)。得到递推公式以后还需要确定一下边界条件。以下 s.charAt(left) 我就简写成 s[left] 了,如果 s[left]!=s[right],说明当前子串左右两端字符不相同,那么你怎么都不会是回文串;如果相同,那么当子串长度小于等于 3 时,你一定是回文串,比如 aba,bb,a,否则就根据 dp[left + 1][right - 1] 递推。
  • 考虑到dp[left][right] 依赖 dp[left + 1][right - 1] 的特性,比如 dp[0][3],要先知道 dp[1][2] ,那 left 在外层遍历是肯定不可能先得到 dp[1][x] 再得到 dp[0][x] 的,所以遍历顺序需要调整为外层为 right,内层为 left
  •   public String longestPalindrome(String s) {
          int n = s.length();
          boolean[][] dp = new boolean[n][n];
          int max = 0,start = 0;
          for(int j=0;j<n;j++){
              for(int i=0;i<=j;i++){
                  if(s.charAt(i)!=s.charAt(j))continue;
                  if(j-i<3)dp[i][j]=true;
                  else dp[i][j]=dp[i+1][j-1];
                  if(dp[i][j] && j-i+1>max){
                      max = j-i+1;
                      start=i;
                  }
              }
          }
          return s.substring(start, start+max);
      }
    
  • 长度为 1 的子串肯定是回文串,所以还可以根据这点稍微优化一下
  •   int max = 1,start = 0;
      for(int j=1;j<n;j++){
          for(int i=0;i<j;i++){
    
  • 马拉车算法(Manacher):回文串有奇数 aba 和偶数 abba 两种形式,使用马拉车算法,会在字符串的每两个字符间以及字符首尾加上一个特殊字符,这样最终字符串长度都会为奇数,同时,原本的每个回文串的长度也会都变成奇数,以上面两个字符串为例就可以证明
  • aba->*a*b*a*
  • abba->*a*b*b*a*
  • 其实就是奇数加偶数必定为奇数
  • 再引用一个变量回文半径,表示回文串左边或右边到中心点的长度,比如 *a*b*a* 回文半径为 *a*b 或者 b*a* 的长度也就是 4, *a*b*b*a* 回文半径为 5
  • 铺垫完毕,进入正题,中心扩散法每次换一个中心点,都要重新计算此时以 i 点为中心点的回文串长度,而马拉车在中心扩散法的基础上改进,可以利用之前的结果。
  • 因为遍历顺序是从左到右,所以我们取之前的回文串结果中右边界最大的一个,利用它推出当前以 i 为中心的回文串长度。我们就假设之前的那个结果中心点为 maxCenter,左右端点为 left 和 maxRight。我们根据 i 的位置划分出不同情况下怎么推出当前结果
    1. i < maxRight,那么以 maxCenter 为对称中心对称过去的点 j 肯定在 left 到 maxCenter,如果以 j 为中心点得到的回文串也在 left 到 maxCenter 之内,那根据对称的性质,i 对应的结果和 j 对应的一样,下图顺便说明了得到 j 点的计算公式,把 left 带入 0 为例子比较容易推导, p[] 为回文串半径数组
      请添加图片描述

    2. i < maxRight,但是以 j 为中心的回文串长度超过了 left,还是根据对称,我起码可以肯定我的回文串半径最小也等于 j - left ,也就是 maxRight - i,我们以此为基础用中心扩散法继续扩散即可
      请添加图片描述

    3. i > maxRight,那没办法了,老老实实从头开始中心扩散法吧请添加图片描述

  • i 为什么没有小于 maxCenter 的情况,因为 maxCenter 就是我们之前不断遍历得到的某个 i,新的 i 当然大于之前的 i 了
  •   int charLen = s.length();//源字符串的长度
      int length = charLen * 2 + 1;//添加特殊字符之后的长度
      char[] chars = s.toCharArray();//源字符串的字符数组
      char[] res = new char[length];//添加特殊字符的字符数组
      int index = 0;
      //添加特殊字符
      for (int i = 0; i < res.length; i++) {
          res[i] = (i % 2) == 0 ? '#' : chars[index++];
      }
    
      //新建p数组 ,p[i]表示以res[i]为中心的回文串半径
      int[] p = new int[length];
      //maxRight(某个回文串延伸到的最右边下标)
      //maxCenter(maxRight所属回文串中心下标),
      //resCenter(记录遍历过的最大回文串中心下标)
      //resLen(记录遍历过的最大回文半径)
      int maxRight = 0, maxCenter = 0, resCenter = 0, resLen = 0;
      //遍历字符数组res
      for (int i = 0; i < length; i++) {
          if (i < maxRight) {
              //情况一,i没有超出范围[left,maxRight]
              //2 * maxCenter - i其实就是j的位置,实际上是判断p[j]<maxRight - i
              // maxRight - i 其实就是 j-left,也就是说 j 的回文半径在 left ~ j
              if (p[2 * maxCenter - i] < maxRight - i) {
                  //j的回文半径没有超出范围[left,maxRight],直接让p[i]=p[j]即可
                  p[i] = p[2 * maxCenter - i];
              } else {
                  //情况二,j的回文半径已经超出了范围[left,maxRight],我们可以确定p[i]的最小值
                  //是maxRight - i,至于到底有多大,后面还需要在计算
                  // i - p[i] 表示以 i 为中心, p[i] 为半径的回文串的左端点,同理 i + p[i] 为右端点
                  // 半径不断增加就等于中心点不断向外扩散
                  p[i] = maxRight - i;
                  while (i - p[i] >= 0 && i + p[i] < length && res[i - p[i]] == res[i + p[i]])
                      p[i]++;
              }
          } else {
              //情况三,i超出了范围[left,maxRight],就没法利用之前的已知数据,而是要一个个判断了
              p[i] = 1;
              while (i - p[i] >= 0 && i + p[i] < length && res[i - p[i]] == res[i + p[i]])
                  p[i]++;
          }
          // 匹配完之后,如果右边界i + p[i]超过maxRight,那么就更新maxRight和maxCenter
          // 因为主要看 i 相对右边界来推导结果,所以能更新右边界就更新
          if (i + p[i] > maxRight) {
              maxRight = i + p[i];
              maxCenter = i;
          }
          //记录最长回文串的半径和中心位置
          if (p[i] > resLen) {
              resLen = p[i];
              resCenter = i;
          }
      }
      //计算最长回文串的长度和开始的位置
      resLen = resLen - 1;
      int start = (resCenter - resLen) >> 1;
      //截取最长回文子串
      return s.substring(start, start + resLen);
    
  • 如下图所示,有特殊字符的回文半径 - 1 其实就是原始回文串的长度,这也就是 resLen = resLen - 1; 的由来
    请添加图片描述
  • 上面三种情况还能合并,把他们都看成确定半径以及扩散这两步即可,情况 1 确定完半径以后不满足扩散条件所以不会扩散,情况 2 和 3 也就是初始半径不同,都会扩散
  •  int charLen = s.length();//源字符串的长度
     int length = charLen * 2 + 1;//添加特殊字符之后的长度
     char[] chars = s.toCharArray();//源字符串的字符数组
     char[] res = new char[length];//添加特殊字符的字符数组
     int index = 0;
     //添加特殊字符
     for (int i = 0; i < res.length; i++) {
         res[i] = (i % 2) == 0 ? '#' : chars[index++];
     }
    
     //新建p数组 ,p[i]表示以res[i]为中心的回文串半径
     int[] p = new int[length];
     //maxRight(某个回文串延伸到的最右边下标)
     //maxCenter(maxRight所属回文串中心下标),
     //resCenter(记录遍历过的最大回文串中心下标)
     //resLen(记录遍历过的最大回文半径)
     int maxRight = 0, maxCenter = 0, resCenter = 0, resLen = 0;
     //遍历字符数组res
     for (int i = 0; i < length; i++) {
         //合并后的代码
         p[i] = maxRight > i ? Math.min(maxRight - i, p[2 * maxCenter - i]) : 1;
         //上面的语句只能确定i~maxRight的回文情况,至于maxRight之后的部分是否对称,
         //就看是否需要一个个去匹配了,匹配的时候首先数组不能越界
         while (i - p[i] >= 0 && i + p[i] < length && res[i - p[i]] == res[i + p[i]])
             p[i]++;
         //匹配完之后,如果右边界i + p[i]超过maxRight,那么就更新maxRight和maxCenter
         if (i + p[i] > maxRight) {
             maxRight = i + p[i];
             maxCenter = i;
         }
         //记录最长回文串的半径和中心位置
         if (p[i] > resLen) {
             resLen = p[i];
             resCenter = i;
         }
     }
     //计算最长回文串的长度和开始的位置
     resLen = resLen - 1;
     int start = (resCenter - resLen) >> 1;
     //截取最长回文子串
     return s.substring(start, start + resLen);
    

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

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

相关文章

Drupal 远程代码执行 CVE-2019-6340 已亲自复现

Drupal 远程代码执行 CVE-2019-6340 已亲自复现 漏洞名称漏洞描述影响版本 漏洞复现环境搭建 修复建议总结 漏洞名称 漏洞描述 Drupal是一个开源且以PHP语言写成的内容管理框架(CMF) 研究人员发现Drupal存在安全漏洞(CVE-2019-6340)&#xff0c;当攻击者以PATCH或POST方式传送…

【数据结构】什么是树?

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 &#x1f4cc;树的定义 树(Tree)是n(n≥0)个结点的有限集.n0时称为空树. 在任意一颗非空树中: 有且仅有一个特定的称为根(Root)的结点;当n>1时,其余结点可分为m(m>0)个互…

flutter开发实战-设置bottomNavigationBar中间按钮悬浮效果

flutter开发实战-设置bottomNavigationBar中间按钮悬浮的效果 在使用tabbar时候&#xff0c;可以使用bottomNavigationBar来设置中间凸起的按钮&#xff0c;如下 一、效果图 中间按钮凸起的效果图如下 二、实现代码 我们使用BottomAppBar 一个容器&#xff0c;通常与[Sscaf…

AcWing算法提高课-1.4.2股票买卖 IV

算法提高课整理 CSDN个人主页&#xff1a;更好的阅读体验 原题链接 题目描述 给定一个长度为 n n n 的数组&#xff0c;数组中的第 i i i 个数字表示一个给定股票在第 i i i 天的价格。 设计一个算法来计算你所能获取的最大利润&#xff0c;你最多可以完成 k k k 笔交易…

百度网盘资源下载慢解决方法

1、使用百度网盘客户端&#xff0c;设置使用空闲带宽下载 亲测&#xff0c;可以一定程度上解决下载慢的问题&#xff0c;但是对于有些文件下载还是很慢就不清楚为什么了。 2、使用IDM进行下载 &#xff08;1&#xff09;、第一步下载和安装IDM 搜索后&#xff0c;普通下载后安…

JMeter---JSON提取器

JMeter的JSON提取器是一个用于从JSON响应中提取数据的元件。它可以从JSON响应中提取特定字段的值&#xff0c;并将这些值用于后续的测试步骤。 使用JSON提取器的步骤如下&#xff1a; 添加一个HTTP请求&#xff0c;用于获取包含JSON响应的数据。 在HTTP请求之后添加一个JSON提…

【Amazon 实验①】使用 Amazon CloudFront加速Web内容分发

文章目录 实验架构图1. 准备实验环境2. 创建CloudFront分配、配置动、静态资源分发2.1 创建CloudFront分配&#xff0c;添加S3作为静态资源源站2.2 为CloudFront分配添加动态源站 在本实验——使用CloudFront进行全站加速中&#xff0c;将了解与学习Amazon CloudFront服务&…

【Amazon 实验③】验证源请求策略将特定的请求信息转发至源站

文章目录 1. 使用源请求策略1.1 什么是源请求策略1.2 源请求策略和缓存策略的关系 2. 实验&#xff1a;验证源请求策略将特定的请求信息转发至源站 接上一篇文章【Amazon 实验②】使用缓存策略及源请求策略&#xff0c;用于控制边缘缓存的行为及回源行为&#xff0c;本篇文章主…

《每天一分钟学习C语言·七》指针、字节对齐等

1、 对于二维数组如a[3][4]可以当做有三个元素的一维数组&#xff0c;每个元素包含四个小元素。 2、 printf(“%-5d”, i); //负号表示左对齐&#xff0c;5d表示空五个光标的位置 3、 栈&#xff1a;先进后出&#xff0c;堆&#xff1a;先进先出 4、 &#xff08;1&#xff…

机器学习笔记(一)从波士顿房价预测开始,梯度下降

从波士顿房价开始 目标 其实这一章节比较简单&#xff0c;主要是概念&#xff0c;首先在波士顿房价这个问题中&#xff0c;我们假设了一组线性关系&#xff0c;也就是如图所示 我们假定结果房价和这些参数之间有线性关系&#xff0c;即: 然后我们假定这个函数的损失函数为…

罗德与施瓦茨FSV40-N手持式频谱分析仪

描述 R&S FSV是速度最快、功能最全面的信号和频谱分析仪&#xff0c;适用于从事RF系统开发、生产、安装和服务的注重性能、注重成本的用户。 频率范围高达3.6 GHz/7 GHz/13.6 GHz/30 GHz 40 MHz分析带宽 0.4 dB级测量不确定度&#xff0c;最高7 GHz 针对GSM/EDGE、WCDMA/…

面试高频的TCP知识点总结,比我想象得还要详细

下午好&#xff0c;我的网工朋友。 TCP 作为传输层的协议&#xff0c;了解它&#xff0c;拿下它&#xff0c;是一个网络工程师素养的体现&#xff0c;也是面试中经常被问到的知识点。 我们账号之前的文章里也写过不少关于TCP相关的文章&#xff0c;感兴趣的朋友们可以点击下方…

Tekton

一. 概念 Tekton 官网 Github Tekton 是一种用于构建 CI/CD 管道的云原生解决方案&#xff0c;它由提供构建块的 Tekton Pipelines&#xff0c;Tekton 作为 Kubernetes 集群上的扩展安装和运行&#xff0c;包含一组 Kubernetes 自定义资源&#xff0c;这些资源定义了您可以为…

Vue表格中鼠标移入移出input显示隐藏 ,有输入值不再隐藏

Vue表格中鼠标移入移出input显示隐藏 , 不再隐藏的效果 <el-tableref"table":data"tableDatas"borderstyle"width: 100%":span-method"arraySpanMethod"id"table"row-key"id"cell-mouse-enter"editCell&q…

Tarjan-vDCC,点双连通分量,点双连通分量缩点

前言 双连通分量是无向图中的一个概念&#xff0c;它是指无向图中的一个极大子图&#xff0c;根据限制条件可以分为边双连通分量和点双连通分量&#xff0c;欲了解双连通分量需先了解Tarjan算法&#xff0c;以及割点割边的概念及求解。本篇博客介绍点双连通分量的相关内容。 前…

简单了解一下当前火热的大数据 -- Kylin

神兽麒麟 一、Apache Kylin 是什么&#xff1f;二、Kylin架构结语 一、Apache Kylin 是什么&#xff1f; 由eBay公司中国团队研发&#xff0c;是一个免费开源的OLAP多维数据分析引擎优点 超快的响应速度&#xff0c;亚秒级支持超大数据集&#xff08;PB以上&#xff0c;千亿记…

数字人解决方案——ER-NeRF实时对话数字人模型推理部署带UI交互界面

简介 这个是一个使用ER-NeRF来实现实时对话数字人、口播数字人的整体架构&#xff0c;其中包括了大语言回答模型、语音合成、成生视频流、背景替换等功能&#xff0c;项目对显存的要求很高&#xff0c;想要达到实时推理的效果&#xff0c;建议显存在24G以上。 实时对话数字人 …

关于Python里xlwings库对Excel表格的操作(十六)

这篇小笔记主要记录如何【设置单元格数据的对齐方式】。 前面的小笔记已整理成目录&#xff0c;可点链接去目录寻找所需更方便。 【目录部分内容如下】【点击此处可进入目录】&#xff08;1&#xff09;如何安装导入xlwings库&#xff1b; &#xff08;2&#xff09;如何在Wps下…

MyBatis——MyBatis的延迟加载

MyBatis的延迟加载&#xff08;一对多查询案例&#xff09; 1.什么是延迟加载&#xff1f; 开启延迟加载后&#xff0c;在真正使用数据的时候才发起级联查询&#xff0c;不用的时候不查询。 2.pojo User类&#xff1a; package com.wt.pojo;import java.io.Serializable; …

Rust中peekable的使用

在 Rust 中&#xff0c;从迭代器中获取&#xff08;也就是“消费”&#xff09;一个元素时&#xff0c;每次调用 next 方法都会“消费”迭代器的一个元素&#xff0c;这意味着此元素被从迭代器中移除并返回给调用者&#xff0c; 一旦一个元素被消费&#xff0c;它就不能再次从同…