leetcode-最长回文子串/有效的括号/发糖果问题/乘法表中第k小的数/顺时针旋转二维矩阵90度

本文详细介绍了LeetCode中的几个经典问题,包括最长回文子串、有效括号、发糖果问题、乘法表中第k小的数以及顺时针旋转二维矩阵90度的解决方案。针对每个问题,提供了多种算法思路,如暴力解决、动态规划、Manacher法、中心扩展法、栈的应用等。这些方法涵盖了不同的时间复杂度和空间复杂度,有助于提升算法理解和实现能力。
摘要由CSDN通过智能技术生成

一 最长回文子串

 

法一 暴力解决

依次遍历每个字母,判断以该字母开头的所有子串是否是回文串,并更新起始字母和最大长度。

    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&&center2<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度

题目链接:https://leetcode-cn.com/problems/rotate-image/description/?utm_source=LCUS&utm_medium=ip_redirect_q_uns&utm_campaign=transfer2china

法一 找规律

二维矩阵可以看作一层包围另一层而组建起来的,那么在旋转的时候可以先旋转最外层,然后依次旋转里层,直至所有层都旋转完毕。

通过自己旋转之后发现规律,元素旋转前后下标转换规律为:

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)

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值