目录
第一题
题目来源
题目内容
解决方法
方法一:动态规划
这道题可以使用动态规划的方法求解。首先,我们观察到第一个房屋和最后一个房屋是相邻的,因此它们不能同时被偷窃。所以,我们可以将问题分成两种情况来考虑:
-
偷窃第一个房屋但不偷窃最后一个房屋:在这种情况下,我们只需要计算从第一个房屋到倒数第二个房屋能够偷窃到的最高金额。
-
不偷窃第一个房屋但偷窃最后一个房屋:在这种情况下,我们只需要计算从第二个房屋到最后一个房屋能够偷窃到的最高金额。
对于每一种情况,我们可以使用动态规划来求解。定义一个长度为n的数组dp,其中dp[i]表示从第一个房屋到第i个房屋能够偷窃到的最高金额。那么,我们有以下状态转移方程:
-
对于第一种情况,dp[i] = max(dp[i-2] + nums[i], dp[i-1]),其中dp[i-2] + nums[i]表示偷窃第i个房屋得到的金额,dp[i-1]表示不偷窃第i个房屋得到的金额。
-
对于第二种情况,dp[i] = max(dp[i-1], dp[i-2] + nums[i]),其中dp[i-1]表示不偷窃第i个房屋得到的金额,dp[i-2] + nums[i]表示偷窃第i个房屋得到的金额。
最后,我们可以将第一种情况和第二种情况的结果取较大值作为最终的结果,即max(dp[n-2], dp[n-1]),其中n是数组nums的长度。
java
class Solution {
public int rob(int[] nums) {
int n = nums.length;
if (n == 1) {
return nums[0];
}
return Math.max(robHouse(nums, 0, n - 2), robHouse(nums, 1, n - 1));
}
private int robHouse(int[] nums, int start, int end) {
int pre2 = 0, pre1 = 0;
for (int i = start; i <= end; i++) {
int cur = Math.max(pre2 + nums[i], pre1);
pre2 = pre1;
pre1 = cur;
}
return pre1;
}
}
在上述解法中,我们使用了动态规划的思想来解决问题。接下来我们来分析一下该解法的时间复杂度和空间复杂度。
时间复杂度分析:
- 在计算偷窃第一种情况的最高金额时,我们需要遍历从第一个房屋到倒数第二个房屋,因此时间复杂度为O(n)。
- 在计算偷窃第二种情况的最高金额时,我们需要遍历从第二个房屋到最后一个房屋,同样时间复杂度为O(n)。
- 最后取两种情况的较大值作为最终结果,时间复杂度为O(1)。 综上所述,该解法的时间复杂度为O(n)。
空间复杂度分析:
- 我们使用了一个长度为n的dp数组来保存每个房屋能够偷窃到的最高金额,因此空间复杂度为O(n)。
- 另外,我们还使用了常量级的额外空间来保存前两个房屋的偷窃金额,所以空间复杂度为O(1)。 综上所述,该解法的空间复杂度为O(n)。
总结: 该解法的时间复杂度为O(n),空间复杂度为O(n)。在给定限制条件下,这是一个较优的解法。
LeetCode运行结果:
第二题
题目来源
题目内容
解决方法
方法一:模拟
这个问题可以使用模拟的方法来解决。我们可以创建一个长度为numRows的字符串数组,表示Z字形排列后每一行的字符串。然后遍历原始字符串s,将每个字符按照Z字形的规律放入对应的行中。
具体步骤如下:
- 若numRows为1或s的长度小于等于numRows,则直接返回s作为结果。
- 创建一个长度为numRows的字符串数组rows,用于保存每一行的字符串。
- 创建一个变量rowIndex,初始值为0,表示当前所在的行。
- 创建一个变量goingDown,初始值为true,表示当前的方向是向下。
- 遍历字符串s的每个字符:
- 将当前字符加入rows[rowIndex]中。
- 如果当前的方向是向下且当前行不是最后一行,则向下移动到下一行(rowIndex++)。
- 如果当前的方向是向上且当前行不是第一行,则向上移动到上一行(rowIndex--)。
- 如果当前行是第一行或最后一行,则改变方向(goingDown = !goingDown)。
- 将数组rows中的每个字符串按顺序连接起来,即为结果。
java
class Solution {
public String convert(String s, int numRows) {
if (numRows == 1 || s.length() <= numRows) {
return s;
}
String[] rows = new String[numRows];
for (int i = 0; i < numRows; i++) {
rows[i] = "";
}
int rowIndex = 0;
boolean goingDown = true;
for (char c : s.toCharArray()) {
rows[rowIndex] += c;
if (goingDown && rowIndex < numRows - 1) {
rowIndex++;
} else if (!goingDown && rowIndex > 0) {
rowIndex--;
}
if (rowIndex == 0 || rowIndex == numRows - 1) {
goingDown = !goingDown;
}
}
StringBuilder result = new StringBuilder();
for (String row : rows) {
result.append(row);
}
return result.toString();
}
}
复杂度分析如下:
时间复杂度:
- String转换为char数组的时间复杂度是O(n),其中n为字符串s的长度。
- 遍历字符数组并按规则放入对应行的时间复杂度是O(n)。
- 将数组中的每个字符串连接起来的时间复杂度是O(numRows)。
- 因此,总体时间复杂度为O(n)。
空间复杂度:
- 创建了一个长度为numRows的字符串数组rows,所以额外的空间复杂度是O(numRows)。
- 除此之外,没有使用其他额外的空间。
- 因此,总体空间复杂度也是O(numRows)。
综上所述,该解法的时间复杂度和空间复杂度都是O(n),其中n为字符串s的长度。
LeetCode运行结果:
方法二:数学规律
除了模拟方法外,还可以使用数学规律来解决这个问题。
观察Z字形变换的规律,可以发现以下几个关键点:
- 第一行和最后一行中的字符之间的距离为周期性的,即间隔为2 * numRows - 2。
- 中间行的字符之间的距离有两个部分组成,一个是上方的字符与下方的字符之间的距离,为周期性的,间隔同样为2 * numRows - 2。另一个是斜对角方向的字符与当前字符之间的距离,为固定值,为2 * numRows - 2 - 2 * i,其中i为当前行的索引。
- 对于原始字符串中每个字符的位置,可以通过行索引和列索引来确定。
基于以上观察,可以直接计算每个字符在变换后字符串中的位置,然后将其按顺序放入结果字符串中。
java
class Solution {
public String convert(String s, int numRows) {
if (numRows == 1 || s.length() <= numRows) {
return s;
}
StringBuilder result = new StringBuilder();
int cycleLen = 2 * numRows - 2;
int n = s.length();
for (int i = 0; i < numRows; i++) {
for (int j = 0; j + i < n; j += cycleLen) {
result.append(s.charAt(j + i));
if (i != 0 && i != numRows - 1 && j + cycleLen - i < n) {
result.append(s.charAt(j + cycleLen - i));
}
}
}
return result.toString();
}
}
复杂度分析如下:
时间复杂度:
- 遍历每个字符并确定其位置的时间复杂度是O(n),其中n为字符串s的长度。
- 在遍历过程中,对于每个字符,最多进行两次append操作。
- 因此,总体时间复杂度仍然为O(n)。
空间复杂度:
- 只使用了常数级别的额外空间,即StringBuilder中的空间。
- 因此,空间复杂度是O(1)。
综上所述,使用数学规律的方法的时间复杂度和空间复杂度分别为O(n)和O(1),其中n为字符串s的长度。相比于模拟方法,这种方法在空间复杂度上有优势,因为不需要额外的数组来存储每一行的字符串。
LeetCode运行结果:
方法三:分组
除了前面介绍的方法,还可以使用分组方法来完成Z字形变换。
具体步骤如下:
- 创建一个长度为numRows的字符串数组rows,用来存储每一行的字符。
- 遍历原始字符串s,按照特定的分组规则将每个字符放入对应的分组中。
- 第一组的长度为2 * numRows - 2。
- 后续的每一组的长度也是2 * numRows - 2,但是每个字符在分组中的位置会有所不同。
- 对于中间的行(行索引1到numRows-2),每个分组包含两个字符,分别位于当前行和其对称行中。
- 对于首尾两行(行索引0和numRows-1),每个分组只包含一个字符,位于对应行中。
- 遍历完成后,将每一行的字符串按顺序拼接成最终的结果字符串。
java
class Solution {
public String convert(String s, int numRows) {
if (numRows == 1 || s.length() <= numRows) {
return s;
}
String[] rows = new String[numRows];
Arrays.fill(rows, "");
int groupSize = 2 * numRows - 2;
for (int i = 0; i < s.length(); i++) {
int remain = i % groupSize;
int row = remain < numRows ? remain : groupSize - remain;
rows[row] += s.charAt(i);
}
StringBuilder result = new StringBuilder();
for (String row : rows) {
result.append(row);
}
return result.toString();
}
}
复杂度分析如下:
-
时间复杂度:
- 遍历整个字符串s的时间复杂度为O(n)。
- 将每个字符放入对应分组的时间复杂度为O(1)。
- 因此,总的时间复杂度为O(n)。
-
空间复杂度:
- 创建了一个大小为numRows的字符串数组rows,因此空间复杂度为O(numRows) = O(n)。这里假设numRows远小于n,可以忽略。
LeetCode运行结果:
第三题
题目来源
题目内容
解决方法
方法一:数学方法
要反转一个整数,可以使用数学方法。
具体步骤如下:
需要注意的是,该代码假设输入的整数为32位有符号整数。如果输入的整数超过了范围,或者溢出反转后的整数范围,将返回0。
- 初始化一个变量
res
用于存储反转后的整数。 - 进入循环,当原始整数不为0时,执行以下操作:
- 通过取余运算获取原始整数的最后一位数字,保存在
digit
中。 - 检查反转后的整数是否有可能溢出,如果溢出则返回0。具体的判断条件如下:
- 如果
res
大于Integer.MAX_VALUE / 10
,或者当res
等于Integer.MAX_VALUE / 10
且digit
大于7,则表示溢出,返回0。 - 如果
res
小于Integer.MIN_VALUE / 10
,或者当res
等于Integer.MIN_VALUE / 10
且digit
小于-8,则表示溢出,返回0。
- 如果
- 更新
res
的值,将res
乘以10并加上digit
。 - 将原始整数除以10,以便获取下一位数字。
- 通过取余运算获取原始整数的最后一位数字,保存在
- 循环结束后,返回
res
,即为反转后的整数。
java
class Solution {
public int reverse(int x) {
int res = 0;
while (x != 0) {
int digit = x % 10;
if (res > Integer.MAX_VALUE / 10 || (res == Integer.MAX_VALUE / 10 && digit > 7)) {
return 0;
}
if (res < Integer.MIN_VALUE / 10 || (res == Integer.MIN_VALUE / 10 && digit < -8)) {
return 0;
}
res = res * 10 + digit;
x /= 10;
}
return res;
}
}
复杂度分析如下:
- 这段代码的时间复杂度是 O(log|x|),其中 x 是输入的整数。在循环中,每次都会将原始整数除以10,因此循环的次数与输入的整数的位数相关。由于一个整数的位数大约为 log|x|(以10为底),所以循环的次数也大约为 log|x|。另外,在循环内部执行的操作包括取余运算、比较和乘法操作,这些操作的时间复杂度都可以忽略不计。因此,整体的时间复杂度可以近似地看作 O(log|x|)。
- 空间复杂度为 O(1),因为只使用了常数个变量来存储结果和临时值,没有使用额外的数据结构。
LeetCode运行结果:
方法二:转换字符串
要实现整数反转的功能,可以按照以下步骤进行:
- 将输入的整数转换为字符串。
- 判断整数是否为负数,如果是负数,则将符号标记下来,并将整数转换为正数处理。
- 将字符串逆序排列。
- 将逆序后的字符串转换回整数。
- 如果原整数为负数,则将结果乘以-1。
java
class Solution {
public int reverse(int x) {
// 转换为字符串
String str = Integer.toString(x);
// 判断是否为负数
boolean isNegative = false;
if (str.charAt(0) == '-') {
isNegative = true;
str = str.substring(1); // 移除负号
}
// 逆序排列字符串
StringBuilder sb = new StringBuilder(str);
sb.reverse();
String reversedStr = sb.toString();
// 转换回整数
long result = Long.parseLong(reversedStr);
// 处理溢出情况
if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE) {
return 0;
}
// 处理负数
if (isNegative) {
result *= -1;
}
return (int)result;
}
}
复杂度分析:
时间复杂度分析:
- 转换为字符串:这个步骤的时间复杂度是O(log|x|),其中|x|是输入整数的绝对值。
- 判断是否为负数:这个步骤的时间复杂度是O(1)。
- 逆序排列字符串:使用StringBuilder的reverse方法,时间复杂度为O(log|x|)。
- 将逆序后的字符串转换为长整型:使用Long.parseLong方法,时间复杂度同样为O(log|x|)。
- 处理溢出情况、处理负数:这两个步骤的时间复杂度都是O(1)。
- 返回结果:同样也是O(1)的时间复杂度。
综上所述,整体的时间复杂度可以视为O(log|x|)。
空间复杂度分析:
- 字符串转换和逆序排列过程中会使用StringBuilder对象来构建字符串,所以它的空间复杂度为O(log|x|)。
- 其他变量的空间复杂度是常数级的,不随输入规模变化。
因此,整体的空间复杂度可以视为O(log|x|)。
需要注意的是,这里的|x|是指输入整数的绝对值的位数。由于整数的位数在实际问题中通常是固定的,因此我们可以将该算法的时间复杂度和空间复杂度视为O(1)。
LeetCode运行结果: