leetcode(四) 字符串专题

38.外观数列

题意很简单就是每次对上一个字符串计数
不能用递归,一开始用爆栈了
就用两层循环更新答案

在这里插入图片描述


class Solution {
public:
    string countAndSay(int n) {
        string res="1";                //最终答案,初始化 为1

        for(int i=0;i<n-1;i++){        //输入n应当更新n-1次
            string ans;                //每次更新的字符串

            for(int j=0;j<res.size();){
                
                char key=res[j];         //标记
                
                int count=0;             //计数

                while(res[j]==key){      //循环到第一个不等于key的字符
                    j++;

                    count++;

                }
                
                ans+=to_string(count)+key;
            }

            res=ans;                  //替换
        }

        return res;
    }
};

49.字母异位词分组 **

这题是哈希表的应用
我们建立这样一个从string映射到vector< string >的哈希表
将每个字符串排好序之后对应的字符串作为一个key,存放到哈希表的string中,并将对应的值存放到key对应的vector中

在这里插入图片描述

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        vector<vector<string>> res;

        unordered_map<string,vector<string>>  hash;

        for(auto str:strs){
            string key=str;                   

            sort(key.begin(),key.end());        //将映射的key进行排序

            hash[key].push_back(move(str));           //每个排完序相同的字符串都将存到一个key值的vector中  ,,使用move避免重新复制字符串,直接改变指针指向str
        }

        for(auto it:hash)res.push_back(move(it.second));  //vector存入res中

        return res;
    }
};

151.翻转字符串里的单词 **

这题要求翻转字符串中的单词,并且不准开额外的空间
基本的思路是先翻转字符串的每个单词,再翻转字符串整体,通过reverse函数,效果如下,即可实现整个字符串的翻转
具体做法是使用k,i,j三个指针,i,j先指向字符串的每个单词,使其先翻转,再使用s[k++]=s[i++]从头开始对s赋值(ij是前进指针,k是跟进指针)
要注意该题的空格在代码中的解决

在这里插入图片描述

在这里插入图片描述


class Solution {
public:
    string reverseWords(string s) {
    
    int k=0;                                //从s[0]开始赋值,避免开头有空格

    for(int i=0;i<s.size();i++){

        while(s[i]==' '&&i<s.size())i++;    //i前进到下一个不是空格的位置

        if(i==s.size())break;

        int j=i;

        while(s[j]!=' '&&j<s.size())j++;    //j前进到单词末尾,指向下一个空格

        reverse(s.begin()+i,s.begin()+j);   //翻转这个单词

        if(k)s[k++]=' ';                    //在每个单词结束加上一个空格,第一个除外,最后一个空格由于if(i==s.size())break也无法加入

        while(i<j)s[k++]=s[i++];

    }

    s.erase(s.begin()+k,s.end());      //收尾,删掉最后一个单词后面的空格   

    reverse(s.begin(),s.end());        //翻转整个字符串

    return s;
    }

 
};

165.比较版本号 ??

题目本身还是比较简单的,只不过在写法上需要点技巧和经验
思路:
小数点将每个字符串都分割成一段一段的数字,只要对应比较每一段数字大小就行。。这里用atoi函数转化成数字,手写也行
需要注意的是,如果两串分出的部分不一样多的话,(同长度的部分比较完依旧没有分出大小),那么继续比较,没有数的那个串看做是0就行了

  • 使用的函数:
  • substr(start,lenth), 截断字符串
  • atoi(char*[]),字符数组转换成数字,需要传字符数组首地址
  • c_str(null),将string转化成字符数组char* []并返回首地址

未解决:
while(x<s1.size()&&s1[x]!=’.’)x++;
while(y<s2.size()&&s2[y]!=’.’)y++; //交换&&的两个条件的顺序会爆溢出,不知道为什么

在这里插入图片描述

class Solution {
public:
    int compareVersion(string s1, string s2) {
        int i=0,j=0;

        while(i<s1.size()||j<s2.size())
        {
            int x=i,y=j;
              
              while(x<s1.size()&&s1[x]!='.')x++;    //每次找到一串数字的终点

              while(y<s2.size()&&s2[y]!='.')y++;

              int a= i==x?0:atoi(s1.substr(i,x-i).c_str());    //将每一段转化为数字,如果该段长度为0或者不存在则返回0

              int b= j==y?0:atoi(s2.substr(j,y-j).c_str());

              i=x+1,j=y+1;           //ij跳向'.'下一个位置

              if(a>b)return 1;

              if(a<b)return -1;
        }

        return 0;
    }
};

929.独特的电子邮件地址

思路:按照题目要求简化每一个所给的邮件地址,得到新的邮件地址加入哈希表中,最后返回哈希表元素个数(哈希表自带判重功能)

在这里插入图片描述

class Solution {
public:
    int numUniqueEmails(vector<string>& emails) {
        unordered_set<string> hash;     //无需映射的哈希表

        for(auto email:emails){
             int at=email.find('@');    //找到每个邮件地址@的位置

             string domain=email.substr(at+1);    //取出域名部分
             
             string name;
             for(auto it:email.substr(0,at)){     //简化本地名称部分
                 if(it=='+')break;

                 else if(it!='.')name+=it;        //去掉里面的'.'
             }

             string s=name+'@'+domain;          //构造简化后的邮件地址

             hash.insert(s);
        }

        return hash.size();
    }
};

5.最长回文子串**

该题使用暴力解法,直接枚举每一个字符,把他当做子串的对称中心点(如果最长回文子串是奇数长,则对称中心是一个点,偶数则是两个点)像两边延伸,找到最长的回文子串

在这里插入图片描述

class Solution {
public:
    string longestPalindrome(string s) {
        string  res;

        for(int i=0;i<s.size();i++){
        
            for(int m=i,n=i; m>=0 && n<s.size()&& s[n]==s[m]; m--,n++)     //找奇数对称最长串
                if(res.size()<n-m+1)res=s.substr(m,n-m+1);


            for(int m=i,n=i+1; m>=0 && n<s.size() && s[n]==s[m]; m--,n++)   //找偶数对称最长串
                if(res.size()<n-m+1)res=s.substr(m,n-m+1);
         }

        return res;
    }
};

6.Z字形变换

y总做法是找规律,感觉也就这样。。
首先我们按规律找寻每一行元素的下标,然后依次添进结果字符串res中
首先第一行和最后一行都是公差为2*(n-1)的数列
其余的行分别是交错的两个公差为2*(n-1)等差数列
他们的开头分别是i,2*(n-1)-i

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    string convert(string s, int n) {
         if(n==1)return s;

        string res;
         
         for(int i=0;i<n;i++){                //列举当前行
             if(!i||i==n-1)                   //列举如果是第一行或者最后一行的情况
                 for(int j=i;j<s.size();j+=2*(n-1))res+=s[j];    
             else{    //列举其他行(每行有两个等差数列)
                 for(int j=i,k=2*(n-1)-i;j<s.size()||k<s.size();j+=2*(n-1),k+=2*(n-1)){
                     if(j<s.size())res+=s[j];       //如果枚举在范围内,则加上该字符

                     if(k<s.size())res+=s[k];
                 }
             }
         }

         return res;
    }
};

附上自己的写法,想法很简单,就是按照要求吧字符串依次放进二维向量里再读出来,

  • 二维向量初始化没有给定长度,所以报了空指针(?)
class Solution {
public:
    string convert(string s, int numRows) {
        int n=s.size();

        vector<vector<char>>  res(numRows);

        int i=0;

        while(i<n){
            for(int j=0;j<numRows&&i<n;j++,i++)   //填满一列
                res[j].push_back(s[i]); 

            for(int k=numRows-2;k>0&&i<n;k--,i++)  //斜着向上填
                res[k].push_back(s[i]);

        }
        
        string ans;

        for(auto items:res){                        //二维向量遍历
            for(auto item:items){
                cout<<item<<" ";

                ans+=item;
            }
        }

        return ans;

    }
};


3.无重复字符的最长子串 (双指针)

思路:双指针算法

  • 前指针i,后指针j
  • 我们保证当前维护的子串(i,j)没有重复元素,当前子串不可能向前拓展(即i不能往前移动)
  • 依靠哈希表来判断维护的区间是否出现重复元素
  • 每次j向后移动,扩展最长子串,那么可能会出现重复的元素,为此需要不断向后移动i,直到满足没有重复元素为止
  • 更新最大子串

ps:如何满足i所在位置是当前最大子串位置?只需要i从头开始行进即可,不需要额外判断

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

菜鸡(O(n2))写法

class Solution {
public:

    const int N=200;

    int lengthOfLongestSubstring(string s) {

        int res=0;

        int flag[N];
        
        for(int i=0;i<s.size();i++){            //外层循环n次,内层循环每次最多走n步,所以复杂度是O(n ^ 2)
              memset(flag,0,sizeof(flag));

              int j;

              for( j=i;j<s.size()&&!flag[s[j]];j++)flag[s[j]]=true;
                  
                  
              res=max(res,j-i);
        }

        return res;
    }
};

O(n)写法

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char, int> hash;
        int res = 0;
        for (int i = 0, j = 0; j < s.size(); j ++ )   //时间复杂度:虽然是双重循环,但由于i最多总共只向后走n步,平均到每一层循环走一步,那么时间复杂度是O(n)
        {
            hash[s[j]] ++ ;
            while (hash[s[j]] > 1) hash[s[i ++ ]] -- ;  //如果出现重复元素,让i往后走直到没有重复元素为止
            res = max(res, j - i + 1);
        }
        return res;
    }
};

208.实现Trie 树

目标是使用结构体的方式实现一个字典树,基础算法里实现过一个二维数组的Trie树,基本思路相同
首先初始化一个根节点root,根节点不存放数据,根节点存在26个子节点指针

  • 插入:对于每个字符串,遍历插入树中,如果该节点不存在则初始化一个节点
    并将结尾Node的is_end赋值为true
  • 查询:遍历当前字符串到其结尾,如果is_end存在则代表有该字符串存在
  • 查询前缀:只要Trie树中存在前缀字符串即可

在这里插入图片描述


class Trie {
public:
    
    struct  Node{
        bool is_end;

        Node* son[26];
        
        Node(){
            is_end=false;

            for(int i=0;i<26;i++)son[i]=NULL;     
        }
    }*root;  //root指向结构体Node

    /** Initialize your data structure here. */
    Trie() {
        root=new Node();
    }
    
    /** Inserts a word into the trie. */
    void insert(string word) {
        auto p=root;

        for(auto s:word){
            int u=s-'a';

            if(!p->son[u])p->son[u]=new Node();   //如果子节点不存在则初始化一个节点

            p=p->son[u];
        }
        p->is_end=true;
    }
    
    /** Returns if the word is in the trie. */
    bool search(string word) {
        auto p=root;

        for(auto s:word){
            int u=s-'a';

            if(p->son[u])p=p->son[u];

            else return false;
        }

        return p->is_end;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) {
        auto p=root;

        for(auto s:prefix){
            int u=s-'a';

            if(p->son[u])p=p->son[u];

            else return false;
        }

        return true;
    }
};


273.整数转化英文表示

这题不注意写法确实麻烦的一批,看了一下好像也有拿递归写的也还不错,但还是这个容易理解一点
思路:转化为每三个一位每三个一位的数,并且逐段翻译,每段中间加上相应的计数(billion,million,thousand)

在这里插入图片描述

class Solution {
public:

    //映射到字符串数组中
    string small[20]={"Zero","One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten",
        "Eleven","Twelve","Thirteen","Fourteen","Fifteen","Sixteen","Seventeen","Eighteen","Nineteen"};

    string decade[10]={"","","Twenty","Thirty","Forty","Fifty","Sixty","Seventy","Eighty","Ninety"};

    string  big[4]={"Billion","Million","Thousand",""};
    
   
    string numberToWords(int num) {
        if(!num)return small[0];

        string res;

        for(int i=1000000000,j=0;i>0;i=i/1000,j++){   //从最高三位开始转换,每次转三位,最多四次

            int n=num/i;

            if(n)res+=helper(n)+big[j]+" ";

            num%=i;   
        }

        while(res.back()==' ')res.pop_back();   //删除最后字符串的多余空格 

        return res;
    }

    string helper(int n){                     //每段的三位数转化为英文
        string res;

        if(n>=100){
            res+=small[n/100]+" Hundred ";

            n=n%100;
        }

        if(!n)return res;

        if(n>=20){
            res+=decade[n/10]+" ";

            n=n%10;

            if(!n)return res;

            res+=small[n]+" ";
        }

        else res+=small[n]+" ";

        return res;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值