文章目录
最长回文子串
题目描述
给你一个字符串 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;
}
};