【今日算法】LeetCode 5.最长回文子串 和 287.寻找重复数

文章目录

  • 最长回文子串
    • 题目描述
    • 思路分析
      • 解法一:暴力枚举(会超时)
      • 解法二:动态规划
    • 代码书写
      • 解法一:暴力枚举
      • 动态规划
  • 寻找重复数
    • 题目描述
    • 思路分析
    • 代码书写

最长回文子串

题目描述

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

  • 示例1:

输入:s = "babad"

输出:"bab"

解释:"aba" 同样是符合题意的答案。

  • 示例2:

输入:s = "cbbd"

输出:"bb"

思路分析

本题是让我们寻找最长的回文子串,那么我们想一想什么是回文串:

  • 所谓回文串就是双指针一个从前遍历,一个从后遍历,如果每一个位置的值都相等,那么这个字符串就叫做回文串
  • 单个字符也叫回文串
  • 两个连续的相等字符也叫回文串

解法一:暴力枚举(会超时)

那么根据上面对回文串的分析,我们首先想到双指针遍历,判断该串是否为回文串,left指针从前面开始,right指针从后面开始,比较每一个位置字符是否相等,如果不相等那么该字符串就不是回文串,如果两个指针相遇了,那么就判断该字符串是一个回文子串;或者left+1>=right并且left位置的值等于right位置的值,那么这个字符串也是一个回文串,所以我们需要写一个判断字符串是否是回文串的函数

然后我们就需要定义双指针去遍历整个字符串,但是这里虽然看似是双指针对撞,但是双指针对撞并不能解决问题,因为会遗漏很多情况,所以我们必须使用循环遍历,这样的话时间复杂度就到了O(N^2),与此同时我们判断字符串是否是回文是一个O(N)的时间复杂度,这样算下来,整个算法的时间复杂度就到达了N的三次方,显然是一个很差劲的算法,但是这个思路对于我们来说有很大的启发意义

解法二:动态规划

那么对于动态规划的问题我们一般遵循四个步骤:

  • 状态表示
  • 状态转移方程
  • 初始化
  • 填表顺序

根据上面解决动态规划问题的四个步骤,我们来思考一下为什么能够用动态规划去解决问题:

  • 首先我们来看回文串的定义:从前往后与从后往前字符串相等,那么如果我们定义一个字符数组,那么这个关系就可以表示为dp[i]==dp[j],i<=j,此时我们发现这似乎就是一个状态转移方程;
  • 与此同时我们发现我们可以用一个二维数组去表示i-j位置是否能形成回文串,如果能形成回文串,那么我们就将这个回文串的长度填写到这个二维数组中去,此时我们发现这似乎就是一个状态表示
  • 最后我们来看这么一个问题,如果我们要判断i-j位置的字符串是否是回文串,那么i+1-j-1位置必定是一个回文串,换句话说就是,如果s[i]==s[j],并且dp[i+1][j-1]!=0,那么我们就能够知道i-j必定是一个回文串,此时这个回文串的长度只需要让dp[i+1][j-1]+2即可,如果我们能够画出这个二维数组,那么我们能够看到dp[i+1][j-1]一定在dp[i][j]的左下角,所以我们这个dp表的填写顺序一定是从下往上

根据上面的分析,我们不难发现,这道题一定可以用动态规划来解答,但这里还有一个小小的优化,我们发现i位置一定是在j位置的前面,也就是说,我们只需要关注dp表一半的元素即可

我们来理一下思路:

  • 首先我们需要创建一个名为dp的二维数组,并把数组的元素初始化为0,将来这个二维数组里面的值表示i-j位置回文串的长度
  • 其次,我们需要从下往上填写dp数组,如果i+1>=j,那么dp[i][j]=j-i+1;如果不满足的话,该位置的值一定可以用dp[i+1][j-1]+2去表示,但是前提条件是dp[i+1][j-1]!=0
  • 最后我们需要更新最长数组的值,并记录起始位置,方便我们后面截取子串并返回

代码书写

解法一:暴力枚举

cpp 复制代码
class Solution {
public:
    bool isPalindrome(string s,int left,int right)
    {
        while(left<=right)
        {
            if(s[left]!=s[right]){return false;}
            ++left;
            --right;
        }
        return true;
    }
    string longestPalindrome(string s) {
        int n=s.size();
        int maxLen=0;
        int start=0;
        for(int i=0;i<n;++i)
        {
            for(int j=i;j<n;++j)
            {
                if(j-i+1<=maxLen){ continue;}
                if(isPalindrome(s,i,j))
                {
                    maxLen=j-i+1;
                    start=i;
                }
            }
        }
        return s.substr(start,maxLen);
    }
};

动态规划

cpp 复制代码
class Solution {
public:
    string longestPalindrome(string s) {
        int n=s.size();
        vector<vector<int>> dp(n,vector<int>(n,0));
        int maxLen=0;
        int start=0;
        for(int i=n-1;i>=0;--i)
        {
            for(int j=i;j<n;++j)
            {
                if(s[i]==s[j])
                {
                    if(i+1>=j){dp[i][j]=j-i+1;}
                    else if(dp[i+1][j-1]){dp[i][j]=dp[i+1][j-1]+2;}
                }
                if(maxLen<dp[i][j]){
                    maxLen=dp[i][j];
                    start=i;
                }
            }
        }
        return s.substr(start,maxLen);
    }
};

寻找重复数

题目描述

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。

你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

  • 示例1:

输入:nums = [1,3,4,2,2]

输出:2

  • 示例2:

输入:nums = [3,1,3,4,2]

输出:3

  • 示例3:

输入:nums = [3,3,3,3,3]

输出:3

思路分析

本题我们需要抓住一个关键的点,就是数组的元素个数是n+1,数组中元素的范围都是[1,n],也就是说数组下标的范围是[0,n],我们可以惊奇的发现:数组中元素的范围与数组下标的范围是相同的

那么为了维持这样的数组的存在,数组中必定至少会存在两个相同的元素

根据上面的分析,我们这里可以定义两个变量,这两个变量的值是将数组的元素作为下标,得到的,我们只需要让这两个变量去遍历整个数组,然后判断这两个位置的值是否相等,如果相等,那么此时的下标就是重复的元素。

那么此时的问题就是我们该如何去遍历?如果两个变量都从最开始去遍历,那么必然会导致它们两个变量所对应的值处处相等,所以我们必须让两个变量从不同的位置开始遍历

那么我们该如何让两个变量从不同的位置开始遍历呢?我们可以让一个变量向后移动一次,另一个变量向后移动两次,由于有相同元素的存在,它们两个变量的值一定会相等,等它们两个相等后,让其中一个变量从头开始,另一个变量从当前位置开始遍历,这样等他们再次相遇的时候,所对应的数组下标就是相同的元素

代码书写

cpp 复制代码
class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int fast=nums[nums[0]];
        int slow=nums[0];
        while(fast!=slow)
        {
            fast=nums[nums[fast]];
            slow=nums[slow];
        }
        slow=0;
        while(fast!=slow)
        {
            fast=nums[fast];
            slow=nums[slow];
        }
        return slow;
    }
};
相关推荐
ChoSeitaku2 小时前
28.C++进阶:map和set封装|insert|迭代器|[]
java·c++·算法
月挽清风2 小时前
代码随想录第六天:哈希表
算法·leetcode
张祥6422889042 小时前
GNSS单点定位方程推导笔记
人工智能·算法·机器学习
吴秋霖2 小时前
某网站x-s补环境(Cursor版)
算法·js逆向·cursor·补环境
炽烈小老头2 小时前
【每天学习一点算法 2026/01/20】汉明距离
学习·算法
夏鹏今天学习了吗2 小时前
【LeetCode热题100(86/100)】最长有效括号
算法·leetcode·职场和发展
踩坑记录2 小时前
leetcode hot100 73.矩阵置零 medium
leetcode
小龙报2 小时前
【初阶数据结构】解锁顺序表潜能:一站式实现高效通讯录系统
c语言·数据结构·c++·程序人生·算法·链表·visual studio
有一个好名字2 小时前
力扣-删除二叉搜索树的节点
算法·leetcode·职场和发展