28.实现strStr()
思路:逐个比对字符串,暴力求解,复杂度极大。从题解中了解KMP算法。具体实现可参考LeetCode官方题解。https://leetcode-cn.com/problems/implement-strstr/solution/shi-xian-strstr-by-leetcode-solution-ds6y/
代码:
public int strStr(String haystack, String needle) {
if (needle.length() == 0) {
return 0;
}
if (haystack.length() < needle.length()) {
return -1;
}
int[] next = new int[needle.length()];
for (int i = 1, j = 0; i < needle.length(); i++) {
while (j > 0 && needle.charAt(i) != needle.charAt(j)) {
j = next[j-1];
}
if (needle.charAt(i) == needle.charAt(j)) {
j ++;
}
next[i] = j;
}
for (int i = 0, j = 0; i < haystack.length(); i++) {
while (j > 0 && haystack.charAt(i) != needle.charAt(j)) {
j = next[j-1];
}
if (haystack.charAt(i) == needle.charAt(j)) {
j ++;
}
if (j == needle.length()) {
return i - needle.length() + 1;
}
}
return -1;
}
时间复杂度:O(m+n),其中m是原字符串的长度,n是需要找的字符串的长度
空间复杂度:O(n)
29.两数相除
思路:题目不允许使用乘法,除法和mod运算。就像乘法是加法的叠加,除法是减法的的叠加。以除数为单位,,一直减到被除数小于除数,此时减去的除数个数,即为答案。此时我们可以利用二进制的概念,来加快减法进程,通过相加获得多倍除数,倍率为2i。从不大于被除数的多倍除数开始减,直到被除数小于单倍除数时,则计算完成。由于负数的取值范围要大于正数,因此统一转换为负数进行计算,异号时返回负数,同号时返回正数。
代码:
public int divide(int dividend, int divisor) {
if (dividend == Integer.MIN_VALUE) {
if (divisor == -1) {
return Integer.MAX_VALUE;
}
if (divisor == 1) {
return Integer.MIN_VALUE;
}
}
if (divisor == Integer.MIN_VALUE) {
return dividend == Integer.MIN_VALUE ? 1 : 0;
}
boolean op = false;
if (dividend > 0) {
dividend = -dividend;
op = !op;
}
if (divisor > 0) {
divisor = -divisor;
op = !op;
}
List<Integer> list = new ArrayList<>();
list.add(divisor);
int index = 0;
while (list.get(index) >= dividend - list.get(index)) {
list.add(list.get(index) + list.get(index));
index ++;
}
int answer = 0;
for (int i = list.size() - 1; i >= 0; i--) {
if (dividend <= list.get(i)) {
answer += 1 << i;
dividend -= list.get(i);
}
}
return op ? -answer : answer;
}
时间复杂度:O(logN),N是被除数的大小
空间复杂度:O(logN)
30.串联所有单词的子串
思路1:固定大小滑动窗口。窗口长度为所有单词的长度之和,即words.length * words[0].length()。窗口左边界的取值范围为0~s.length() - words.length * words[0].length(),窗口长度为words.length * words[0].length()。建立HashMap保存words中的单词及频次,从窗口左边界开始取词,并匹配。每当匹配时,就将map中的对应频次-1,当对应频次减到0时,从map中清除掉这个key。当遇到不包含的key时,标志以当前左边界作为开始的字符串不能满足条件,将窗口向右滑动1。
代码:
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> res = new ArrayList<>();
if (words.length * words[0].length() > s.length()) {
return res;
}
for (int i = 0; i < s.length() - words.length * words[0].length() + 1; i++) {
Map<String, Integer> remain = new HashMap<>();
for (String word : words) {
remain.put(word, remain.getOrDefault(word, 0) + 1);
}
int index = 0;
while (index < words.length * words[0].length()) {
if (remain.containsKey(s.substring(i + index, i + index + words[0].length()))) {
if (remain.get(s.substring(i + index, i + index + words[0].length())) == 1) {
remain.remove(s.substring(i + index, i + index + words[0].length()));
} else {
remain.put(s.substring(i + index, i + index + words[0].length()), remain.get(s.substring(i + index, i + index + words[0].length())) - 1);
}
index += words[0].length();
} else {
break;
}
}
if (remain.isEmpty()) {
res.add(i);
}
}
return res;
}
时间复杂度:O(n * m),n为整个字符串的长度,m为所有单词的长度之和
空间复杂度:O(m)
思路2:可变大小的滑动窗口。设单个单词长度为m,窗口左边界为l,右边界为r。则l的取值可归纳为i + len * n,其中i的取值为0~i-1,而l的取值为s.length() - len。因此我们遍历i的所有可能性来确定初始的l取值,同时r的初始值等于l。让r向后滑动,即窗口扩大,每次扩大一个单词的长度,并将新进入窗口的词进行统计。若当前窗口内的新晋词不存在于要找的单词之中时,则说明目前窗口无法满足条件,因此将窗口的左边界设置到这个词右侧,同时将大小归零,重新滑动。如果新晋词存在与要找的单词中,则统计词频。当目前窗口内新晋词的词频超过了要找的单词中该词的词频时,我们要滑动窗口使得当前窗口中的该词词频减少至和原单词组中该词的词频一致。因此需要从左侧滑动窗口,不断减少窗口大小,直至满足条件。当滑动完成后,若窗口中的词数量恰好等于要找的单词数量,则说明此时的窗口符合条件,将窗口左边界加入结果集。
代码:
public List<Integer> findSubstring(String s, String[] words) {
int num = words.length, len = words[0].length();
List<Integer> res = new ArrayList<>();
if (len * num > s.length()) {
return res;
}
Map<String, Integer> map = new HashMap<>();
for (String word : words) {
map.put(word, map.getOrDefault(word, 0) + 1);
}
for (int i = 0; i < len; i++) {
int l = i, r = i, count = 0;
Map<String, Integer> tempMap = new HashMap<>();
while (r + len <= s.length()) {
String temp = s.substring(r, r + len);
r += len;
if (map.containsKey(temp)) {
count ++;
tempMap.put(temp, tempMap.getOrDefault(temp, 0) + 1);
while (tempMap.get(temp) > map.get(temp)) {
String lTemp = s.substring(l, l + len);
l += len;
count --;
tempMap.put(lTemp, tempMap.get(lTemp) - 1);
}
if (count == num) {
res.add(l);
}
} else {
l = r;
count = 0;
tempMap.clear();
}
}
}
return res;
}
时间复杂度:O(m * n),此时m是原字符串的长度,而n则是单个单词的长度
空间复杂度:O(n * k),此时k为单词个数