基础算法(10)——字符串

1. 最长公共前缀

题目描述

算法思路

解法一:两两比较

我们可以先拿前两个字符串比较得出一个公共前缀,然以后拿这个公共前缀依次与后面的字符串比较,这样就可以找出所有字符串的最长公共前缀

代码实现

// 解法一:两两比较
class Solution {
    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);
    }
}

解法二:统一比较

我们可以逐位比较这些字符串,哪一位出现了不同,就在哪一位截至

代码实现

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

2. 最长回文子串

题目描述

算法思路

枚举每一个可能的子串非常费时,有没有比较简单一点的方法呢?

对于一个子串而言,如果它是回文串,并且长度大于 2,那么将它首尾的两个字母去除之后,它仍然是个回文串。如此这样去除,一直除到长度小于等于 2 时呢?长度为 1 的,自身与自身就构成回文;而长度为 2 的,就要判断这两个字符是否相等了。

从这个性质可以反推出来,从回文串的中心开始,往左读和往右读也是一样的。那么,是否可以枚举回文串的中心呢?

从中心向两边扩展,如果两边的字母相同,我们就可以继续扩展;如果不同,我们就停止扩展。这样只需要一层 for 循环,我们就可以完成先前两层 for 循环的工作量。

代码实现

class Solution {
    public String longestPalindrome(String s) {
        int begin = 0;
        int len = 0;
        int 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);
    }
}

3. 二进制求和

题目描述

算法思路

模拟十进制中列竖式计算两个数之和的过程,但是要注意这里是逢二进一

代码实现

class Solution {
    public String addBinary(String a, String b) {
        StringBuffer ret = new StringBuffer();
        int cur1 = a.length() - 1;
        int cur2 = b.length() - 1;
        int 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)((char)t % 2 + '0'));
            t /= 2;
        }
        ret.reverse();
        return ret.toString();
    }
}

4. 字符串相乘

题目描述

算法思路

解法一:模拟列竖式运算

如果 num₁ 和 num₂ 之一是 0,则直接将 0 作为结果返回即可。

如果 num₁ 和 num₂ 都不是 0,则可以通过模拟「竖式乘法」的方法计算乘积。从右往左遍历乘数,将乘数的每一位与被乘数相乘得到对应的结果,再将每次得到的结果累加。这道题中,被乘数是 num₁,乘数是 num₂。

需要注意的是,num₂ 除了最低位以外,其余的每一位的运算结果都需要补 0。

复杂度分析

  • 时间复杂度:O(mn + n²),其中 m 和 n 分别是 num₁ 和 num₂ 的长度。需要从右往左遍历 num₂,对于 num₂ 的每一位,都需要和 num₁ 的每一位计算乘积,因此计算乘积的总次数是 mn。字符串相加操作共有 n 次,相加的字符串长度最长为 m + n,因此字符串相加的时间复杂度是 O(mn + n²)。总时间复杂度是 O(mn + n²)。
  • 空间复杂度:O(m + n),其中 m 和 n 分别是 num₁ 和 num₂ 的长度。空间复杂度取决于存储中间状态的字符串,由于乘积的最大长度为 m + n,因此存储中间状态的字符串的长度不会超过 m + n。
代码实现
class Solution {
    // 主方法:实现字符串形式的非负整数相乘
    public String multiply(String num1, String num2) {
        // 若其中一个数为 "0",直接返回 "0"(因为 0 乘任何数都为 0)
        if (num1.equals("0") || num2.equals("0")) {
            return "0";
        }
        // 初始化结果为 "0",用于存储最终的乘积
        String ans = "0";
        // m、n 分别为两个字符串数字的长度
        int m = num1.length(), n = num2.length();
        
        // 从右往左遍历乘数 num2 的每一位(模拟竖式乘法中乘数的每一位与被乘数相乘)
        for (int i = n - 1; i >= 0; i--) {
            // 用于存储当前位相乘后的中间结果(带补 0 及进位处理)
            StringBuffer curr = new StringBuffer();
            // 进位值,初始为 0
            int add = 0;
            
            // 为当前位的乘积结果补 0(因为从右往左遍历,第 i 位对应的是 10^(n - 1 - i) 量级,需要补 (n - 1 - i) 个 0)
            for (int j = n - 1; j > i; j--) {
                curr.append(0);
            }
            
            // 取出 num2 当前位的数字值(转换为 int 类型)
            int y = num2.charAt(i) - '0';
            // 从右往左遍历被乘数 num1 的每一位,进行乘法运算
            for (int j = m - 1; j >= 0; j--) {
                // 取出 num1 当前位的数字值(转换为 int 类型)
                int x = num1.charAt(j) - '0';
                // 计算当前位相乘结果加上进位值
                int product = x * y + add;
                // 将乘积结果的个位数字追加到 curr 中
                curr.append(product % 10);
                // 更新进位值(取乘积结果的十位及以上数字)
                add = product / 10;
            }
            // 若最后还有进位值,将其追加到 curr 中
            if (add != 0) {
                curr.append(add % 10);
            }
            
            // 将当前位相乘得到的中间结果(已反转过,此时是正常顺序)与之前的结果 ans 进行字符串形式的加法运算,更新 ans
            ans = addStrings(ans, curr.reverse().toString());
        }
        // 返回最终的乘积结果
        return ans;
    }

    // 辅助方法:实现两个字符串形式的非负整数相加
    public String addStrings(String num1, String num2) {
        // i、j 分别用于从右往左遍历 num1、num2;add 用于存储进位值
        int i = num1.length() - 1, j = num2.length() - 1, add = 0;
        // 用于存储相加结果
        StringBuffer ans = new StringBuffer();
        
        // 只要还有数字位未处理完,或者还有进位值,就继续循环
        while (i >= 0 || j >= 0 || add != 0) {
            // 取出 num1 当前位的数字(若已遍历完则用 0 代替)
            int x = i >= 0 ? num1.charAt(i) - '0' : 0;
            // 取出 num2 当前位的数字(若已遍历完则用 0 代替)
            int y = j >= 0 ? num2.charAt(j) - '0' : 0;
            // 计算当前位相加结果加上进位值
            int result = x + y + add;
            // 将相加结果的个位数字追加到 ans 中
            ans.append(result % 10);
            // 更新进位值(取相加结果的十位及以上数字)
            add = result / 10;
            // 继续往左遍历
            i--;
            j--;
        }
        // 因为是从右往左计算并追加结果的,所以需要反转才能得到正确的顺序
        ans.reverse();
        // 返回相加后的结果字符串
        return ans.toString();
    }
}

解法二:对解法一进行优化->无进位相乘然后相加,最后处理进位

算法思路:

1. 无进位乘法:

直接计算每对数字的乘积,并累加到对应位置(tmp[i+j]),不处理进位。例如:

  • num1 = "123", num2 = "45"
  • tmp[0] 存储 3 * 5 = 15
  • tmp[1] 存储 2 * 5 + 3 * 4 = 22
  • 以此类推...

2. 统一处理进位:

遍历临时数组 tmp,将每一位的进位累加到下一位,最终生成正确结果。例如:

  • tmp = [15, 22, 22, 5]
  • 处理后转为字符串:[5, 3, 4, 7] -> 反转后为 "7435"
关键步骤:
  • 临时数组长度: m + n - 1 确保能覆盖所有可能的乘积位置,最高位进位会在后续处理
  • 反转处理:将字符串反转后,索引 i + j 直接对应乘积的位置(个位为 0,十位为 1,依此类推)
  • 进位处理:通过 t 变量传递进位,确保每一位的值正确。例如:tmp[0] = 15 -> 处理后个位为 5,进位 1 传递给下一位
  • 前导零处理:当结果为 "00" 时,需删除前导零,最终返回 "0"
代码实现:
class Solution {
    public String multiply(String num1, String num2) {
        // 1. 准备工作
        int m = num1.length(), n = num2.length();
        // 将字符串反转并转为字符数组,方便从低位(个位)开始处理
        char[] n1 = new StringBuffer(num1).reverse().toString().toCharArray();
        char[] n2 = new StringBuffer(num2).reverse().toString().toCharArray();
        // 创建临时数组存储无进位的乘积和,长度为 m+n-1 (两数相乘最大位数为 m+n)
        int[] tmp = new int[m + n - 1];

        // 2. 无进位相乘并累加结果
        // 遍历 num1 的每一位(从低位到高位)
        for (int i = 0; i < m; i++)
            // 遍历 num2 的每一位(从低位到高位)
            for (int j = 0; j < n; j++)
                // num1[i] 与 num2[j] 的乘积对应加到 tmp[i+j] 位置
                // i+j 表示当前乘积在结果中的位置索引(反转后)
                tmp[i + j] += (n1[i] - '0') * (n2[j] - '0');

        // 3. 处理进位并生成结果字符串
        int cur = 0;     // 当前处理的临时数组索引
        int t = 0;       // 进位值
        StringBuffer ret = new StringBuffer();
        // 遍历临时数组并处理进位,同时处理可能的最高位进位
        while (cur < m + n - 1 || t != 0) {
            // 累加当前位的值和进位
            if (cur < m + n - 1)
                t += tmp[cur++];
            // 将当前位的值(取模10)添加到结果中
            ret.append((char) ((t % 10) + '0'));
            // 更新进位值(整除10)
            t /= 10;
        }

        // 4. 处理前导零(结果长度>1时)
        while (ret.length() > 1 && ret.charAt(ret.length() - 1) == '0') {
            ret.deleteCharAt(ret.length() - 1);
        }

        // 反转结果并转为字符串返回
        return ret.reverse().toString();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值