一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第20天,点击查看活动详情。
剑指 Offer 48. 最长不含重复字符的子字符串、
思路
(双指针扫描) O(n)
定义两个指针 i,j(i<=j),表示当前扫描到的子串是 [i,j] (闭区间)。扫描过程中维护一个哈希表unordered_map <chat,int>hash,表示 [i,j]中每个字符出现的次数。
线性扫描时,每次循环的流程如下:
- 1.指针 j 向后移一位, 同时将哈希表中
s[j]的计数加一:hash[s[j]]++; - 2.假设 j 移动前的区间 [i,j]中没有重复字符,则 j 移动后,只有
s[j]可能出现2次。因此我们不断向后移动i,直至区间[i,j]中s[j]的个数等于1为止;
时间复杂度分析: 由于 i,j 均最多增加n次,且哈希表的插入和更新操作的复杂度都是 O(1),因此,总时间复杂度 O(n).
c++代码
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char, int> hash; //存贮每个字符出现的次数
int res = 0;
for(int j = 0, i = 0; j < s.size(); j++){ //[j, i]
hash[s[j]]++;
while(hash[s[j]] > 1) hash[s[i++]]--;
res = max(res, j - i + 1);
}
return res;
}
};
剑指 Offer 49. 丑数
思路
(三路归并) O(n)
我们把只包含质因子 2、3 和 5 的数称作丑数,使用vector<int> res 存储每个丑数,且已知第一个丑数是 1,即 res[0] = 1。
用 i,j,k,三个指针分别指向三个序列:
i指向质因子包含2的所有数组成的序列 II。j指向质因子包含3的所有数组成的序列 III。k指向质因子包含5的所有数组成的序列 V。
初始状态下三个指针都是0,指向第一个丑数 res[0] = 1。
三路归并,每次取 res[i]∗2,res[j]∗3,res[k]∗5中的最小值,就是下一个丑数。 其中 res[i] 是序列 II 的第 i个数,那么 res[i]∗2 就是第 i + 1 个数,res[j] 是序列 III 的第 j 个数,那么 res[j]∗3 就是第 j + 1 个数,res[k] 是序列 V 的第 k 个数,那么 res[k]∗5就是第 k + 1个数。
res[0] = 1 不属于任何序列。
如果下一个丑数为 res[i]∗2,则 i指针向往后移,如果 为res[j]∗3,则 j指针往后移,如果为res[k]∗5,则 k 指针往后移。
细节:
如果下一个丑数即是 2 的倍数也是 3 的倍数,那么指针 i 和 j 都要往后移。
时间复杂度分析: 求第 n 个丑数,已知第一个丑数是 1,循环 n - 1 次即可求得,时间复杂度为 O(n)。
c++代码
class Solution {
public:
int nthUglyNumber(int n) {
vector<int> res;
res.push_back(1);
int i = 0, k = 0, j = 0;
while(--n){
int t = min(res[i] * 2, min(res[j] * 3, res[k] * 5));
res.push_back(t);
if(t % 2 == 0) i++;
if(t % 3 == 0) j++;
if(t % 5 == 0) k++;
}
return res.back();
}
};
剑指 Offer 50. 第一个只出现一次的字符
思路
(哈希) O(n)
1、定义一个hash表,存贮字符串s中每个字符出现的次数。
2、遍历整个字符串,如果字符串s中的某个字符出现的次数为1,则我们返回该字符。
3、如果没有,则返回空格。
时间复杂度分析: O(n)。
c++代码
class Solution {
public:
char firstUniqChar(string s) {
unordered_map<char, int> hash;
for(char c : s) hash[c]++;
for(char c : s){
if(hash[c] == 1) return c;
}
return ' ';
}
};