目录
复习哈希表
使用哈希统计字符串中的字符个数
#include<stdio.h>
#include<string>
int main(int argc, char *argv[]) {
int char_map[128] = { 0 };
std::string str = "abcdefgaaxxy";
for (auto i : str) {
char_map[i]++;
}
for (int i = 0; i < 128; i++) {
if (char_map[i] > 0) {
printf("[%c][%d]:%d\n", i, i, char_map[i]);
}
}
return 0;
}
使用哈希排序数组中的元素
#include<stdio.h>
#include<string>
int main(int argc, char *argv[]) {
int random[10] = { 99,1,444,7,20,1,3,7,7 ,9};
int hash_map[1000] = { 0 };
for (auto i : random) {
hash_map[i]++;
}
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < hash_map[i]; j++) {
printf("%d\n", i);
}
}
return 0;
}
思考:
解决哈希冲突的方法——拉链法
代码实现:
#include<stdio.h>
#include<vector>
struct ListNode {
int val;
ListNode *next;
ListNode(int x):val(x),next(nullptr){}
};
int hash_func(int key, int table_len) {
return key % table_len;
}
//头插法
void insert(ListNode* hash_table[], ListNode *node, int table_len) {
int hash_key = hash_func(node->val, table_len);
node->next = hash_table[hash_key];
hash_table[hash_key] = node;
}
bool search(ListNode *hash_table[], int value, int table_len) {
int hash_key = hash_func(value, table_len);
ListNode *head = hash_table[hash_key];
while (head) {
if (head->val == value) {
return true;
}
head = head->next;
}
return false;
}
int main(int argc, char *argv[]) {
const int TABLE_LEN = 11;
ListNode *hash_table[TABLE_LEN] = { 0 };
std::vector<ListNode*>hash_node_vec;
int test[8] = { 1,1,4,9,20,30,150,500 };
for (auto i : test) {
hash_node_vec.push_back(new ListNode(i));
}
for (auto i : hash_node_vec) {
insert(hash_table, i, TABLE_LEN);
}
printf("Hash Table \n");
for (int i = 0; i < TABLE_LEN; i++) {
printf("[%d]:", i);
ListNode *head = hash_table[i];
while (head) {
printf("->%d", head->val);
head = head->next;
}
printf("\n");
}
printf("\n");
printf("Test search:\n");
for (int i = 0; i < 10; i++) {
if (search(hash_table, i, TABLE_LEN)) {
printf("%d is in the hash table. \n",i);
}
else {
printf("%d is not in the hash table. \n",i);
}
}
return 0;
}
leetcode 409 最长回文串
给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。
在构造过程中,请注意区分大小写。比如 "Aa"
不能当做一个回文字符串。
注意:
假设字符串的长度不会超过 1010。
示例 1:
输入:
"abccccdd"
输出:
7
解释:
我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。
思考:
利用哈希统计字符,出现次数为奇数的字符放在中间,偶数的放在两侧。设置一个标记变量flag,若存在出现次数为奇数的字符,则flag的值设置为1,表明其中的一个出现次数为奇数的字符可以作为字符串的中心。
class Solution {
public:
int longestPalindrome(string s) {
if(s.length()==0) return 0;
int char_map[128]={0};
int max_length=0;
int flag=0;
for(int i=0; i<s.length(); i++){
char_map[s[i]]++;
}
for(int i=0; i<128; i++){
if(char_map[i]%2==0){
max_length+=char_map[i];
}else{
//奇数个,则丢弃一个字符
max_length+=char_map[i]-1;
flag=1;
}
}
//若存在出现次数为奇数的字符,则flag的值为1,
//表明其中的一个出现次数为奇数的字符可以作为字符串的中心
return max_length+flag;
}
};
leetcode 290 单词模式
给定一种 pattern(模式)
和一个字符串 str
,判断 str
是否遵循相同的模式。
这里的遵循指完全匹配,例如, pattern
里的每个字母和字符串 str
中的每个非空单词之间存在着双向连接的对应模式。
示例1:
输入: pattern = "abba", str = "dog cat cat dog"
输出: true
示例 2:
输入:pattern = "abba", str = "dog cat cat fish"
输出: false
示例 3:
输入: pattern = "aaaa", str = "dog cat cat dog"
输出: false
示例 4:
输入: pattern = "abba", str = "dog dog dog dog"
输出: false
思路:
class Solution {
public:
bool wordPattern(string pattern, string str) {
map<string, char>word_map;
char used[128]={0};
string word;
int pos=0;
str.push_back(' ');
for(int i=0; i<str.length(); i++){
if(str[i]==' '){
//pattern的长度比str中单词个数小
if(pos==pattern.length()){
return false;
}
//单词没有出现在哈希表中
if(word_map.find(word)==word_map.end()){
//但是对应的字母已经被使用过了
if(used[pattern[pos]]){
return false;
}
word_map[word]=pattern[pos];
used[pattern[pos]]=1;
}
else{
//当前Word已经建立映射,但是无法与pattern对应
if(word_map[word]!=pattern[pos]){
return false;
}
}
word="";
pos++;
}else{
word+=str[i];
}
}
//有多余的pattern字符
if(pos!=pattern.length()){
return false;
}
return true;
}
};
错误的思路:
用map,以str中的字母和pattern中的单词作为键值对。最后map的尺寸如果和str中字母的种数相同,则匹配。
结果:
失败了,因为map中对于已存在的键,他在做insert操作的时候,会直接忽略该值。下面代码中helper中只有“a,dog”和“b,cat”这两对,后面的lll和fish并没有被存入。
#include<vector>
#include<iostream>
#include<map>
#include<set>
using namespace std;
void wordPattern(string pattern, string str) {
map<char, string>helper;
set<char>character;
vector<string>container;
string temp = "";
for (int i = 0; i < str.length(); i++) {
if (str[i] == ' ') {
container.push_back(temp);
temp = "";
}
else {
temp.append(1, str[i]);
}
}
container.push_back(temp);
if (pattern.length() != container.size()) return;
//helper中只有“a,dog”和“b,cat”这两对,后面的lll和fish并没有被存入
for (int i = 0; i < pattern.length(); i++) {
helper.insert(make_pair(pattern[i], container[i]));
int a = 0;
}
}
int main(int argc, char *argv[]) {
string pattern = "abba";
string str = "dog cat lll fish";
wordPattern(pattern, str);
return 0;
}
leetcode 49 字母异位词分组
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: ["eat", "tea", "tan", "ate", "nat", "bat"],
输出:
[ ["ate","eat","tea"], ["nat","tan"], ["bat"] ]
说明:
- 所有输入均为小写字母。
- 不考虑答案输出的顺序。
思路:
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
vector<vector<string>>result;
map<string, vector<string>>anagram;
for(int i=0; i<strs.size(); i++){
string str=strs[i];
sort(str.begin(), str.end());
//哈希表中不存在该组合
if(anagram.find(str)==anagram.end()){
//创建vector
vector<string>item;
anagram[str]=item;
}
//将字符串存入对应vector中
anagram[str].push_back(strs[i]);
}
map<string, vector<string>>::iterator it;
for(it=anagram.begin(); it!=anagram.end(); it++){
result.push_back((*it).second);
}
return result;
}
};
leetcode 3 无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
思路:
使用两根指针begin和 i 在字符串上从左向右滑动,保持两根指针之间的每个字符都只出现一次。用一个string类型的变量 Word记录两根指针之间的子串。
用一个哈希表记录两根指针之间的字符以及每个字符出现的次数。
i 向右滑动:
- 如果 i 所指的字符在begin和 i 之间没出现过,则将该字符追加到Word后面。
- 如果 i 指向的字符在begin和 i 之间出现过,则将begin向右滑动,直到当前 i 指针指向的字符出现次数为 1 时为止,同时更新Word中记录的子串。
用一个string类型的变量 result 去记录最长的子串,遍历的时候不断更新 result 的值遍历完整个字符串时,result 上记录的就是最长子串了。
int lengthOfLongestSubstring(string s) {
int begin = 0, result = 0;
string word = "";
int char_map[128] = { 0 };
for (int i = 0; i < s.length(); i++) {
char_map[s[i]]++;
if (char_map[s[i]] == 1) {
//将只出现过一次的Word追加到Word尾部
word += s[i];
//更新最长子串的值
if (result < word.length()) {
result = word.length();
}
} else {
//当前字符出现次数超过一次,并且当前i指向的字符出现次数超过一次
while (begin < i && char_map[s[i]]>1) {
//产出哈希表中begin所指向的字符
char_map[s[begin]]--;
//移动begin指针,知道char_map[s[i]]中字符出现一次为止
begin++;
}
//清空Word的记录
word = "";
//将跟新后的begin和i之间的字符存入Word中
for (int j = begin; j <= i; j++) {
word += s[j];
}
}
}
return result;
}
leetcode 187 重复的DNA序列
所有 DNA 由一系列缩写为 A,C,G 和 T 的核苷酸组成,例如:“ACGAATTCCG”。在研究 DNA 时,识别 DNA 中的重复序列有时会对研究非常有帮助。
编写一个函数来查找 DNA 分子中所有出现超过一次的10个字母长的序列(子串)。
示例:
输入: s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
输出: ["AAAAACCCCC", "CCCCCAAAAA"]
思路:
方法一:使用map记录字符串中每一个长度为10的子串出现的次数。以string为键,以string出现的次数为值。
vector<string> findRepeatedDnaSequences(string s) {
vector<string>result;
if (s.length() < 10) return result;
map<string, int>container;
//查看每个长度为10的子串
for (int i = 0; i < s.length(); i++) {
string word = s.substr(i, 10);
if (container.find(word) != container.end()) {
container[word]++;
} else {
container[word] = 1;
}
}
for (auto i : container) {
if (i.second > 1) {
result.push_back(i.first);
}
}
return result;
}
方法二:位运算
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s) {
vector<string>result;
if (s.length() < 10) return result;
//每次调用都需要更新全局数组
for (int i = 0; i < 1048576; i++) {
g_hash_map[i] = 0;
}
int char_map[128] = { 0 };
char_map['A'] = 0;//00
char_map['C'] = 1;//01
char_map['G'] = 2;//10
char_map['T'] = 3;//11
int key = 0;
//存储前10个字符
//输入 AACCTTGGAA TTCCGGAATTCCGG 输入的是AAGGTTCCAA
//00 00 10 10 11 11 01 01 00 00 是最终存储的值 AA GG TT CC AA
for (int i = 9; i >= 0; i--) {
key = (key << 2) + char_map[s[i]];
}
g_hash_map[key] = 1;
for (int i = 10; i < s.length(); i++) {
//00 00 00 10 10 11 11 01 01 00
//AA GG TT CC A
key = key >> 2;
//11 00 00 10 10 11 11 01 01 00
//T AA GG TT CC A
key = key | (char_map[s[i]] << 18);
g_hash_map[key]++;
}
for (int i = 0; i < 1048576; i++) {
if (g_hash_map[i] > 1) {
result.push_back(change_in_to_DNA(i));
}
}
return result;
}
};
leetcode 76 最小覆盖子串
给定一个字符串 S 和一个字符串 T,请在 S 中找出包含 T 所有字母的最小子串。
示例:
输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"
说明:
- 如果 S 中不存这样的子串,则返回空字符串
""
。 - 如果 S 中存在这样的子串,我们保证它是唯一的答案。
思路:
类似于leetcode 3,使用两根指针 begin 和 i ,从字符串s的起始位置出发向右滑动。
使用两个数组map_s和map_t,
- map_s——存储当前窗口中的每个字符从数量
- map_t——存储 t 中的每个字符出现的次数
i 指针先向右逐个扫描s中的字符,在 i 移动的过程中,不断去判断当前 i 指向的字符能否出发指针 begin 的移动。
触发begin移动的条件(begin不能超过 i ):
- 当前begin指向的字符在 t 中没出现过,则向右移动begin指针
- 当前begin指向的字符(一定是t中的某个字符)在两个指针之间出现的次数大于该字符在t中出现的次数,则向右移动begin指针
class Solution {
public:
bool is_window_ok(int map_s[], int map_t[], vector<int>&vec_t) {
for (int i = 0; i < vec_t.size(); i++) {
//如果该字符在map_s中出现的次数少于在map_t中出现的次数
if (map_s[vec_t[i]] < map_t[vec_t[i]])
return false;
}
return true;
}
string minWindow(string s, string t) {
const int MAX_ARRAY_LEN = 128;//char 0-127.利用数组下标记录字符个数
int map_t[MAX_ARRAY_LEN] = { 0 };//记录t字符串各个字符的个数
int map_s[MAX_ARRAY_LEN] = { 0 };//记录s字符串各个字符的个数
vector<int>vec_t;//记录t字符串有哪些字符
for (auto i : t) //遍历t,记录t字符串中的各个字符的个数
map_t[i]++;
for (int i = 0; i < MAX_ARRAY_LEN; i++) { //将t中出现的字符存储在vec_t中
if (map_t[i] > 0)
vec_t.push_back(i);
}
int window_begin = 0;//最小窗口的起始指针
string result;
for (int i = 0; i < s.length(); i++) {
map_s[s[i]]++;
//确保begin不能超过i
while (window_begin < i) {
char begin_ch = s[window_begin];
//begin指向的字符在t中没出现过
if (map_t[begin_ch] == 0) {
//向右移动begin指针
window_begin++;
}
//begin指向的字符在t中出现过,但是begin指向的字符出现的次数在窗口中超过t中出现的次数
else if (map_s[begin_ch] > map_t[begin_ch]) {
map_s[begin_ch]--;
window_begin++;
}
else {
break;
}
}
//如果map_s中各个字符都满足map_t中的字符要求
if (is_window_ok(map_s, map_t, vec_t)) {
int new_window_len = i - window_begin + 1;
if (result == "" || result.length() > new_window_len) {
result = s.substr(window_begin, new_window_len);
}
}
}
return result;
}
};