【字符串】《字符串算法实战:用代码解决现实难题》


在这里插入图片描述

前言

字符串在算法中的体现:

字符串是算法处理文本的基础数据结构。在文本处理算法里,无论是查找关键词的字符串匹配算法,还是给单词列表排序的排序算法,字符串都作为核心对象。像 KMP 算法能高效查找字符串,优化朴素匹配的不足;基数排序可按字典序排列字符串。在数据压缩中,如 LZW 算法利用字符串特性,用字典替换重复串来减少存储空间,凸显了字符串在算法实现各类功能时的重要地位 。

本文将通过几道字符串例题带大家深入了解字符串相关算法题目以及其在算法中的应用!

例题

一、最长公共前缀

  1. 题目链接:最长公共前缀
  2. 题目描述:

编写⼀个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀,返回空字符串 “”。 示例 1:
输入:strs = [“flower”,“flow”,“flight”]
输出:“fl”
示例 2:
输⼊:strs = [“dog”,“racecar”,“car”]
输出:“”
解释:输入不存在公共前缀。
提示: 1 <= strs.length <= 200
0 <= strs[i].length <= 200
strs[i] 仅由小写英文字母组成

  1. 解法:
    解法一(统⼀比较): 题目要求多个字符串的公共前缀,我们可以逐位⽐较这些字符串,哪⼀位出现了不同,就在哪⼀位截止
    算法思路: 解法二(两两比较): 我们可以先找出前两个的最长公共前缀,然后拿这个最长公共前缀依次与后面的字符串比较,这样就可以找出所有字符串的最长公共前缀。

  2. 代码示例:

 public String longestCommonPrefix(String[] strs) {
        // 解法一:统⼀⽐较
        for (int i = 0; i < strs[0].length(); i++) {
            char tmp = strs[0].charAt(i);
            for (int j = 1; j < strs.length; j++) {
                if (i == strs[j].length() || strs[j].charAt(i) != tmp)
                    return strs[0].substring(0, i);
            }
        }
        return strs[0];
    }
    
  public String longestCommonPrefix(String[] strs) {
        // 解法二:两两⽐较
        String ret = strs[0];
        for (int i = 1; i < strs.length; i++) {
            ret = findCommon(strs[i], ret);
        }
        return ret;
    }

    public String findCommon(String s1, String s2) {
        int i = 0;
        while (i < Math.min(s1.length(), s2.length()) && s1.charAt(i) ==
                s2.charAt(i))
            i++;
        return s1.substring(0, i);
    }

二、最长回文子串

  1. 题目链接:最长回文子串
  2. 题目描述:

给你⼀个字符串 s,找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
示例 1: 输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 2:
输入:s = “cbbd”
输出:“bb”
提示:
1 <= s.length <= 1000
s 仅由数字和英文字母组成

  1. 解法(中心扩散):
    算法思路: 枚举每⼀个可能的子串非常费时,有没有比较简单⼀点的方法呢?
    对于⼀个子串而言,如果它是回文串,并且长度大于 2,那么将它首尾的两个字母去除之后,它仍然是个回文串。如此这样去除,⼀直除到长度小于等于 2 时呢?长度为 1 的,自身与自身就构成回文;而 长度为 2 的,就要判断这两个字符是否相等了。
    从这个性质可以反推出来,从回文串的中心开始,往左读和往右读也是⼀样的。那么,是否可以枚举回文串的中心呢?
    从中心向两边扩展,如果两边的字母相同,我们就可以继续扩展;如果不同,我们就停⽌扩展。这样 只需要⼀层 for 循环,我们就可以完成先前两层 for 循环的工作量。

  2. 代码示例:

 public String longestPalindrome(String s) {
        int begin = 0, len = 0, n = s.length();
        for (int i = 0; i < n; i++) // 固定所有的中间点
        {
            // 先扩展奇数⻓度的⼦串
            int left = i, right = i;
            while (left >= 0 && right < n && s.charAt(left) == s.charAt(right)) {
                left--;
                right++;
            }
            if (right - left - 1 > len) {
                begin = left + 1;
                len = right - left - 1;
            }
            // 扩展偶数⻓度
            left = i;
            right = i + 1;
            while (left >= 0 && right < n && s.charAt(left) == s.charAt(right)) {
                left--;
                right++;
            }
            if (right - left - 1 > len) {
                begin = left + 1;
                len = right - left - 1;
            }
        }
        return s.substring(begin, begin + len);
    }

三、二进制求和

  1. 题目链接:二进制求和
  2. 题目描述;

给你两个二进制字符串 a 和 b ,以二进制字符串的形式返回它们的和。
示例 1:
输入:a = “11”, b = “1”
输出:“100”
示例 2:
输入:a = “1010”, b = “1011”
输出:"10101

  1. 解法:
    可与十进制加法类比
    初始化变量:初始化一个变量 carry 用于存储进位,初始值为 0。还需要一个空字符串 result 用于存储最终的结果。
    从右向左逐位相加:从两个二进制数的最右边一位开始,将对应位上的数字以及进位 carry 相加。例如,对于二进制数 a 和 b,在第 i 位上的计算为 sum = a[i] + b[i] + carry。
    计算当前位和进位:计算当前位的结果 digit = sum % 2,即对 2 取余,得到当前位的值(因为二进制只有 0 和 1)。然后更新进位 carry = sum // 2,即整除 2,得到下一位的进位。
    存储结果:将当前位的结果 digit 转换为字符串形式,并添加到 result 的开头(因为我们是从右向左计算的,最终结果需要反转)。
    处理最后进位:当所有位都相加完后,如果 carry 仍然为 1,则需要将 1 添加到 result 的开头。

  2. 代码示例:

 public String addBinary(String a, String b) {
        StringBuffer ret = new StringBuffer();
        int cur1 = a.length() - 1, cur2 = b.length() - 1, t = 0;
        while (cur1 >= 0 || cur2 >= 0 || t != 0) {
            if (cur1 >= 0) t += a.charAt(cur1--) - '0';
            if (cur2 >= 0) t += b.charAt(cur2--) - '0';
            ret.append((char) ('0' + (char) (t % 2)));
            t /= 2;
        }
        ret.reverse();
        return ret.toString();
    }

四、字符串相乘

  1. 题目链接:字符串相乘
  2. 题目描述:

给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。 注意:不能使用任何内置的 BigInteger 库或直接将输入转换为整数。
示例 1:
输入: num1 = “2”, num2 = “3”
输出: “6”
示例 2:
输入: num1 = “123”, num2 = “456”
输出: "56088

  1. 解法(⽆进位相乘然后相加,最后处理进位):
    算法思路:
    整体思路就是模拟我们小学列竖式计算两个数相乘的过程。但是为了我们书写代码的方便性,我们选择⼀种优化版本的,就是在计算两数相乘的时候,先不考虑进位,等到所有结果计算完毕之后,再去考虑进位。如下图

在这里插入图片描述
4. 代码示例:

 public String multiply(String num1, String num2) {
        int m = num1.length(), n = num2.length();
        char[] n1 = new StringBuffer(num1).reverse().toString().toCharArray();
        char[] n2 = new StringBuffer(num2).reverse().toString().toCharArray();
        int[] tmp = new int[m + n - 1];
        // 1. ⽆进位相乘后相加
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++)
                tmp[i + j] += (n1[i] - '0') * (n2[j] - '0');

        // 2. 处理进位
        int cur = 0, t = 0;
        StringBuffer ret = new StringBuffer();
        while (cur < m + n - 1 || t != 0) {
            if (cur < m + n - 1) t += tmp[cur++];
            ret.append((char) (t % 10 + '0'));
            t /= 10;
        }
        // 3. 处理进位
        while (ret.length() > 1 && ret.charAt(ret.length() - 1) == '0')
            ret.deleteCharAt((ret.length() - 1));
        return ret.reverse().toString();
    }

结语

本文到这里就结束了,主要介绍了几道字符串相关题目的算法思路以及展示了各题的代码,字符串这块学好还需大量练习,大家加油喵!

以上就是本文全部内容,感谢各位能够看到最后,希望大家可以有所收获!创作不易,希望大家多多支持!

最后,大家再见!祝好!我们下期见!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值