力扣大厂热门面试算法题 43-45
43. 字符串相乘,44. 通配符匹配,45. 跳跃游戏 II,每题做详细思路梳理,配套Python&Java双语代码, 2024.03.18 可通过leetcode所有测试用例。
目录
43. 字符串相乘
解题思路
完整代码
Python
Java
44. 通配符匹配
解题思路
完整代码
Python
Java
45. 跳跃游戏 II
解题思路
完整代码
Python
Java
43. 字符串相乘
给定两个以字符串形式表示的非负整数
num1
和num2
,返回num1
和num2
的乘积,它们的乘积也表示为字符串形式。注意:不能使用任何内置的 BigInteger 库或直接将输入转换为整数。
示例 1:
输入: num1 = "2", num2 = "3" 输出: "6"示例 2:
输入: num1 = "123", num2 = "456" 输出: "56088"提示:
1 <= num1.length, num2.length <= 200
num1
和num2
只能由数字组成。num1
和num2
都不包含任何前导零,除了数字0本身。
解题思路
可以使用一个被称为“竖式乘法”的方法,这是一种我们在小学学过的乘法手算方法。由于题目要求不能直接将字符串转换为整数进行计算,我们需要模拟这个过程。整体思路如下:
- 初始化结果字符串:最开始,我们可以将结果初始化为"0",因为任何数与0相乘都是0。
- 逐位相乘:从
num2
的最低位开始,每一位与num1
相乘,得到一个临时的乘积。注意,我们需要处理乘积中的进位问题。 - 处理进位:将每一步的乘积加到最终的结果上时,同样需要处理进位问题。
- 结果累加:将每次乘法的结果累加到最终结果中,注意,由于是逐位相乘,我们需要在结果的右侧添加相应数量的0(这模拟了竖式乘法中的位移)。
- 去除前导零:最终,我们可能会得到一个有前导零的字符串,需要去掉这些前导零。
完整代码
Python
class Solution:
def multiply(self, num1: str, num2: str) -> str:
if num1 == "0" or num2 == "0": # 如果有一个数为0,直接返回0
return "0"
result = [0] * (len(num1) + len(num2)) # 初始化结果数组,最大长度为两个字符串长度之和
# 逐位相乘
for i in range(len(num1) - 1, -1, -1):
for j in range(len(num2) - 1, -1, -1):
mul = (ord(num1[i]) - ord('0')) * (ord(num2[j]) - ord('0')) # 计算每一位的乘积
sum = mul + result[i + j + 1] # 加上之前的结果
result[i + j + 1] = sum % 10 # 更新当前位
result[i + j] += sum // 10 # 处理进位
# 将结果数组转换为字符串,同时去除前导零
result_str = ''.join(map(str, result))
return result_str.lstrip('0')
Java
public class Solution {
public String multiply(String num1, String num2) {
if (num1.equals("0") || num2.equals("0")) { // 如果有一个数为0,直接返回0
return "0";
}
int[] result = new int[num1.length() + num2.length()]; // 初始化结果数组
// 逐位相乘
for (int i = num1.length() - 1; i >= 0; i--) {
for (int j = num2.length() - 1; j >= 0; j--) {
int mul = (num1.charAt(i) - '0') * (num2.charAt(j) - '0');
int sum = mul + result[i + j + 1]; // 加上之前的结果
result[i + j + 1] = sum % 10; // 更新当前位
result[i + j] += sum / 10; // 处理进位
}
}
// 将结果数组转换为字符串,同时去除前导零
StringBuilder resultStr = new StringBuilder();
for (int num : result) {
if (!(resultStr.length() == 0 && num == 0)) { // 跳过前导零
resultStr.append(num);
}
}
return resultStr.length() == 0 ? "0" : resultStr.toString();
}
}
44. 通配符匹配
给你一个输入字符串 (
s
) 和一个字符模式 (p
) ,请你实现一个支持'?'
和'*'
匹配规则的通配符匹配:
'?'
可以匹配任何单个字符。'*'
可以匹配任意字符序列(包括空字符序列)。判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。
示例 1:
输入:s = "aa", p = "a" 输出:false 解释:"a" 无法匹配 "aa" 整个字符串。示例 2:
输入:s = "aa", p = "*" 输出:true 解释:'*' 可以匹配任意字符串。示例 3:
输入:s = "cb", p = "?a" 输出:false 解释:'?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。提示:
0 <= s.length, p.length <= 2000
s
仅由小写英文字母组成p
仅由小写英文字母、'?'
或'*'
组成
解题思路
通配符匹配是一个经典问题,我们可以通过动态规划(DP)来解决这个问题。动态规划的基本思想是用一个二维数组dp[i][j]
来表示字符串s
的前i
个字符和模式p
的前j
个字符是否匹配。下面是解决这个问题的步骤:
- 初始化:首先,我们初始化
dp[0][0]
为true
,因为两个空字符串是匹配的。对于dp[0][j]
,如果p
的前j
个字符都是*
,那么它们可以匹配空字符串,因此dp[0][j]
也为true
。 - 填充DP表:接下来,我们按行填充这个DP表。对于每一对
(i, j)
,我们需要根据s[i-1]
和p[j-1]
的关系来更新dp[i][j]
:- 如果
s[i-1] == p[j-1]
或者p[j-1] == '?'
,则dp[i][j] = dp[i-1][j-1]
。 - 如果
p[j-1] == '*'
,则dp[i][j] = dp[i][j-1]
(不使用*
字符匹配)或dp[i-1][j]
(使用*
字符匹配s[i-1]
以及它之前的字符)。
- 如果
- 返回结果:最后,
dp[s.length()][p.length()]
就是整个问题的答案,表示s
和p
是否完全匹配。
完整代码
Python
class Solution:
def isMatch(self, s: str, p: str) -> bool:
dp = [[False] * (len(p) + 1) for _ in range(len(s) + 1)]
dp[0][0] = True
for j in range(1, len(p) + 1):
if p[j-1] == '*':
dp[0][j] = dp[0][j-1]
for i in range(1, len(s) + 1):
for j in range(1, len(p) + 1):
if p[j-1] == '*':
dp[i][j] = dp[i][j-1] or dp[i-1][j]
elif p[j-1] == '?' or s[i-1] == p[j-1]:
dp[i][j] = dp[i-1][j-1]
return dp[len(s)][len(p)]
Java
public class Solution {
public boolean isMatch(String s, String p) {
boolean[][] dp = new boolean[s.length() + 1][p.length() + 1];
dp[0][0] = true;
for (int j = 1; j <= p.length(); j++) {
if (p.charAt(j-1) == '*') {
dp[0][j] = dp[0][j-1];
}
}
for (int i = 1; i <= s.length(); i++) {
for (int j = 1; j <= p.length(); j++) {
if (p.charAt(j-1) == '*') {
dp[i][j] = dp[i][j-1] || dp[i-1][j];
} else if (p.charAt(j-1) == '?' || s.charAt(i-1) == p.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1];
}
}
}
return dp[s.length()][p.length()];
}
}
45. 跳跃游戏 II
给定一个长度为
n
的 0 索引整数数组nums
。初始位置为nums[0]
。每个元素
nums[i]
表示从索引i
向前跳转的最大长度。换句话说,如果你在nums[i]
处,你可以跳转到任意nums[i + j]
处:
0 <= j <= nums[i]
i + j < n
返回到达
nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达nums[n - 1]
。示例 1:
输入: nums = [2,3,1,1,4] 输出: 2 解释: 跳到最后一个位置的最小跳跃数是2
。 从下标为 0 跳到下标为 1 的位置,跳1
步,然后跳3
步到达数组的最后一个位置。示例 2:
输入: nums = [2,3,0,1,4] 输出: 2
解题思路
可以使用贪心算法。基本思想是,在每一步中都尽可能地跳得更远,但同时保留当前能达到的最远距离内的下一步跳跃能达到的最远距离。具体步骤如下:
- 初始化变量:创建几个变量,
steps
用于记录跳跃次数,maxReach
用于记录当前步骤内能到达的最远距离,end
用于记录这一跳能到达的最远位置。 - 遍历数组:从数组的第一个元素开始遍历,直到倒数第二个元素(因为最后一个元素是我们的目的地,不需要再跳跃)。
- 更新最远距离:在遍历的每一步中,更新能达到的最远距离
maxReach = max(maxReach, i + nums[i])
,其中i
是当前位置,nums[i]
是从当前位置能跳跃的最大长度。 - 到达跳跃点:当我们到达上一次跳跃确定的最远位置
end
时,意味着需要进行下一次跳跃,此时更新end
为当前能达到的最远距离maxReach
,并增加跳跃次数steps++
。 - 返回结果:当遍历完成时,
steps
即为到达数组末尾的最小跳跃次数。
完整代码
Python
class Solution:
def jump(self, nums: List[int]) -> int:
steps = 0
maxReach = 0
end = 0
for i in range(len(nums) - 1): # 不需要遍历到最后一个元素
maxReach = max(maxReach, i + nums[i])
if i == end:
end = maxReach
steps += 1
return steps
Java
public class Solution {
public int jump(int[] nums) {
int steps = 0;
int maxReach = 0;
int end = 0;
for (int i = 0; i < nums.length - 1; i++) { // 不需要遍历到最后一个元素
maxReach = Math.max(maxReach, i + nums[i]);
if (i == end) {
end = maxReach;
steps++;
}
}
return steps;
}
}