26 删除排序数组中的重复项
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
分析:
其实这道题目就很简单了,只是要注意题目要求不能使用额外的数组空间以及必须原地修改数组。那么我们只需要用一个临时变量保存前一个数组元素, 然后遍历数组,用遍历的当前元素与之对比,如果相同,删除当前元素;如果不同,令变量等于当前元素。 为了防止在遍历过程中删除元素对数组范围造成影响,我们采取从后向前的方式去遍历数组。
int removeDuplicates(vector& nums) {
if(nums.size() == 0) return 0;
int preNum = nums.back();
for(int i = nums.size() - 2; i >= 0; --i) {
if(nums[i] == preNum) {
nums.erase(nums.begin() + i);
}else {
preNum = nums[i];
}
}
return nums.size();
}
27.移除元素
给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。
分析:
这道题比上一道更加单,我们甚至不需要用变量保存目标元素了,只需要对上一段代码稍加修改就行了。
int removeElement(vector& nums) {
if(nums.size() == 0) return 0;
for(int i = nums.size() - 1; i >= 0; --i) {
if(nums[i] == val) nums.erase(nums.begin() + i);
}
return nums.size();
}
28. 实现strStr()
实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
示例 1:
输入: haystack = "hello", needle = "ll"
输出: 2
示例 2:
输入: haystack = "aaaaa", needle = "bba"
输出: -1
说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。
分析:
首先,我们需要知道strStr()函数的作用是什么。 strStr()函数就是给定一个源字符串以及一个目标字符串, 如果目标字符串在源字符串中存在,则返回目标字符串在源字符串中的位置,如果不存在返回-1。首先,我们分析特殊情况:
- 源字符串为空, 此时返回-1;
- 目标字符串为空, 此时返回0;
然后分析一般情况。 在一般情况下, 我们首先要遍历源字符串,找到字符串中与目标字符串第一个字符相同的项, 然后从此项开始往后依次比较,如果全部匹配, 则找到位置,返回。 如果不匹配,继续向后循环知道找到匹配项或者遍历结束。那么到哪个位置遍历结束呢? 我们可以不必等到遍历到源字符串末尾,如果源字符串剩余长度小于目标字符串,则一定不匹配了,此时循环结束。
int strStr(string haystack, string needle) {
if(needle.length() == 0) return 0;
int index = -1;
for(int i = 0; i < haystack.length(); ++i) {
if(haystack.length() - i < needle.length()) break;
int k = i;
bool isMatch = true;
while((k - i) < needle.length()) {
if(needle[k-i] == haystack[k]) {
++k;
continue;
} else {
isMatch = false;
break;
}
}
if(isMatch) return index;
}
return index;
}
29. 两数相除
给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。
返回被除数 dividend 除以除数 divisor 得到的商。
示例 1:
输入: dividend = 10, divisor = 3
输出: 3
示例 2:
输入: dividend = 7, divisor = -3
输出: -2
说明:
被除数和除数均为 32 位有符号整数。
除数不为 0。
假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−231, 231-1]。本题中,如果除法结果溢出,则返回 231-1。
分析:
由于题目限定不能使用乘法、除法以及mod运算符,那么我们首先根据除法的定义我们可以使用减法,用被除数不断的减去除数,直到被除数小于除数为止。这里记录一共减了多少次得到的次数就是需要的结果。我们考虑一下特殊情况:
- 如果两个数相等, 直接返回1
- 由于有位数限制, 如果除数为最小值,即:-232, 直接返回0
- 如果有负数, 我们先将符号保存下来, 然后将除数与被除数转换为整数然后计算
最后,将计算记过加上符号再与取值范围相比较,返回最终结果。
考虑到不断相减的话,如果被除数过大,比如232,而除数过小如1,此时运行时间将会变得很长。所以我们对减法形式做一些优化。我们可以考虑将除数不断以2的倍数增加,直到除数大于被除数。用两个临时变量tmp存储当前的除数,i存储当前次数。如果我们将tmp*2 然后用被除数减去tmp, 就表示我们进行了2次的减法运算了,此时i也乘以2, 然后计算次数加上i;如果被除数还是大于tmp,那我们就将tmp再次增加2倍。在计算机中乘以2就是向右移一位,所以我们用位移运算符<<进行计算。
int divide(int dividend, int divisor) {
if(dividend == divisor) return 1;
if(divisor == INT_MIN) return 0;
bool isPositive = false;
if((dividend < 0 && divisor < 0) || (dividend > 0 && divisor > 0)) isPositive = true;
long long divide = 0;
// 如果除数恰好等于最小值,先做一次运算 避免溢出
if(dividend == INT_MIN) {
dividend = divisor < 0 ? dividend - divisor : dividend + divisor;
divide += 1;
}
long long uDividend = dividend < 0 ? -dividend : dividend;
long long uDivisor = divisor < 0 ? -divisor : divisor;
while(uDividend >= uDivisor) {
long long temp = uDivisor;
long long i = 1;
while(uDividend >= temp) {
uDividend -= tmp;
divide += i;
i == i<<1;
tmp = tmp << 1;
}
}
divide = isPositive ? divide : -divide;
if(divide < INT_MIN || divide > INT_MAX) return INT_MAX;
return divide;
}
30.串联所有单词
给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
示例 1:
输入:
s = "barfoothefoobarman",
words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoor" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。
示例 2:
输入:
s = "wordgoodgoodgoodbestword",
words = ["word","good","best","word"]
输出:[]
分析:
由题目可知,数组words中的单词长度是一致的,因此我们在遍历字符串s时,只需要每次截取这样长度的子字符串,然后看此子字符串是否存在于words数组中。此题的难度就在于对words数组的处理当中,由于words数组中的单词无序而且可能重复,同时可以组成的字符串的顺序也是任意的,所以如果我们直接暴力遍历比较的话时间复杂度将会急剧增加。所以我们可以用一个map以<单词,数量>的形式将words数组转化后存储起来,每次从字符串s中截取子串后查看是否在map中,在的话相应的计数减1;同时用一个变量保存所有单词的个数,然后也减1,直到遍历到s的末尾或者数量为0。
vector findSubstring(string s, vector& words) {
vector res;
if(s.empty() || words.empty()) return res;
int wordLen = words[0].length();
int sizeWords = wordLen * words.size();
if(s.length() < sizeWords) return res;
std::unordered_map word_map;
for(int i = 0; i < words.size(); ++i) {
word_map[words[i]]++;
}
for(int i = 0; i < s.length(); ++i) {
int j = i, count = word.size();
string pre = s.substr(j, wordLen);
if(word_map.find(pre) != word_map.end()) {
//由于操作会引起map内值的改变,所以这里我们用一份副本来进行当前的匹配
std::unordered_map temp_word_map(word_map);
while(j < i + sizeWords) {
string word = s.substr(j, wordLen);
if(word_map.find(word) == word_map.end() || temp_word_map[word] == 0) {
break;
} else {
temp_word_map[word]--;
count--;
}
j += wordLen;
}
if(count == 0) {
res.push_back(i);
}
}
}
return res;
}