leetcode-字符串

news/2024/6/17 13:57:21 标签: leetcode, 算法, 职场和发展

1.反转字符串LeetCode344. 20230911

难度为0,此处就不放代码了

  1. 注意reverse和swap等一系列字符串函数什么时候该用,记一记库函数
  • swap可以有两种实现,涨知识了,除了temp存值还可以通过位运算:s[i] ^= s[j]; s[j] ^= s[i]; s[i] ^= s[j];
  • 2.反转字符串LeetCode541. 20230917

    这一题做的时候其实有点奇怪,想了好多方法都被自己否掉,然后看题解发现for里面i+=2k才恍然大悟,又看到reverse里面用了s.begin()+i这个用法,这才明白为啥本题本来很简洁被我写得那么复杂

    这个题在今天再重拾的时候觉得完全不用自己想,就按部就班地按着题意写就行了

    然后看到还有一个reverse的用法是reverse(s,i,i+k),第一个而是字符串,第二个和第三个是一个左闭右开的区间

    在这里插入图片描述

    3.替换空格 剑指Offer 05. 20230917

    自己做的:开了一个string,if else地一个个字符加进去了。

    卡尔说的:可以先用resize函数将s先变长到最终的长度,然后用双指针法一个一个从后往前填字符。但是在写的时候犯了一个很蠢的错误,resize之后的长度已经变了,我还在用s.length()表示旧的,s.length()+ 2 *num 表示新的。下面是正确的代码。

    class Solution {
    public:
        string replaceSpace(string s) {
            int spacenum = 0;
            for(char i : s){
                if(i == ' ') 
                    spacenum++;
            }
            int size = s.length();
            int left = size - 1;
            
            s.resize(s.length() + 2*spacenum);      
            
            for(int right = size + 2*spacenum - 1; right > 0; right--){
                if(s[left] != ' '){
                    s[right] = s[left];
                    left--;
                }
                else{
                    s[right] = '0';
                    s[right - 1] = '2';
                    s[right - 2] = '%';
                    left--;
                    right -= 2;
                }
            }
            return s;
        }
    };

    4.翻转字符串里的单词 LeetCode 151. 20230919-10/03

    思路还是蛮简单,就是写的时候老转不过弯。

    1. split函数似乎可以实现分割string里面的单词,不过C++里好像没有

    2. 学习了一下erase函数(没用到):C++标准库中的erase函数通常用于从容器(例如std::vectorstd::stringstd::list等)中删除元素,例如

    std::vector<int> myVector = {1, 2, 3, 4, 5}; // 删除第三个元素(索引为2) myVector.erase(myVector.begin() + 2);
    std::string myString = "Hello, World!"; // 删除从索引6开始的5个字符 
    
    myString.erase(6, 5);
    std::vector<int> myVector = {1, 2, 3, 4, 5, 6}; // 删除第二个到第四个元素 
    
    myVector.erase(myVector.begin() + 1, myVector.begin() + 4);

    然而,erase背后的实现是O(N)的时间复杂度,如果外面再套一个for循环就是O(N²)的复杂度。

    3. 本题写的代码,显示自己定义一个闭区间倒置函数,然后经过两次倒转就可以。里面细节非常多,什么时候+1,什么时候到末尾要判断,什么时候-1,都很凌乱

    class Solution {
    public:
        void m_reverse(string &s, int a, int b){
            for(int i = 0; i < (b - a + 1)/2; ++i){
                swap(s[a + i], s[b - i]); 
            }
        }
    
        string reverseWords(string s) {
            //去除空格
            int fast = 0, slow = 0;
            while(fast < s.length()){
                if(s[fast] != ' ')
                    s[slow++] = s[fast++];
                else if(slow != 0)
                {
                    while(s[fast] ==' ' && fast < s.length()){
                        fast++;
                    }
                    if(fast != s.length())
                        s[slow++] =' ';
                }
                else{
                    fast++;
                }
            }
            s.resize(slow);
    
            //全倒
            reverse(s.begin(),s.end());
    
            //闭区间m_reverse
            for(int i = 0, j = 0; i < s.length(); ++i){
                while(s[i] != ' ' && i < s.length())
                    ++i;
                m_reverse(s, j, i - 1);
                cout<<i<<" "<<j<<endl;
                cout<<s<<endl;
                j = i + 1;
            }
    
            return s;
        }
    };

    5.左旋转字符串 剑指Offer 58 - II 20230917

    简单的做法就是使用substr函数,注意substr的第二个参数是“多长”而不是“到哪”。

    string substr1 = s.substr(0, n);
    s = s.substr(n, s.length() - n)+ substr1;
    return s;

    然后还有原地旋转的思路:

    先局部反转,再整体反转,很妙

            reverse(s.begin(), s.begin() + n);
            reverse(s.begin() + n, s.end());
            reverse(s.begin(), s.end());

    里面两个注意点:

    一个是reverse()里面的两个参数:

    • 首先,std::vector<int> vec = {1, 2, 3, 4, 5}; auto it = vec.begin() + 2; // it指向第三个元素,即3 std::cout << *it << std::endl; // 输出 3 因为vec.begin()指向第一个元素,也即vec.begin()+0,那么+2就是指向第三个元素
  • 其次,如果是reverse(vec.begin(), vec.begin() + 2);要记住reverse的第一个参数是第一个元素,第二个参数是最后一个元素的下一个元素。也即,虽然begin()指的是1,begin()+2指的是3,但是反转的是1,2此处反转成为{2,1,3,4,5}
  • 另一个是reverse、substr的内部实现,及它们的时间复杂度

    为什么要看内部实现,因为做题的时候会想着要把时间复杂度降低,但是使用库函数有的时候就会忽略掉本身这个函数自己实现的时候用的时间复杂度,所以在这里简单看一下

    reverse: 源码实现是while ((first!=last)&&(first!=--last)) { std::iter_swap (first,last); ++first; },也就是只遍历一遍即可,而swap是之前讲过的,要么是疑惑,要么是temp存,总之是用temp和数组定位。最终合起来的结果也就是遍历一遍,对每个元素O(1)的时间复杂度,最后是O(N)。

    substr: 实现思路为创建一个新的字符串对象,将原始字符串中的子字符串复制到新的字符串中。复制的时间复杂度与子字符串的长度成正比,因此是 O(N)。

    6.找出字符串中第一个匹配项的下标-KMP LeetCode28. 20231019

    KMP居然是简单题吗,好吧

    将近一个月没写。

    这道题连着看了得有好几天了,但是依然不太会。今天终于约摸着把next数组和利用next数组进行匹配的代码写出来了,但是其实还是没有完全理解。

    
     
     
    class Solution {
    public:
        int strStr( string haystack, string needle) {
        //next数组
        int length = needle. length();
        int next[length];
        next[ 0] = 0; //
        int j = 0; // 前缀末尾
        // 求next数组
        for( int i = 1; i < length; ++i){
            while(j > 0 && needle[i] != needle[j]){
                j = next[j- 1];
            }
            if( needle[i]== needle[j]){
                j++;
            }
            next[i] = j;
        }
        int n = 0;
        //用next数组去做匹配
        for( int m = 0; m < haystack. length(); ++m){  
            while(n > 0 && haystack[m] != needle[n])
                n = next[n- 1];      
            if( haystack[m] == needle[n])
                n++;
            if(n == length)
                return m - length + 1;
        }
        return - 1;
        }
    };

    aabaa前缀(不带末尾):针对a为空;针对aa为a;针对aab为a,aa;针对aaba为a,aa,aab;针对aabaa为a,aa,aab,aaba

    后缀(不带头):针对a为空;针对aa为a;针对aab为b,ab;针对aaba为a,ba,aba;针对aabaa为a,aa,baa,abaa


    首先是求next数组。

    1)初始化的时候,next数组第一个必为0,这是因为位置0前后缀不可能相等(后缀位置0没有)。

    2)然后开始从i=1挨个填next数组,在填这个数组的时候就已经用到了kmp思想了。具体表现为填next[i]的时候,j也不是一直从头开始匹配的。j先为0,如果needle串s的s[i]!=s[j],此时说明j要往前移动,但是只移到next[j-1]那边(注意点:while、>0);如果needle串的s[i]==s[j],就j++;然后,next[i]=j,进行到next[i+1]。

    然后是利用next数组进行匹配。

    这个地方也就是遍历haystack串,与模式串匹配就两个指针都自动后移,不匹配就模式串的指针往前移(依旧是while、>0注意),然后这个地方还有一个根据题意返回一下结果。

    这个题让我自己想是完全不可能想出来的,感觉以后还得复习三遍才能记住

    7. 重复的子字符串 LeetCode 459. 20231019

    第一种解法的思想是两个s拼接起来如果去头去尾还含有s,那么s是可以由重复子串构成的。

    理解:

    [xx {xxxx][xx} xxxx] 如果{xxxxxx}=[xxxxxx],证明[xx={xx;{xx=xx];xx]=[xx};而[xx}=[xx,所以

    [xx={xx=xx]=[xx},所以xx为[xxxxxx]可用来重复的子串。大概这么理解吧,不再看严格数学证明。

    里面有两个亮点:erase函数删除特定位置的元素,以及string::npos代表没找到任何位置。

    class Solution {
    public:
        bool repeatedSubstringPattern(string s) {
            //方法1.移动匹配--具体表现为拼接-删除首尾-调用find库函数
            string ss = s+s;
            ss.erase(ss.length()-1,1);
            ss.erase(0,1);
            auto it = ss.find(s);
            if (it != string::npos)
                return 1;
            return 0;
        }
    };

    第二种解法是KMP,

    class Solution {
    public:
        bool repeatedSubstringPattern(string s) {
            //方法2.KMP--两个思路中的关键数学证明
            //1. 如果是由子串重复构成,那么最长相等前后缀不包含的那个子串就是所要求的子串。
            //2. 如果子串整除整个串,那么整个串就是由这个子串重复构成的。这个依然看我关于[xx {xxxx][xx} xxxx] 的理解,总而言之各个部分都相等。
    
            //下面,首先是算next数组,我们需要知道next[s.length()-1]等于多少,因为这个值就代表我们整个串的相等前后缀有多长。然后,用s.length()-next[s.length()-1] 除 s.length(), 如果能整除,就返回1,否则返回0。
            int length = s.size();
            //int next[length]={0};
            int next[s.length()];
            next[0] = 0;
            int j = 0;
            for (int i = 1; i < length; ++i){
                while(j > 0 && s[j]!=s[i])
                    j = next[j-1];
                if(s[j]== s[i])
                    j++;
                next[i] = j;
            }
            if(next[length-1] != 0 && (length % (length - next[length-1]) == 0))
                return 1;
            return 0;
        }
    };

    注1: next[length-1] != 0 要加,漏了完全没有相等前后缀的情形。

    注2:int next[s.length()];这种用法很奇怪,按本科讲的课来说应该写成int *next = new int[s.length()];才行。

    leetcode的奇怪机制:
    ·参数是(string s),在函数体内定义一个数组int array[s.length()];可以过
    ·int array[s.length()]={0};过不了
    ·int array[s.length()]; for (int i = 0; i < s.length(); ++i) {array[i] = 0;}又能过

    其他:LeetCode刷题总结-字符串篇 - 舞动的心 - 博客园 (cnblogs.com) (还有什么题可刷)待看


http://www.niftyadmin.cn/n/5134303.html

相关文章

windows电脑安装系统后固态硬盘和机械硬盘的盘符号顺序显示错乱,解决方法

一、场景 由于电脑磁盘是SSD固态硬盘自己拓展的1T机械硬盘组成&#xff0c;固态硬盘分为C、D两个盘区&#xff0c;机械硬盘分为E、F两个盘区。为了提升运行速度&#xff0c;系统安装在C盘&#xff0c;安装完成后按照习惯盘区顺应该为C、D、E、F&#xff0c;但实际情况却是D、E…

读图数据库实战笔记03_遍历

1. Gremlin Server只将数据存储在内存中 1.1. 如果停止Gremlin Server&#xff0c;将丢失数据库里的所有数据 2. 概念 2.1. 遍历&#xff08;动词&#xff09; 2.1.1. 当在图数据库中导航时&#xff0c;从顶点到边或从边到顶点的移动过程 2.1.2. 类似于在关系数据库中的查…

JWT详解解读读

&#x1f4d1;前言 本文主要是jwt解读文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#x1f304;每日一句&#xff1a;努力一点&#…

C++总结(3):类的动态内存分配、异常、类型转换运算符

文章目录 1 类的动态内存分配1.1 C动态内存分配1.2 拷贝构造函数1.3 赋值运算符(operator)重载 2 异常3 类型转换运算符 1 类的动态内存分配 1.1 C动态内存分配 在C/C中都可以使用malloc/free来分配内存&#xff0c;但C还有一种更好的方法&#xff1a;new和delete。下面以动态…

华为OD机考算法题:乘坐保密电梯

题目部分 题目乘坐保密电梯难度难题目说明难 有一座保密大楼&#xff0c;你从 0 楼到达指定楼层 m&#xff0c;必须这样的规则乘坐电梯&#xff1a; 给定一个数字序列&#xff0c;每次根据序列中的数字 n,上升 n 层或者下降 n 层&#xff0c;前后两次的方向必须相反&#xff0…

600MW发电机组继电保护自动装置的整定计算及仿真

摘要 随着科技的发展&#xff0c;电力已成为最重要的资源之一&#xff0c;如何保证电力的供应对于国民经济发展和人民生活水平的提高都有非常重要的意义。在电能输送过程中&#xff0c;发电机组是整个过程中最重要的一个基本元素&#xff0c;在电力系统中的输送和分配中被广泛应…

C++二分查找算法的应用:最长递增子序列

涉及知识点 二分查找 单调映射 源码下载 点击下载源码 题目 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xf…

3 Go的基础语法

概述 在上一节的内容中&#xff0c;我们介绍了第一个Go程序&#xff0c;包括&#xff1a;安装Go环境、编写第一个Go程序、编译并运行程序等。在本节中&#xff0c;我们将介绍Go的基础语法。Go是一门简洁和优雅的语言&#xff0c;有自己特殊的一些语法规则。因此&#xff0c;在介…