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();
}
}