一 最长回文子串
法一 暴力解决
依次遍历每个字母,判断以该字母开头的所有子串是否是回文串,并更新起始字母和最大长度。
string longestPalindrome(string s) {
int len=s.size();
if(len==0)
return "";
if(len==1)
return s;
int pos=0,maxlen=1; //注意初始的长度为1,因为至少是1
for(int i=0;i<len;i++)
{
for(int j=i+1;j<len;j++)
{
int left=i;
int right=j;
for(;left<right;left++,right--)
{
if(s[left]!=s[right])
break;
}
if(left>=right)
{
if(j-i+1>maxlen)
{
maxlen=j-i+1;
pos=i;
}
}
}
}
return s.substr(pos,maxlen);
}
时间复杂度是O(n^3)
法二 动态规划
令dp[i][j]表示str(i,j)是不是回文子串,如果是,置1。
其状态转移方程是
dp[i][j]=dp[i+1][j-1] if(dp[i+1][j-1]==1&&s[i]==s[j])
dp[i][j]=0 s[i]!=s[j]
初始化状态为
dp[i][i]=1;
dp[i][i+1]=1; (s[i]=s[i+1])
string longestPalindrome(string s) {
int len=s.size();
if(len==0)
return "";
if(len==1)
return s;
int dp[len][len];
int pos=0,maxlen=1;
//初始化
for(int i=0;i<len;i++)
{
dp[i][i]=1;
if(i<len-1)
{
if(s[i]==s[i+1])
{
dp[i][i+1]=1;
pos=i;
maxlen=2;
}
}
}
for(int l=3;l<=len;l++)
{
for(int i=0;i+l-1<len;i++)
{
int j=i+l-1;
if((s[i]==s[j])&&(dp[i+1][j-1]==1))
{
dp[i][j]=1;
pos=i;
maxlen=l;
}
}
}
return s.substr(pos,maxlen);
}
时间复杂度是O(n^2)
法三 Manacher法
法四 中心扩展法
所谓中心扩展法,就是说回文中心两侧互为镜像,通过试探所有可能的回文中心求出其对应的回文长度然后比较,即可得出最终答案。
那么如何确定可能的回文中心?回文中心有多少个呢?
对于abcba这样的字符串来说,其最终的答案是abcba,回文中心是c
对于abccba这样的字符串来说,其最终的答案是abccba,回文中心是cc
因此,回文中心有可能是一个字符也有可能是2个字符。
那么对于abcba这样的字符串要试探的回文中心就是a b c b a ab bc cb ba 共5+4=9=n+n-1
所以回文中心的个数是2n-1
class Solution {
public:
//计算以center1,center2为中心,s的最大回文子串
int centerExpand(string s,int center1,int center2)
{
while(center1>=0&¢er2<s.size()&&s[center1]==s[center2])
{
//如果满足条件,开始向2边扩展
center1--;
center2++;
}
return center2-center1-1;
}
string longestPalindrome(string s) {
if(s.size()==0||s.size()==1)
return s;
int center=0;
int maxlen=0;
for(int i=0;i<s.size();i++)
{
//当最长回文子串是奇数时,以下标i为中心的最长回文子串长度是
int len1=centerExpand(s,i,i);
//当最长回文子串是偶数时,以下标i和i+1为中心的最长回文子串长度
int len2=centerExpand(s,i,i+1);
//取最大长度
int len=len1>len2?len1:len2;
if(len>maxlen)
{
center=i;
maxlen=len;
}
}
//返回最长回文子串,对奇偶都适用
return s.substr(center-(maxlen-1)/2,maxlen);
}
};
二 有效的括号
题目链接:https://leetcode-cn.com/problems/valid-parentheses/
自力更生1
class Solution {
public:
bool isValid(string s) {
if(s.size()==0)
return true;
if(s.size()%2)
return false;
int left=0;
int right=0;
for(int i=left;i<(s.size());)
{
if(s[i]=='('||s[i]=='['||s[i]=='{')
{
left=i++;
}
else if(s[i]==']'&&s[left]!='[')
return false;
else if(s[i]=='}'&&s[left]!='{')
return false;
else if(s[i]==')'&&s[left]!='(')
return false;
else
{
s.erase(left,2);
i=0;
}
}
if(s.size()!=0)
return false;
else
return true;
}
};
1 首先判断是否是空字符串
2 再判断是否括号个数是奇数
3 从左到右遍历,记录连续出现左括号的最后一个,当不是左括号时,看这个括号和前一个是不是一对,如果不是直接false否则把这2个字符都删掉,再从头遍历
4 最后判断字符串是不是为0 不是返回false
自力更生2
class Solution {
public:
bool isValid(string s) {
if(s.size()==0)
return true;
if(s.size()%2)
return false;
while(s.find("{}")!=-1||s.find("[]")!=-1||s.find("()")!=-1)
{
if(s.find("{}")!=-1)
s.replace(s.find("{}"),2,"");
if(s.find("[]")!=-1)
s.replace(s.find("[]"),2,"");
if(s.find("()")!=-1)
s.replace(s.find("()"),2,"");
}
if(s.empty())
return true;
else
return false;
}
};
思路一目了然,但是运行了300+ms,太慢了。
借鉴他人:
class Solution {
public:
bool isValid(string s) {
if(s.size()==0)
return true;
if(s.size()%2)
return false;
stack<char> sk;
for(int i=0;i<s.size();i++)
{
if(s[i]=='['||s[i]=='{'||s[i]=='(')
sk.push(s[i]);
else
{
if(sk.empty())
return false;
else if(s[i]==']'&&sk.top()!='[')
return false;
else if(s[i]==')'&&sk.top()!='(')
return false;
else if(s[i]=='}'&&sk.top()!='{')
return false;
else
sk.pop();
}
}
if(sk.empty())
return true;
else
return false;
}
};
运用到栈的思想
1 同样先判断空串和字符串长度
2 碰到左括号入栈,否则比较右括号和栈顶是否配对,不配对返回false 否则栈顶出栈
3 如果栈空返回true
三 发糖果问题
原题
题目连接:https://leetcode-cn.com/problems/candy/
思路:贪心。
(1)初始化每个人糖果为1
(2)从左到右遍历,如果当前比左边分数高,糖果数为左边糖果数加1
(3)从右到左遍历,如果当前比右边分数高且糖果数小于等于右边,糖果数为右边加1
class Solution {
public:
int candy(vector<int>& ratings) {
//初始化为1
vector<int> res(ratings.size(),1);
//从左往右遍历,如果当前分数比左边分数高,则糖果数为左边加1
for(int i=1;i<res.size();i++)
{
if(ratings[i]>ratings[i-1])
res[i]=res[i-1]+1;
}
//从右往左遍历,如果当前比右边分数高,且糖果数小于等于右边,则糖果数为右边加1
for(int i=res.size()-2;i>=0;i--)
{
if(ratings[i]>ratings[i+1]&&res[i]<=res[i+1])
res[i]=res[i+1]+1;
}
int sum=0;
for(int i=0;i<res.size();i++)
{
sum+=res[i];
}
return sum;
}
};
变种
这个题目有点难受,因为是一个环,所以如果按照上述做法估计要无限循环下去。
思路,下标也搞成循环状:对于index的左边
index_left=(index-1+n)%n;
对于index的右边
index_right=(index+1)%n;
然后遍历一遍分数数组,如果存在某人比左右都低(小于等于),某人糖果数为1。
然后遍历糖果数组,对于没有确认的糖果数目采用记忆化搜搜的形式确定糖果数目。
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
const int maxn=100;
//糖果数组,初始化为0,表示还未确定呢
vector<int> dp(maxn,0);
vector<int> score(maxn);
void dfs(int n,int k)
{
int pre=(k-1+n)%n;
int next=(k+1)%n;
//如果左边还没确定而且分数比左边高
if(!dp[pre]&&score[pre]<score[k])
dfs(n,pre);
//如果右边还没确定而且分数比右边高
if(!dp[next]&&score[next]<score[k])
dfs(n,next);
//确定完之后,有的依然没有确定,但是不影响结果
if(score[pre]<score[k])
dp[k]=max(score[pre]+1,dp[k]);
if(score[next]<score[k])
dp[k]=max(score[next]+1,dp[k]);
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>score[i];
}
//先遍历数组,如果存在分数小于等于左边又同时小于等于右边的情况,初始化为1
for(int i=0;i<n;i++)
{
int pre=(i-1+n)%n;
int next=(i+1+n)%n;
if(score[i]<=score[pre]&&score[i]<=score[next])
dp[i]=1;
}
//遍历数组,如果糖果数还没有确定,就采取记忆化搜索
for(int i=0;i<n;i++)
{
if(!dp[i])
dfs(n,i);
}
int sum=0;
for(int i=0;i<n;i++)
{
sum+=dp[i];
}
cout<<sum;
return 0;
}
四 乘法表中第k小的数
乘法表中第k小的数
题目链接:https://leetcode-cn.com/problems/kth-smallest-number-in-multiplication-table/
二分查找:查找不是下标,而是值的查找。
注意边界信息。
class Solution {
public:
int findKthNumber(int m, int n, int k) {
if(k==1)
return 1;
if(k==m*n)
return m*n;
int left=1;
int right=m*n;
while(left<right)
{
int mid=left+(right-left)/2;
int ctl=0;
for(int i=1;i<=m;i++)
{
ctl+=min(mid/i,n);
}
if(ctl<k)
left=mid+1;
else
right=mid;
}
return left;
}
};
class Solution {
public:
//Binary Search for value
//跟普通二分不一样 普通二分把下标来当作边界,而这里的二分则是把值来当作边界
int fun(int m, int n, int num) {//函数功能:获得在m*n的乘法表中,找出有多少个值 <= num。返回满足条件的值的数量
int count = 0;
for(int i = 1; i<=m; ++i) {//行从第一行开始
count += min(num/i, n);//此表达式的含义:num这个值在当前第i行,有多少个值不比它大(<=num的个数)
}
return count;
}
int findKthNumber(int m, int n, int k) {
if(k == 1) return 1;
if(k == m*n) return m*n;
int left = 1, right = m*n, mid;//值来当作边界,乘法表(m*n)最小是1,最大是m*n
while(left < right) {
mid = (left+right) >> 1;
int temp = fun(m, n, mid);//得到在乘法表中 值 <= mid 的数量
if(temp < k) {
left = mid+1;//如果temp < k, 说明当前mid这个值在目标值的左边(比目标值小),所以可以缩小边界
}
else right = mid;//temp >= k, 在temp>k时,为什么不取 right = mid-1,而是right = mid。因为我们的目标值可能存在重复,比如 123334,如果我选择要找第3小的数,而mid当前恰好=3,那么temp得到的结果会是5(<=mid)。如果我们选择right = mid-1=2。那么将会运行错误导致结果错误。在temp = k时,为什么不能立马返回结果呢,而是继续运行缩小边界?因为我们当前的mid可能是一个不在乘法表中的值,毕竟mid=(left+right) >> 1; 所以立即返回可能返回错误答案。所以一定收缩范围 直到left=right。最终返回的一定是正确值(若答案t的temp = k, 而某一非表值x的temp也=k, 那么t一定比x小,最终x也会被right缩小导致出局)。
}
return left;
}
};
五 顺时针旋转二维矩阵90度
法一 找规律
二维矩阵可以看作一层包围另一层而组建起来的,那么在旋转的时候可以先旋转最外层,然后依次旋转里层,直至所有层都旋转完毕。
通过自己旋转之后发现规律,元素旋转前后下标转换规律为:
vt[i][j]=vt[j][n-1-i] n为方阵的维度
可以旋转的次数为即层数为
for(int i=0;i<n/2;i++)
旋转的时候存在交换,替换,因为顺时针旋转,所以进行逆时针交换,同时要注意,因为是逆时针交换,所以关系要倒过来,比如
原来,vt[n-j-1][i]旋转到vt[i][j]上。vt[i][j]旋转到vt[j][n-1-i]位置上。
那么在替换的时候,vt[i][j]要替换掉vt[n-j-1][i]。
同时内层的转换元素个数为该层方阵维数-1
for(int j=i;j<n-i-1;j++)
代码示例:
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
void Rotate(vector<vector<int> >&vt)
{
int m=vt.size();
for(int i=0;i<m/2;i++)
{
for(int j=i;j<m-1-i;j++)
{
int temp=vt[i][j];
vt[i][j]=vt[m-1-j][i];
vt[m-1-j][i]=vt[m-1-i][m-1-j];
vt[m-1-i][m-1-j]=vt[j][m-1-i];
vt[j][m-1-i]=temp;
}
}
}
int main()
{
int m;
cin>>m;
vector<vector<int> > vt(m,vector<int> (m));
for(int i=0;i<m;i++)
{
for(int j=0;j<m;j++)
{
cin>>vt[i][j];
}
}
//反转二维数组
Rotate(vt);
for(int i=0;i<m;i++)
{
for(int j=0;j<m;j++)
{
cout<<vt[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
法二
步骤一:先调换行,即如果有4行,第一行与最后一行调换,第2行与第三行调换
步骤二:转置矩阵。
void Rotate(vector<vector<int> >&vt)
{
int m=vt.size();
//调换行
for(int i=0;i<m/2;i++)
{
swap(vt[i],vt[m-1-i]);
}
//转置
for(int i=0;i<m;i++)
{
for(int j=i;j<m;j++)
{
swap(vt[i][j],vt[j][i]);
}
}
}
法一的空间复杂度是O(1)
法二的空间复杂度是O(n)