目录
第一题
题目来源
题目内容
解决方法
方法一:贪心
第二题
题目来源
题目内容
解决方法
方法一:双指针
方法二:KMP算法
方法三:indexOf方法
方法四:Boyer-Moore算法
方法五:Rabin-Karp算法
第三题
题目来源
题目内容
解决方法
方法一:递减法
方法二:位运算和循环
方法三:二分查找
2591. 将钱分给最多的儿童 - 力扣(LeetCode)
class Solution {
public int distMoney(int money, int children) {
// 如果可分配的钱小于儿童的个数,则无法均分,返回-1
if (money < children) {
return -1;
}
// 减去每个儿童至少分配的1美元后剩余的钱
money -= children;
// 计算剩下的钱最多可以再均分给多少个儿童,即money / 7与儿童个数中的较小值
int cnt = Math.min(money / 7, children);
// 已经分配了cnt个儿童
money -= cnt * 7;
children -= cnt;
// 如果剩余的儿童个数为0且剩余的钱大于0,或者剩余的儿童个数为1且剩余的钱为3
// 表示不满足要求,需要减少已分配的儿童个数
if ((children == 0 && money > 0) || (children == 1 && money == 3)) {
cnt--;
}
// 返回已分配的儿童个数
return cnt;
}
}
这段代码是使用贪心思路实现的,计算分配钱币给儿童的最多个数。
复杂度分析:
LeetCode运行结果:
28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)
class Solution {
public int strStr(String haystack, String needle) {
int m = haystack.length();
int n = needle.length();
// 如果 needle 为空字符串,则返回 0
if (n == 0) {
return 0;
}
// 如果 haystack 长度小于 needle 长度,则不可能匹配,返回 -1
if (m < n) {
return -1;
}
for (int i = 0; i <= m - n; i++) {
int j;
// 在 haystack 中从当前位置开始与 needle 进行匹配
for (j = 0; j < n; j++) {
if (haystack.charAt(i + j) != needle.charAt(j)) {
break;
}
}
// 如果 j 的值等于 needle 的长度,则说明找到了匹配项,返回匹配项的下标
if (j == n) {
return i;
}
}
// 没有找到匹配项,返回 -1
return -1;
}
}
复杂度分析:
需要注意的是,虽然上述代码的时间复杂度与 needle 的长度 n 相关,但由于 n 的取值范围很小(1 <= n <= 104),因此该算法在实际情况中通常会表现良好。
LeetCode运行结果:
当使用KMP算法来实现字符串匹配时,需要先构建Next数组,然后利用Next数组进行匹配。
class Solution {
public int strStr(String haystack, String needle) {
if (needle.isEmpty()) {
return 0;
}
int[] next = getNext(needle);
int i = 0, j = 0, m = haystack.length(), n = needle.length();
while (i < m) {
if (haystack.charAt(i) == needle.charAt(j)) {
i++;
j++;
if (j == n) {
return i - j;
}
} else if (j > 0) {
j = next[j - 1];
} else {
i++;
}
}
return -1;
}
private static int[] getNext(String pattern) {
int[] next = new int[pattern.length()];
int i = 1, j = 0, n = pattern.length();
while (i < n) {
if (pattern.charAt(i) == pattern.charAt(j)) {
j++;
next[i] = j;
i++;
} else if (j > 0) {
j = next[j - 1];
} else {
next[i] = 0;
i++;
}
}
return next;
}
}
以上代码中,strStr方法用于进行字符串匹配,返回第一个匹配位置的索引(若不存在匹配则返回-1)。getNext方法用于构建模式串(即 needle)的Next数组。
复杂度分析:
KMP算法的时间复杂度为O(m+n),其中m为原串的长度,n为模式串的长度。
综上所述,KMP算法的时间复杂度为O(m+n)。空间复杂度为O(n),即Next数组的长度。
需要注意的是,KMP算法虽然能够提高字符串匹配的效率,但也有一定的局限性。当模式串过长时,构建Next数组的时间可能会变得很慢,从而影响整个算法的性能。
LeetCode运行结果:
可以使用Java中的indexOf方法来实现。indexOf方法用于查找子字符串在父字符串中第一次出现的位置。
class Solution {
public int strStr(String haystack, String needle) {
return haystack.indexOf(needle);
}
}
在上述代码中,strStr方法接受两个参数haystack和needle,并调用indexOf方法在haystack字符串中查找needle字符串的第一次出现位置。如果找到了匹配项,则返回对应的下标;否则,返回-1。
复杂度分析:
需要注意的是,尽管indexOf方法的时间复杂度为O((n-m+1)m),但在实际应用中,它通常比手动实现的算法更高效。这是因为indexOf方法在底层经过了优化,使用了一些技巧来加速字符串匹配。因此,在大多数情况下,使用现有的API是更好的选择。当然,如果需要特定的定制功能或特别高效的性能,可能需要考虑实现自己的匹配算法。
LeetCode运行结果:
Boyer-Moore算法利用了两个启发式规则来跳过尽可能多的字符,以达到快速匹配的目的。这两个规则是坏字符规则(Bad Character Rule)和好后缀规则(Good Suffix Rule)。
class Solution {
public int strStr(String haystack, String needle) {
int n = haystack.length();
int m = needle.length();
if (m == 0) {
return 0;
}
int[] badChar = new int[256];
Arrays.fill(badChar, -1);
for (int i = 0; i < m; i++) {
badChar[needle.charAt(i)] = i;
}
int s = 0;
while (s <= n - m) {
int j = m - 1;
while (j >= 0 && needle.charAt(j) == haystack.charAt(s + j)) {
j--;
}
if (j < 0) {
return s;
} else {
s += Math.max(1, j - badChar[haystack.charAt(s + j)]);
}
}
return -1;
}
}
在该代码中,我们首先对needle字符串进行预处理,得到坏字符表badChar,其中badChar[c]表示字符c在needle中最靠右出现的位置,如果字符c不在needle中,则为-1。
然后,我们从0开始枚举haystack中长度为m的子串s,并比较s与needle是否相等。如果相等,则返回当前起始位置;否则,我们利用坏字符表来计算s需要移动的距离。
具体来说,我们将字串s中最靠右的坏字符与s中的对应字符对齐,然后向右移动j - badChar[haystack.charAt(s + j)]个字符,其中j - badChar[haystack.charAt(s + j)]表示坏字符在needle中最靠右出现的位置与它在s中出现的位置之差。如果坏字符不在子串s中,则我们可以将s直接向右移动一个位置。
复杂度分析:
值得注意的是,Boyer-Moore算法的性能在处理大文本时更加明显,因为它利用了预处理表格来跳过尽可能多的字符,减少了比较的次数。
总结起来,Boyer-Moore算法是一种高效的字符串匹配算法,适用于处理大文本的情况,时间复杂度为O(n+m),空间复杂度为O(1)。
LeetCode运行结果:
Rabin-Karp算法的基本思想是通过计算字符串的hash值来进行快速匹配。具体步骤如下:
1、首先,判断特殊情况,如果needle为空字符串,则返回0;如果haystack的长度小于needle的长度,则返回-1。
2、接下来,计算needle和haystack的初始hash值。这里使用了一个质数31作为base,并将每个字符映射为对应的整数值。
3、然后,使用一个循环遍历needle的每个字符,计算needleHash和currHash,并更新power的值。
4、然后,在一个循环中,通过滑动窗口的方式在haystack上逐个移动子串,并比较hash值是否相等和子串是否与needle相同。
5、如果循环结束后仍未找到匹配,返回-1表示未找到。
class Solution {
public int strStr(String haystack, String needle) {
int n = haystack.length();
int m = needle.length();
if (m == 0) {
return 0;
}
if (n < m) {
return -1;
}
int prime = 31;
long needleHash = 0;
long currHash = 0;
long power = 1;
for (int i = 0; i < m; i++) {
needleHash = needleHash * prime + (needle.charAt(i) - 'a');
currHash = currHash * prime + (haystack.charAt(i) - 'a');
power *= prime;
}
for (int i = 0; i <= n - m; i++) {
if (currHash == needleHash && haystack.substring(i, i + m).equals(needle)) {
return i;
}
if (i < n - m) {
currHash = currHash * prime - (haystack.charAt(i) - 'a') * power + (haystack.charAt(i + m) - 'a');
}
}
return -1;
}
}
复杂度分析:
综上所述,Rabin-Karp算法的总体时间复杂度为O((n-m+1) * m),其中n是文本串的长度,m是模式串的长度。在最坏情况下,即当哈希值匹配每次都发生冲突时,时间复杂度可能达到O(nm)。
需要注意的是,选择合适的哈希函数和哈希值比较算法对算法的性能有影响。另外,Rabin-Karp算法需要额外的空间来存储哈希值,空间复杂度为O(m)。
LeetCode运行结果:
29. 两数相除 - 力扣(LeetCode)
class Solution {
public int divide(int dividend, int divisor) {
// 处理边界条件
if (dividend == 0) { // 被除数为0
return 0;
}
if (divisor == 1) { // 除数为1
return dividend;
}
if (divisor == -1) { // 除数为-1
if (dividend > Integer.MIN_VALUE) { // 检查是否溢出
return -dividend;
} else {
return Integer.MAX_VALUE;
}
}
// 处理正负号
boolean isNegative = (dividend < 0 && divisor > 0) || (dividend > 0 && divisor < 0);
// 将被除数和除数都转为正数进行计算
long dividendAbs = Math.abs((long) dividend);
long divisorAbs = Math.abs((long) divisor);
int quotient = 0;
while (dividendAbs >= divisorAbs) {
long temp = divisorAbs;
int multiple = 1; // 除数的倍数
while (dividendAbs >= (temp << 1)) {
temp <<= 1;
multiple <<= 1;
}
dividendAbs -= temp;
quotient += multiple;
}
if (isNegative) {
quotient = -quotient;
}
// 检查是否溢出
if (quotient > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
} else {
return quotient;
}
}
}
复杂度分析:
综上所述,代码的时间复杂度主要由循环的迭代次数决定,即O(logN)。其中N是被除数与除数的差值的大小。空间复杂度方面,代码只使用了有限的变量存储中间结果,所以是O(1)的。
需要注意的是,由于代码使用了long类型来处理可能的溢出情况,因此在某些情况下,时间复杂度可能会略微增加,具体取决于具体的输入值。但整体上,该算法的时间复杂度仍然是O(logN)的。
LeetCode运行结果:
思路是使用位运算和循环来实现整数相除的操作。
class Solution {
public int divide(int dividend, int divisor) {
// 判断特殊情况:除数为0、被除数为最小值且除数为-1(防止溢出)
if (divisor == 0 || (dividend == Integer.MIN_VALUE && divisor == -1)) {
return Integer.MAX_VALUE;
}
// 判断结果的符号
boolean negative = (dividend < 0) ^ (divisor < 0);
// 将被除数和除数都转为负数进行计算,防止溢出
long dvd = Math.abs((long) dividend);
long dvs = Math.abs((long) divisor);
int result = 0;
while (dvd >= dvs) {
long temp = dvs;
long multiple = 1;
while (dvd >= (temp << 1)) {
temp <<= 1;
multiple <<= 1;
}
dvd -= temp;
result += multiple;
}
// 根据符号确定结果的正负
if (negative) {
return -result;
} else {
return result;
}
}
}
复杂度分析:
需要注意的是,在处理边界情况时,判断除数是否为0以及被除数为最小值且除数为-1等操作都是常数级别的操作,并不会改变算法的时间和空间复杂度。
综上所述,该算法的时间复杂度为O(log(max(dividend, divisor))),空间复杂度为O(1)。
LeetCode运行结果:
使用了二分查找的思想,通过不断将搜索范围缩小来逼近商的值,直到找到最接近的商。
具体实现步骤如下:
class Solution {
public int divide(int dividend, int divisor) {
// 特殊情况处理
if (dividend == Integer.MIN_VALUE && divisor == -1) {
return Integer.MAX_VALUE;
}
// 将被除数和除数转换为长整型
long dvd = Math.abs((long) dividend);
long dvs = Math.abs((long) divisor);
// 确定搜索的上下界
long left = 0, right = dvd;
int res = 0;
// 二分查找
while (left <= right) {
long mid = (left + right) / 2;
if (mid * dvs <= dvd) {
res = (int) mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
// 根据符号返回结果
return ((dividend > 0) ^ (divisor > 0)) ? -res : res;
}
}
复杂度分析:
LeetCode运行结果: