目录
第一题
题目来源
题目内容
解决方法
方法一:位运算
class Solution {
public String addBinary(String a, String b) {
StringBuilder result = new StringBuilder();
int carry = 0;
int i = a.length() - 1;
int j = b.length() - 1;
while (i >= 0 || j >= 0) {
int sum = carry;
if (i >= 0) {
sum += a.charAt(i--) - '0';
}
if (j >= 0) {
sum += b.charAt(j--) - '0';
}
result.append(sum % 2);
carry = sum / 2;
}
if (carry > 0) {
result.append(carry);
}
return result.reverse().toString();
}
}
在这个方法中,我们使用 StringBuilder 来保存计算结果。然后,通过两个指针 i 和 j 分别从两个输入字符串的末尾开始遍历。在每一位上,我们将对应位置上的字符转换为数值,并加上上一位的进位值。然后,根据和的奇偶性确定当前位的值,并更新进位值。最后,如果还有进位值剩余,则添加到结果的最前面。最后将结果翻转并转换为字符串返回。
复杂度分析:
时间复杂度:
- 在上述代码中,我们使用了两个指针 i 和 j 分别遍历两个输入字符串的末尾。因此,时间复杂度为O(max(m, n)),其中m和n分别是字符串a和b的长度。
- 另外,我们使用了一个StringBuilder来保存结果,最后还需要将其翻转。这些操作的时间复杂度均为O(max(m, n))。
- 综上所述,该方法的时间复杂度为O(max(m, n))。
空间复杂度:
- 对于空间复杂度,我们仅使用了一个StringBuilder对象来保存结果。因此,空间复杂度为O(max(m, n)),主要取决于输入字符串较长的那个长度。
需要注意的是,这里使用了StringBuilder来构建字符串,而不是直接拼接字符串。这是因为字符串的拼接操作会生成新的字符串对象,并且每次拼接都需要将原字符串复制到新的对象中。而StringBuilder则是基于可变数组实现的,可以高效地进行字符串拼接操作。因此,使用StringBuilder可以提高性能。
LeetCode运行结果:
第二题
题目来源
题目内容
解决方法
方法一:贪心算法
这道题可以采用贪心的思路,每次尽可能选取更多的单词,同时尽量均匀分配单词之间的空格数量。
具体实现可以按行来处理,每次选取一些单词使得它们加起来不超过给定的 maxWidth,然后根据单词数量和剩余空格数量来决定如何分配单词之间的空格。如果当前行只有一个单词,那么左对齐;如果是最后一行,则左对齐且单词之间只加一个空格;否则就尽可能均匀分配单词之间的空格数量即可。
具体实现可以使用两个指针 i 和 j 分别表示当前行的第一个和最后一个单词,每次从 i 开始向后扫描单词,直到扫描到一个单词会导致当前行的长度超过 maxWidth 为止。此时,i 到 j-1 这些单词就组成了当前行,可以根据上述规则来对它们进行排版。最后将结果添加到答案中并继续扫描下一行。
class Solution {
public List<String> fullJustify(String[] words, int maxWidth) {
List<String> res = new ArrayList<>();
int n = words.length;
int i = 0;
while (i < n) {
int len = 0; // 当前行已选单词的长度
int wordsNum = 0; // 当前行已选单词的数量
StringBuilder sb = new StringBuilder();
while (i < n && len + words[i].length() + wordsNum <= maxWidth) {
// 选取单词
len += words[i].length();
wordsNum++;
i++;
}
if (wordsNum == 1) { // 如果当前行只有一个单词,左对齐
sb.append(words[i - wordsNum]);
for (int j = 0; j < maxWidth - len; j++) {
sb.append(" ");
}
} else if (i == n) { // 如果是最后一行,左对齐且单词之间只加一个空格
for (int j = i - wordsNum; j < i; j++) {
sb.append(words[j]);
if (j < i - 1) {
sb.append(" ");
}
}
for (int j = 0; j < maxWidth - len - (wordsNum - 1); j++) {
sb.append(" ");
}
} else { // 否则,尽可能均匀分配单词间的空格数量
int spaces = maxWidth - len;
int slots = wordsNum - 1;
int spaceWidth = spaces / slots;
int remain = spaces % slots;
for (int j = i - wordsNum; j < i; j++) {
sb.append(words[j]);
if (j == i - 1) {
break;
}
for (int k = 0; k < spaceWidth; k++) {
sb.append(" ");
}
if (remain > 0) {
sb.append(" ");
remain--;
}
}
}
res.add(sb.toString());
}
return res;
}
}
复杂度分析:
时间复杂度分析:
假设单词数量为 n,最大长度为 L。
扫描一遍单词数组找出每一行的单词需要 O(n) 的时间复杂度,对于每一行,排版时需要根据单词数量和剩余空格数量来决定如何分配单词之间的空格,这一步需要 O(L) 的时间复杂度。因此总时间复杂度为 O(nL)。
空间复杂度分析:
算法中使用了一个 StringBuilder 对象来存储当前行的字符串,以及一个 List<String> 类型的数组来存储所有排版后的字符串,因此空间复杂度为 O(nL),其中n为单词数量,L为最大单词长度。
LeetCode运行结果:
第三题
题目来源
题目内容
解决方法
方法一:二分查找
题目要求计算一个非负整数x的算术平方根,并返回整数部分。
可以使用二分查找的方法来逼近平方根的整数部分。假设平方根的整数部分为r,那么可以将问题转化为在区间[0, x]中查找一个数r,使得rr<=x且(r+1)(r+1)>x。可以通过二分查找在有序区间中查找满足条件的数。
class Solution {
public int mySqrt(int x) {
if (x == 0) {
return 0;
}
long left = 1;
long right = x;
while (left <= right) {
long mid = left + (right - left) / 2;
if (mid * mid == x) {
return (int)mid;
} else if (mid * mid < x) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return (int)right;
}
}
复杂度分析:
- 时间复杂度:二分查找的时间复杂度为O(logN),其中N为x的值。每次迭代将搜索范围缩小一半,因此需要的迭代次数为O(logN)。
- 空间复杂度:只使用了常数级别的额外空间,空间复杂度为O(1)。
LeetCode运行结果:
方法二:牛顿迭代法
除了二分查找,还可以使用牛顿迭代法来求解平方根。
牛顿迭代法的思想是通过不断逼近函数的零点来求解方程的根。对于求解平方根的问题,可以将其转化为求解方程 f(r) = r^2 - x = 0 的根。根据牛顿迭代法的迭代公式,可以得到如下的迭代过程:
r_{n+1} = r_n - f(r_n) / f'(r_n)
其中,r_n 是第 n 次迭代得到的近似解,f'(r_n) 是 f(r_n) 的导数。
对于平方根的问题,迭代公式可以简化为:
r_{n+1} = (r_n + x / r_n) / 2
class Solution {
public int mySqrt(int x) {
if (x == 0) {
return 0;
}
double r = x;
while (Math.abs(r * r - x) > 0.0001) {
r = (r + x / r) / 2;
}
return (int)r;
}
}
复杂度分析:
- 时间复杂度:牛顿迭代法通常会在较少的迭代次数内收敛,因此时间复杂度可以看作是常数级别的,即 O(1)。
- 空间复杂度:只使用了常数级别的额外空间,空间复杂度为 O(1)。
LeetCode运行结果:
方法三:位操作
除了二分查找和牛顿迭代法,还可以使用位操作的方法来求解平方根。
我们可以从二进制的角度思考,一个非负整数 x 的平方根的整数部分最大不会超过 x 的一半。因此,我们可以从 0 到 x/2 这个范围内进行搜索。
具体思路如下:
- 如果 x 为 0 或 1,直接返回 x。
- 初始化左边界 left 为 0,右边界 right 为 x / 2。
- 在循环中进行二分搜索:
- 计算中间值 mid = (left + right) / 2。
- 比较 mid * mid 和 x 的大小关系:
- 如果 mid * mid == x,返回 mid。
- 如果 mid * mid > x,说明 mid 太大,将右边界 right 更新为 mid - 1。
- 如果 mid * mid < x,说明 mid 可能是解,将左边界 left 更新为 mid + 1,并记录最近一个满足 mid * mid <= x 的数。
- 返回最近满足 mid * mid <= x 的数。
class Solution {
public int mySqrt(int x) {
if (x == 0 || x == 1) {
return x;
}
int left = 0;
int right = x / 2;
int ans = 0;
while (left <= right) {
int mid = (left + right) / 2;
long square = (long)mid * mid;
if (square == x) {
return mid;
} else if (square < x) {
ans = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
return ans;
}
}
复杂度分析:
- 时间复杂度:由于二分搜索的时间复杂度为 O(logN),其中 N 为 x 的值,因此该方法的时间复杂度为 O(logN)。
- 空间复杂度:只使用了常数级别的额外空间,空间复杂度为 O(1)。
LeetCode运行结果: