当前位置: 首页 > news >正文

字符串算法题

1.最长公共前缀和

 题目要求找出所有字符串的公共前缀,如果没有公共的前缀就返回空字符串。

解法1:纵向比较

         我们以第一个字符串为标准,依次比较所有字符串的每一位,如果都相同,则比较下一个位置,如果不同,那么前一个位置就是最长的公共前缀。

在纵向比较的时候要避免越界,因为第一个字符串的长度有可能比后面的字符串长,所以除了字符不相等外,i越界,也是一个判断条件。

时间复杂度:O(nm),n为数组长度,m为最长的字符串的长度

空间复杂度:O(1)

string longestCommonPrefix(vector<string>& strs) 
    {   
        if(strs.size() == 1) return strs[0];
        for(int i=0; i<strs[0].size(); ++i)
        {
            char c = strs[0][i];
            for(int j=1; j<strs.size(); ++j)
            {
                if(i == strs[j].size() || strs[j][i] != c)
                    return strs[0].substr(0,i);
            }
        }

        return strs[0];
    }

 解法2:横向比较——两两比较

        两个两个字符串进行比较,用它们得出的前缀和与第三个字符串继续比较。重复该过程。如果在某一个比较过程中发现前缀和的长度变为0,那就说明所有字符串没有公共的前缀和。 

时间复杂度:O(mn),n是数组长度,m是最长的字符串长度

空间复杂度:O(1)

string longestCommonPrefix(vector<string>& strs) 
    {   
        if(strs.size() == 1) return strs[0];

        string prefix = strs[0];
        for(int i=0; i<strs.size(); ++i)
        {
            prefix = compareTwoString(prefix, strs[i]);
            if(!prefix.size()) break;
        }    
        return prefix;
    }

    string compareTwoString(const string& s1, const string& s2)
    {
        int index = 0;
        int minLen = min(s1.size(), s2.size()); //前缀和肯定是在长度较短的字符串中出现
        while(index < minLen && s1[index] == s2[index]) index++;

        return s1.substr(0,index);
    }

2.最长回文子串 

解法:中心扩展算法 

        不管是偶数回文串还是奇数回文串,它们都是对称的。所以我们可以固定一个中心元素,然后定义两个指针i,j,分别向两边进行搜索,如果两边元素相等就继续移动,直到不相等或者越界,此时i+1,j-1之间的子串就是最长的回文串。

        当然上面求出的是奇数的回文串,所以还需考虑偶数的回文串。我们让i位置不变,j从中心元素位置开始,重复上面的搜索。

        不管是奇数还是偶数,每搜索一次就要更新结果,而且我们返回的是子串,所以得直到起始位置和长度。起始位置就是i+1,而长度则是right -1 - (left +1)+1 = right - left - 1.

时间复杂度:O(n^2),枚举每一个中心元素是O(n),向两边搜索合起来也是O(n)

空间复杂度:O(1)

class Solution 
{
public:
    string longestPalindrome(string s) 
    {
        // 中心扩展算法
        int begin = 0, length = 0;
        for(int i=0; i<s.size(); ++i) // 枚举中心元素
        {
            // 奇数子串
            int left = i, right = i;
            while(left >=0 && right < s.size() && s[left] == s[right])
            {
                left--;
                right ++;
            }
            
            // 更新结果
            if(right - left - 1 > length)
            {
                begin = left + 1;
                length = right - left - 1;
            }

            // 偶数子串
            left = i;
            right = i + 1;
            while(left >=0 && right < s.size() && s[left] == s[right])
            {
                left--;
                right ++;
            }

            // 更新结果
            if(right - left - 1 > length)
            {
                begin = left + 1;
                length = right - left - 1;
            }
        }
        return s.substr(begin, length);
    }
};

3.二进制求和 

 这其实就是一道高精度加法,只不过换成了满二进位。模拟竖式加法即可。

时间复杂度:O(max(n,m))

空间复杂度:O(1)

class Solution 
{
public:
    string addBinary(string a, string b) 
    {
        if(a[0] == '0' || b[0] == '0') return a[0] == '0' ? b : a;

        string ret;
        int i = a.size() - 1, j = b.size() - 1;
        int carry = 0;
        while(i>=0 || j>=0 || carry)    
        {
            carry += i>=0?a[i]-'0':0;
            carry += j>=0?b[j]-'0':0;
            ret += carry % 2 + '0';
            carry /= 2;
            i--;
            j--;
        }
        reverse(ret.begin(), ret.end());
        return ret;
    }
};

4.字符串相乘 

 解法1:模拟——用另一个数的每一位依次和另一个数相乘

        用竖式模拟乘法时,其实就是让下面的数的每一位依次和上面的相乘,然后将相乘的结果相加。

所以我们解法一采取的策略就是模拟上述过程。用另一个数的每一位依次和另一个数相乘,但是乘的时候要注意位数,比如1与123相乘时,结果应该时1230,所以我们要对每一次乘积进行补零。接着就是对每一次乘的结果相加,这其实就是一个高精度加法,这个加法我们可以在每一次乘的结果就加上,没必要等到最后在处理。 

时间复杂度:O(mn(n+m))

空间复杂度:O(m+n)

class Solution 
{
public:
    string _solve(string s, int multiplier, string multiple)// 被乘数,乘数,倍数
    {
        string ret;
        int carry = 0;
        for (int i = s.size() - 1; i >= 0 || carry; --i)
        {
            int x = i >= 0 ? s[i] - '0' : 0;
            carry += x * multiplier;
            ret += to_string(carry % 10);
            carry /= 10;
        }
        reverse(ret.begin(), ret.end());

        return ret + multiple; // 补零操作
    }
    
    // 高精度加法
    string addTwoString(string s, string t)
    {
        if (!s.size() || !t.size())
            return !s.size() ? t : s;

        int i = s.size() - 1, j = t.size() - 1, carry = 0;
        string ret;
        while (i >= 0 || j >= 0 || carry)
        {
            carry += i < s.size() ? s[i--] - '0' : 0;
            carry += j < t.size() ? t[j--] - '0' : 0;
            ret += to_string(carry % 10);
            carry /= 10;
        }
        reverse(ret.begin(), ret.end());
        return ret;
    }

    string multiply(string s, string t) 
    {
        if (s[0] == '0' || t[0] == '0')
            return "0";

        // 依次取出s的每一位 与 t相乘
        string multiple;
        int tmp = 0;
        string ret;
        for (int i = s.size() - 1; i >= 0; --i)
        {
            tmp = (s[i] - '0');
            ret = addTwoString(ret, _solve(t, tmp, multiple));
            multiple += '0';
        }
        return ret;
    }
};

解法2:模拟无进位相乘后相加

        做法就是我们直接模拟竖式相乘,但是不管进位,直接存储下来,且对应位置的还应该加在一起,最后统一处理进位,让每个位置只保留一个数。下面模拟一个示例: 

根据示例,我们需要存储下对应位置的乘积的和,而且这些乘积的位置是由规律的,逆序相乘,下标从0开始,那么两个数乘积的位置就在i+j处。所以我们创建一个vector用来存储对应位置的乘积的和,最后在对vector里面的数进行进位处理。进位处理时同时要考虑carry!=0的情况。

因为我们是逆序进行操作的,vector里面也是逆序存储的,按照上面的例子来说应该是72 87 21,所以最后还需要进行逆序。但是逆序前还得进行一步——处理前导零。因为我们开的vector空间比较大,但是存储时可能存储不完,就可能导致后面多余了0,我们需要将这些0给删除掉,之后再进行逆置。

class Solution 
{
public:
    string multiply(string nums1, string nums2) 
    {
        if(nums1[0] == '0' || nums2[0] == '0') return "0";
        
        // 无进位相乘
        int n = nums1.size(), m = nums2.size();
        vector<int> add_no_carry(n+m);    
        reverse(nums1.begin(), nums1.end());
        reverse(nums2.begin(), nums2.end());
        for(int i=0; i<n; ++i)
        {
            for(int j=0; j<m; ++j)
            {
                // 对应位置相加
                add_no_carry[i+j] += (nums1[i] - '0') * (nums2[j] - '0');
            }
        }

        // 处理进位
        string ret;
        int carry = 0;
        for(auto e : add_no_carry)
        {
            carry += e;
            ret += carry % 10 + '0';
            carry /= 10;
        }
        while(carry)
        {
            ret += carry % 10 + '0';
            carry /= 10;
        }
        cout << ret << endl;

        // 处理前导零,因为还没有将结果逆置回来,多余的0堆积在ret尾部
        while(ret.size() && ret.back() == '0') ret.pop_back();
        reverse(ret.begin(), ret.end());
        return ret;
    }
};

相关文章:

  • 在 Kaggle 中绘制中文乱码解决
  • 相对论-空间和时间(2)
  • 神聖的綫性代數速成例題5. 矩陣運算的定義、轉置的性質、方陣多項式的概念
  • Android 英文文章选词
  • 【Go】函数闭包、堆和栈的概念
  • android ConstraintLayout布局 实战:打造复杂界面的最佳实践
  • Go语言--语法基础3--变量常量运算符--变量
  • 1.8PageTable
  • CSS选择器
  • 93.HarmonyOS NEXT窗口管理基础教程:深入理解WindowSizeManager
  • 蓝桥杯学习-12递归
  • git基础概念和操作
  • 2025年西安交通大学少年班招生考试初试数学试题(初中组)
  • 【TCP】三次挥手,四次挥手详解--UDP和TCP协议详解
  • 继承知识点—详细
  • EMC整改黄金搭档:共模滤波器与磁环
  • Qt中的 #include “xxxx.moc“ 说明
  • 3.13-4 字符
  • 【C++】如何高效掌握UDP数据包解析
  • 2023年蓝桥杯 省赛 ————特殊日期
  • 做网站代理/抖音seo排名软件哪个好
  • 做网站保定/刷神马seo排名首页排名
  • 网站设计毕业论文的模板咋写/如何让百度收录
  • wordpress自定义密码/搜狗seo查询
  • 服装业网站建设的策划/推广游戏赚钱的平台
  • 医院网站建设策划/抖音指数查询