3.无重复字符的最长子串(中等)
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串
方法一:采用滑动窗口:
class Solution
{
public:
int lengthOfLongestSubstring(string s)
{
//s[start,end) 前面包含 后面不包含
int start(0), end(0), length(0), result(0);
int sSize = int(s.size());
while (end < sSize)
{
char tmpChar = s[end];
for (int index = start; index < end; index++)
{
if (tmpChar == s[index])
{
start = index + 1;
length = end - start;
break;
}
}
end++;
length++;
result = max(result, length);
}
return result;
}
};
也就相当于用了两个指针start和end,开始时都指向字符串的开始位置,然后进行while循环,遍历整个字符串,如果end指向的字符在前面[start,end)这个左闭右开的区间中没有出现过,那么就让end指向下一个字符,length的长度++,最后比较result和目前这个length的长度,取所有找到的length中的最大值;
如果end指向的那个字符在前面的那个区间中存在过,那么让这个窗口的start指向区间中相同元素的下一个字符,并让length的长度改变,相当于改变了这个窗口的位置,end接着指向下一个字符,继续判断,直到结束。观察效率,感觉好厉害啊
执行用时 :
8 ms, 在所有 C++ 提交中击败了92.82%的用户
内存消耗 :8.5 MB, 在所有 C++ 提交中击败了100.00%的用户
方法二:利用hashmap优化
class Solution
{
public:
int lengthOfLongestSubstring(string s)
{
//s[start,end) 前面包含 后面不包含
int start(0), end(0), length(0), result(0);
int sSize = int(s.size());
unordered_map<char, int> hash;
while (end < sSize)
{
char tmpChar = s[end];
//仅当s[start,end) 中存在s[end]时更新start
if (hash.find(tmpChar) != hash.end() && hash[tmpChar] >= start)
{
start = hash[tmpChar] + 1;
length = end - start;
}
hash[tmpChar] = end;
end++;
length++;
result = max(result, length);
}
return result;
}
};
与方法一类似,这里采用哈希表,如果end对应的字符在哈希表中存在,而且位置在start后面,那么就改变start的指向和length的长度
在这里多贴几种写法,用于后面复习的适合比较:
方法三:利用数组(桶)来代替hashmap
class Solution
{
public:
int lengthOfLongestSubstring(string s)
{
//s[start,end) 前面包含 后面不包含
int start(0), end(0), length(0), result(0);
int sSize = int(s.size());
vector<int> vec(128, -1);
while (end < sSize)
{
char tmpChar = s[end];
//仅当s[start,end) 中存在s[end]时更新start
if (vec[int(tmpChar)] >= start)
{
start = vec[int(tmpChar)] + 1;
length = end - start;
}
vec[int(tmpChar)] = end;
end++;
length++;
result = max(result, length);
}
return result;
}
};
思想与之前类似,只不过将hashmap替换为了桶,过程如下:
方法四:让left指向窗口的前一个位置
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int res = 0, left = -1, n = s.size();
unordered_map<int, int> m;
for (int i = 0; i < n; ++i) {
if (m.count(s[i]) && m[s[i]] > left) {
left = m[s[i]];
}
m[s[i]] = i;
res = max(res, i - left);
}
return res;
}
};
建立一个 HashMap,建立每个字符和其最后出现位置之间的映射,然后需要定义两个变量 res 和 left,其中 res 用来记录最长无重复子串的长度,left 指向该无重复子串左边的起始位置的前一个,由于是前一个,所以初始化就是 -1,然后遍历整个字符串,对于每一个遍历到的字符,如果该字符已经在 HashMap 中存在了,并且如果其映射值大于 left 的话,那么更新 left 为当前映射值。然后映射值更新为当前坐标i,这样保证了 left 始终为当前边界的前一个位置,然后计算窗口长度的时候,直接用 i-left 即可,用来更新结果 res。
方法五:方法四的优化,用桶来代替hashmap
class Solution {
public:
int lengthOfLongestSubstring(string s) {
vector<int> m(128, -1);
int res = 0, left = -1;
for (int i = 0; i < s.size(); ++i) {
left = max(left, m[s[i]]);
m[s[i]] = i;
res = max(res, i - left);
}
return res;
}
};
建立一个 256 位大小的整型数组来代替 HashMap,这样做的原因是 ASCII 表共能表示 256 个字符,但是由于键盘只能表示 128 个字符,所以用 128 也行,然后全部初始化为 -1,这样的好处是不用像之前的 HashMap 一样要查找当前字符是否存在映射对了,对于每一个遍历到的字符,直接用其在数组中的值来更新 left,因为默认是 -1,而 left 初始化也是 -1,所以并不会产生错误,这样就省了 if 判断的步骤。
121.买卖股票的最佳时机Ⅰ
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,
在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
答:这道题我们需要找出给定数组中两个数字之间的最大差值(即,最大利润)。此外,第二个数字(卖出价格)必须大于第一个数字(买入价格)。
方法一:暴力法
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = (int)prices.size(), ans = 0;
for (int i = 0; i < n; ++i){
for (int j = i + 1; j < n; ++j){
ans = max(ans, prices[j] - prices[i]);
}
}
return ans;
}
};
分析:暴力法就是遍历prices[]数组,将每一个后一个元素减去前一个元素的值都算出来,然后取最大值即可。
方法二:一次遍历:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int inf = 1e9;
int n = (int)prices.size();
int minprice = inf, maxprofit = 0;
for (int i = 0 ;i < n; ++i){
maxprofit = max(maxprofit, prices[i] - minprice);
minprice = min(prices[i], minprice);
}
return maxprofit;
}
};
分析:
1.记录【今天之前买入的最小值】
2.计算【今天之前最小值买入,今天卖出的获利】,也即【今天卖出的最大获利】,然后比较最小值与今天的值,修改最小值
3.比较【每天的最大获利】,取最大值即可
本文参考了很多leetcode别人的算法,并结合自己的想法,重在理解