文章目錄
哈希表與字符串
哈希表基礎知識
哈希表(Hash table,也叫散列表),是根據關鍵字(key)直接進行訪問的數據結構,它通過把關鍵值映射到表中一個位置(數組下標)來直接訪問,以加快查找關鍵值的速度。這個映射函數叫做哈希(散列)函數,存放記錄的數組叫做哈希(散列)表。
給定表(M),存在函數f(key),對任意的關鍵值key,代入函數後若能得到包含該關鍵字的表中地址,稱表M爲哈希表,函數f(key)爲哈希函數。
- 最簡單的哈希——字符哈希
int main(){
// ASCII碼,從0~127,故使用數組下標做映射,最大範圍至128
int char_map[128] = {0};
string str = "abcdefgaaxxy";
// 統計字符串中,各個字符的數量。若char_map['a']++;即char_map[97]++;
for(int i=0;i<str.length();i++)char_map[str[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;
}
- Hash表的實現
問題引入1:任意元素的映射
解決:利用哈希函數
將關鍵值(大整數、字符串、浮點數等)轉化爲整數再對錶長取餘,從而關鍵字值被轉換爲哈希表的表長範圍內的整數。
問題引入2:不同整數或字符串,由於哈希函數的選擇,映射到同一個下標,發生衝突
解決:拉鍊法解決衝突
將所有哈希函數結果相同的結點連接在同一個單鏈表中。
若選定的哈希表長度爲m,則可將哈希表定義爲一個長度爲m的指針數組t[0…m-1],指針數組中的每個指針指向哈希函數結果相同的單鏈表。
插入value:
將元素value插入哈希表,若元素value的哈希函數值爲hash_key,將value對應的結點以頭插法的方式插入到以t[hash_key]爲頭指針的單鏈表中。
查找value:
若元素value的哈希函數值爲hash_key,遍歷以t[hash_key]爲頭指針的單鏈表,查找鏈表各個結點的值域是否爲value。
public class HashNode{
// Hash表數據結構爲單鏈表構成的數組
int val;
HashNode next;
HashNode(int x){
val = x;
next = null;
}
}
int hash_func(int key,int table_len) {
// 整數哈希函數:直接對錶長取餘。獲得表長範圍內的正整數。
return key % table_len;
}
void insert(HashNode[] hash_table, HashNode node, int table_len) {
// 向哈希表中插入元素,採用頭插法
int hash_key = hash_func(node.val, table_len);
node.next = hash_table[hash_key].next;
hash_table[hash_key].next = node;
}
boolean search(HashNode[] hash_table, int value, int table_len) {
// 從哈希表中查找元素,遍歷哈希值對應的單鏈表
int hash_key = hash_func(value, table_len);
HashNode head = hash_table[hash_key].next;
while(head != null) {
if(head.val == value)return true;
head = head.next;
}
return false;
}
HashMap 基本使用
- 基本使用
(1) 插入鍵值對數據
public V put(K key, V value)
(2)根據鍵值獲取鍵值對值數據
public V get(Object key)
(3)獲取Map中鍵值對的個數
public int size()
(4)判斷Map集合中是否包含鍵爲key的鍵值對
public boolean containsKey(Object key)
(5)判斷Map集合中是否包含值爲value的鍵值對
boolean containsValue(Object value)
(6)判斷Map集合中是否沒有任何鍵值對
public boolean isEmpty()
(7)清空Map集合中所有的鍵值對
public void clear()
(8)根據鍵值刪除Map中鍵值對
public V remove(Object key)
- 遍歷
(1)將Map中所有的鍵裝到Set集合中返回
//public Set keySet();
Set set=map. keySet()
(2)返回集合中所有的value的值的集合
// public Collection values();
Collection c=map.values()
(3)將每個鍵值對封裝到一個個Entry對象中,再把所有Entry的對象封裝到Set集合中返回
// public Set<Map.Entry<K,V>> entrtSet();
Set<Map.Entry<K,V>> entrys=map.entrySet()
HashMap<String,Integer> map =new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
map.put("d", 4);
// 遍歷方法1——將Map中所有的鍵裝到Set集合中返回
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println("map.get(key) is :"+map.get(key));
}
// 遍歷方法2——將每個鍵值對封裝到一個個Entry對象中,再把所有Entry對象封裝到Set集合中返回
Set<Map.Entry<String, Integer>> set=map.entrySet();
Iterator<Map.Entry<String, Integer>> it=set.iterator();
while(it.hasNext()){
//System.out.println(list.get(0) );
Map.Entry<String, Integer> e=it.next();
System.out.println(e.getKey()+":"+e.getValue());
}
leetcoe
例1:最長迴文串(409)
題目描述
給定一個包含大寫字母和小寫字母的字符串,找到通過這些字母構造成的最長的迴文串。
在構造過程中,請注意區分大小寫。比如 “Aa” 不能當做一個迴文字符串。
注意:假設字符串的長度不會超過 1010。
示例 1:
輸入:
"abccccdd"
輸出:
7
解釋:
我們可以構造的最長的迴文串是"dccaccd", 它的長度是 7。
解題思路
使用字符串s 中的字符,任意組合,生成新的字符串,若生成的字符串爲迴文字符串,需要除了中心字符,其餘字符只要頭部出現,尾部就要對應出現
- 利用字符哈希方法,統計字符串中所有的字符數量;
- 設置最長迴文串偶數字符長度爲max_length = 0;
- 設置是否有中心標記flag = 0;
- 遍歷每一個字符,字符數爲count,若count爲偶數,max_length += count;若count爲奇數,max_length+=count-1,flag=1;
- 最終最長迴文子串長度:max_length+flag
程序代碼
// 解法1:(採用HashMap)
public int longestPalindrome(String s) {
// 定義一個哈希表,哈希表的鍵值爲字符串中的字母,值爲對應字母出現的次數。
int length = 0;
boolean isAddOne = false;
// 定義一個HashMap。鍵爲字符串的字母,值爲字母出現的次數
HashMap<String,Integer> m = new HashMap<String,Integer>();
for(int i=0;i<s.length();i++) {
String c = s.charAt(i)+"";
if(m.get(c) == null)m.put(c, 0);
m.put(c, m.get(c)+1);
}
// 遍歷HashMap,最長迴文串中對於每個字母對應的會問串長度分兩種情況討論:
// (1) 若該字母出現次數爲偶數,則可作爲一個迴文字符串
// (2) 若該字母出現次數爲奇數,則如果位於中間則可作爲一個迴文字符串,如果位於兩邊則-1作爲一個迴文字符串
Iterator<String> iterator = m.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
Integer value = m.get(key);
if(value%2==0)length += value;
else {length += value-1;isAddOne = true;}
}
if(isAddOne)length++;
return length;
}
// 解法2:採用自定義HashMap
public int longestPalindrome2(String s) {
// 定義一個長度爲128哈希表(包含大小寫字符),哈希表的鍵值爲字符串中的字母,值爲對應字母出現的次數。
int max_length = 0;
boolean flag = false; // 若存在單數次數字母,則可作爲迴文串中心字符
int[] hash_table = new int[128];
for(int i=0;i<128;i++)hash_table[i] = 0; // 初始化,每個ASCII碼字符出現個數爲0
for(int i=0;i<s.length();i++)hash_table[s.charAt(i)]++; // 記錄字符串中每個字符出現次數
for(int i=0;i<128;i++) {
int value = hash_table[i];
if(value>0) {
// 遍歷每個字符的出現次數,如果出現次數爲偶數,則作爲最長迴文串的一部分直接加入;
// 若出現次數爲奇數,偶數部分作爲兩端,長度爲value-1;奇數部分作爲中心字符,flag = true;
if(value%2 == 0)max_length += value;
if(value%2 == 1) {max_length += value -1;flag = true;}
}
}
if(flag)max_length++;
return max_length;
}
例2:詞語模式(290)
題目描述
給定一種規律 pattern 和一個字符串 str ,判斷 str 是否遵循相同的規律。
這裏的 遵循 指完全匹配,例如, pattern 裏的每個字母和字符串 str 中的每個非空單詞之間存在着雙向連接的對應規律。
示例1:
輸入: pattern = "abba", str = "dog cat cat dog"
輸出: true
示例 2:
輸入:pattern = "abba", str = "dog cat cat fish"
輸出: false
解題思路
匹配:字符串str中的單詞與pattern中地字符一一對應(一一遍歷,一一比較)
結論:
(1)當拆解出一個單詞時,若該單詞已出現,則當前單詞對應的pattern字符必爲該單詞曾經對應地pattern字符。
(2)當拆解出一個單詞時,若該單詞未曾出現,則當前單詞對應的pattern字符也必須未曾出現。
(3)單詞的個數與pattern字符串中字符數量相同
算法思路:
- 設置單詞(字符串)到pattern字符的映射(哈希);使用數組used[128]記錄pattern字符是否使用。
- 遍歷str,按照空格拆分單詞,同時對應的向前移動指向pattern字符的指針,每拆分出一個單詞,判斷
如果該單詞從未出現在哈希表中:
如果當前的pattern字符已被使用,返回false
將單詞與當前指向pattern字符做映射;標記當前指向pattern字符已使用
否則:
如果當前單詞在哈希表中的映射字符不是當前指向的pattern字符,則返回false - 若單詞個數與pattern字符個數不符,則返回false
程序代碼
public boolean wordPattern(String pattern, String str) {
// 匹配:字符串str中的單詞與pattern中地字符一一對應(一一遍歷,一一比較)
// 結論:
//(1)當拆解出一個單詞時,若該單詞已出現,則當前單詞對應的pattern字符必爲該單詞曾經對應地pattern字符。
//(2)當拆解出一個單詞時,若該單詞未曾出現,則當前單詞對應的pattern字符也必須未曾出現。
//(3)單詞的個數與pattern字符串中字符數量相同
// 算法思路:
// 1.設置單詞(字符串)到pattern字符的映射(哈希);使用數組used[128]記錄pattern字符是否使用。
// 2.遍歷str,按照空格拆分單詞,同時對應的向前移動指向pattern字符的指針,每拆分出一個單詞,判斷
// 如果該單詞從未出現在哈希表中:
// 如果當前的pattern字符已被使用,返回false
// 將單詞與當前指向pattern字符做映射;標記當前指向pattern字符已使用
// 否則:
// 如果當前單詞在哈希表中的映射字符不是當前指向的pattern字符,則返回false
// 3.若單詞個數與pattern字符個數不符,則返回false
HashMap<String, Integer> hash_table = new HashMap<String, Integer>(); // 定義一個哈希表,存儲單詞對應的模式字符
boolean[] used = new boolean[128]; // 定義used數組,表示各模式字符是否被使用過,初始化均未使用
for(int i=0; i<128; i++)used[i] = false;
String[] str_list = str.split(" "); // 將字符串拆分爲單詞數組
if(str_list.length != pattern.length())return false; // 情況1:如果模式串長度與單詞數組長度不同,則不匹配
for(int i=0;i<str_list.length;i++) {
String cur_str = str_list[i];
int cur_pattern = pattern.charAt(i);
if(hash_table.containsKey(cur_str)) {
// 若單詞出現在哈希表中,則該單詞對應的模式字符一定爲之前存儲在哈希表中的模式字符
// 情況2:模式字符串中對應字符 與 哈希表存儲模式字符 不同,則不匹配
if(hash_table.get(cur_str) != cur_pattern)return false;
}else {
// 若該單詞從未出現在哈希表中,說明是一個新單詞。該單詞對應的模式爲一個新字符
// 情況3:若未使用過的字符 出現在 used表中(向前遍歷pattern已使用),則不匹配
if(used[cur_pattern])return false;
// 添加該字符的映射到哈希表,並標記使用
hash_table.put(cur_str, cur_pattern);
used[cur_pattern] = true;
}
}
return true;
}
例3:同字符詞語分組(49)
題目描述
給定一個字符串數組,將字母異位詞組合在一起。字母異位詞指字母相同,但排列不同的字符串。
示例:
輸入: ["eat", "tea", "tan", "ate", "nat", "bat"],
輸出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
說明:
所有輸入均爲小寫字母。
不考慮答案輸出的順序。
解題思路
問題:如何建立哈希表,怎樣設計哈希表的key與value,可將各個字符數相同的字符串映射到一起?
設計:哈希表以內部進行排序的各個單詞爲key,以字符串向量(列表)爲value,存儲各個字符asc碼相同的字符串。
算法思路:
設置字符串到字符串向量的哈希表anagram,遍歷字符串向量strs中的單詞strs[i]:
- 設置臨時變量str = strs[i],對str進行排序。
- 若str未出現在anagram中,設置str到一個空字符串向量的映射。
- 將strs[i]添加至字符串向量anagram[str]中。
- 遍歷哈希表anagram,將全部key對應的value push到最終結果中。
程序代碼
public List<List<String>> groupAnagrams(String[] strs) {
// 定義一個哈希表,存儲(按各個字符ASCII排序後的單詞,該單詞在原字符串數組中下標數組)鍵值對
// 遍歷該哈希表,將相同字符存儲的位置對應的單詞進行分組
List<List<String>> result = new ArrayList<>();
HashMap<String,ArrayList> hash_table = new HashMap<String,ArrayList>();
for(int i=0;i<strs.length;i++) {
// 對單詞按ASCII碼排序,可以將字母異位詞分爲一組
String words = sortByASC(strs[i]);
// 對於每個字母異位詞,記錄他們在原字符串數組中的位置
// 對於每個字母異位詞,存儲在哈希表中,鍵爲按ASCII碼排序的單詞,值爲原單詞所構成的列表(字符串向量)
ArrayList position_list = new ArrayList<>();
if(hash_table.containsKey(words)) {
position_list = hash_table.get(words);
position_list.add(strs[i]);
hash_table.replace(words, position_list);
}else {
position_list.add(strs[i]);
hash_table.put(words, position_list);
}
}
// 遍歷哈希表,根據每個字母異位詞的位置數組找到它們對應的單詞並進行分組
// 將分組後的結果存儲
Iterator<String> iterator = hash_table.keySet().iterator();
while (iterator.hasNext()) {
ArrayList result_item = new ArrayList<>();
String key = iterator.next();
ArrayList<Integer> value = hash_table.get(key);
for(int i=0;i<value.size();i++) {
result_item.add(value.get(i));
}
result.add(result_item);
}
// 輸出結果
return result;
}
public String sortByASC(String s) {
// 將String 字符串按ASCII碼順序排序
// 這裏利用哈希表進行排序
int[] hash_table = new int[128];
String result = "";
for(int i=0;i<128;i++)
hash_table[i] = 0;
for(int i=0;i<s.length();i++) {
hash_table[s.charAt(i)]++;
}
for(int i=0;i<128 ;i++){
if(hash_table[i]>0)
for(int j=0;j<hash_table[i];j++){
char char_element = (char)i;
result += char_element;
}
}
return result;
}
例4:無重複字符的最長子串(3)
題目描述
給定一個字符串,請你找出其中不含有重複字符的 最長子串 的長度。
示例 1:
輸入: "abcabcbb"
輸出: 3
解釋: 因爲無重複字符的最長子串是 "abc",所以其長度爲 3。
解題思路
(優化的)滑動窗口Sliding Window——字符串匹配問題
在暴力法中,我們會反覆檢查一個子字符串是否含有有重複的字符,但這是沒有必要的。如果從索引 i到 j - 1之間的子字符串 s{ij}已經被檢查爲沒有重複字符。我們只需要檢查 s[j]對應的字符是否已經存在於子字符串 s{ij}中。
要檢查一個字符是否已經在子字符串中,我們可以檢查整個子字符串,這將產生一個複雜度爲 O(n^2)的算法,但我們可以做得更好。
通過使用 HashSet 作爲滑動窗口,我們可以用 O(1)的時間來完成對字符是否在當前的子字符串中的檢查。
滑動窗口是數組/字符串問題中常用的抽象概念。 窗口通常是在數組/字符串中由開始和結束索引定義的一系列元素的集合,即 [i, j)(左閉,右開)。而滑動窗口是可以將兩個邊界向某一方向“滑動”的窗口。例如,我們將 [i, j) 向右滑動 11 個元素,則它將變爲 [i+1, j+1)(左閉,右開)。
回到我們的問題,我們使用 HashSet 將字符存儲在當前窗口 [i, j)(最初 j = i)中。 然後我們向右側滑動索引 j,如果它不在 HashSet 中,我們會繼續滑動 j。直到 s[j] 已經存在於 HashSet 中。此時,我們找到的沒有重複字符的最長子字符串將會以索引 i開頭。
我們可以做以下優化:我們可以定義字符到索引的映射,而不是使用集合來判斷一個字符是否存在。 當我們找到重複的字符時,我們可以立即跳過該窗口。
也就是說,如果 s[j]s[j] 在 [i, j)範圍內有與 j’重複的字符,我們不需要逐漸增加 i 。 我們可以直接跳過 [i,j’]範圍內的所有元素,並將 i 變爲j ′ +1。
算法思路
- 定義result爲當前最長子串的長度
- 定義begin指向滑動窗口的起始索引,end指向滑動窗口的結束索引
- 定義哈希表記錄當前各字符的位置
- 指針向後逐個掃描字符串中的字符,並用哈希表記錄當前字符的位置。
- 如果該字符在哈希表中,說明該字符重複遍歷,此時起始索引爲 當前起始索引 與 上一次遍歷該字符的索引 中較大值。
- 如果該字符不在哈希表中,則在哈希表中記錄該字符索引。
- 整個過程中,begin 與 end 維護一個窗口,窗口中的子串爲無重複字符的子串。若該子串長度大於當前最長子串長度result,則更新result
程序代碼
public int lengthOfLongestSubstring(String s) {
// 滑動窗口
int result = 0; // result爲當前最長子串的長度
int begin = 0;int end = 0; // begin指向滑動窗口的起始索引,end指向滑動窗口的結束索引
HashMap<Character, Integer> hash_map = new HashMap<Character, Integer>(); // 利用哈希表記錄當前各字符的位置
// 指針向後逐個掃描字符串中的字符,並用哈希表記錄當前字符的位置。
// 如果該字符在哈希表中,說明該字符重複遍歷,此時起始索引爲 當前起始索引 與 上一次遍歷該字符的索引 中較大值。
// 如果該字符不在哈希表中,則在哈希表中記錄該字符索引。
// 整個過程中,begin 與 end 維護一個窗口,窗口中的子串爲無重複字符的子串。若該子串長度大於當前最長子串長度result,則更新result
for(end = 0;end < s.length();end++) {
Character cur_char = s.charAt(end);
if(hash_map.containsKey(cur_char)) {
// 滑動窗口的優化,避免無用的遍歷(包含該重複字符),將起始索引指向該字符的索引+1 與 當前索引 中較大值
begin = Math.max(hash_map.get(cur_char)+1, begin);
}
hash_map.put(cur_char, end); // hash表更新當前字符的位置
result = Math.max(result, end-begin+1); // 比較當前窗口的長度與當前最長子串長度result大小,若大於,則更新result
}
return result;
}
例5:重複的DNA序列(187)
題目描述
所有 DNA 由一系列縮寫爲 A,C,G 和 T 的核苷酸組成,例如:“ACGAATTCCG”。在研究 DNA 時,識別 DNA 中的重複序列有時會對研究非常有幫助。
編寫一個函數來查找 DNA 分子中所有出現超多一次的10個字母長的序列(子串)。
示例:
輸入: s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
輸出: ["AAAAACCCCC", "CCCCCAAAAA"]
解題思路
枚舉DNA字符串中所有長度爲10的子串。將其插入到哈希Map中,並記錄子串數量;遍歷HashMap,將所有出現超過1次的子串存儲到結果中。算法複雜度O(n)
滑動窗口+雙Set
- 定義一個set(不包含重複元素的容器)作爲結果容器result,存放所有出現超過一次且長度爲10的序列
- 定義一個set作爲哈希數組hash_map,存放所遍歷過的長度爲10的字母串
- 滑動窗口爲長度爲10的字母子串。
- 每次向後遍歷一個位置,若此時滑動窗口中的字母串已存在hash_map,則說明出現超過1次,則放入結果數組
- 將該滑動窗口字母串加入hash_map中存儲
程序代碼
public List<String> findRepeatedDnaSequences(String s) {
// 滑動窗口+雙Set
// 定義一個set(不包含重複元素的容器)作爲結果容器result,存放所有出現超過一次且長度爲10的序列
// 定義一個set作爲哈希數組hash_map,存放所遍歷過的長度爲10的字母串
// 滑動窗口爲長度爲10的字母子串。
// 每次向後遍歷一個位置,若此時滑動窗口中的字母串已存在hash_map,則說明出現超過1次,則放入結果數組
// 將該滑動窗口字母串加入hash_map中存儲
Set<String> result = new HashSet<String>(); // 利用Set存放結果序列,防止重複
Set<String> hash_map = new HashSet<String>(); // 利用Set記錄已遍歷的長度爲10的字母序列
int begin = 0;int end = 10; // begin指向滑動窗口的起始索引,end指向滑動窗口的結束索引
for(end = 10;end <= s.length();begin++,end++) {
String cur_s = s.substring(begin, end); // 當前滑動窗口的字母串
if(hash_map.contains(cur_s)) { // 出現超過1次字母序列,加入結果序列
result.add(cur_s);
}
hash_map.add(cur_s); // 將當前字母串加入hash_map,表示遍歷過的字母序列
}
return new ArrayList<String>(result); // 轉換類型
}
例6:最小窗口子串(76)
題目描述
給你一個字符串 S、一個字符串 T,請在字符串 S 裏面找出:包含 T 所有字母的最小子串。
示例:
輸入: S = "ADOBECODEBANC", T = "ABC"
輸出: "BANC"
說明:
如果 S 中不存這樣的子串,則返回空字符串 “”。
如果 S 中存在這樣的子串,我們保證它是唯一的答案。
解題思路
滑動窗口算法的思路是這樣:
- 我們在字符串 S 中使用雙指針中的左右指針技巧,初始化 left = right = 0,把索引閉區間 [left, right] 稱爲一個「窗口」。
- 我們先不斷地增加 right 指針擴大窗口 [left, right],直到窗口中的字符串符合要求(包含了 T 中的所有字符)。
- 此時,我們停止增加 right,轉而不斷增加 left 指針縮小窗口 [left, right],直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同時,每次增加 left,我們都要更新一輪結果。
上述過程可以簡單地寫出如下僞碼框架:
string s, t;
// 在 s 中尋找 t 的「最小覆蓋子串」
int left = 0, right = 0;
string res = s;
while(right < s.size()) {
window.add(s[right]);
right++;
// 如果符合要求,移動 left 縮小窗口
while (window 符合要求) {
// 如果這個窗口的子串更短,則更新 res
res = minLen(res, window);
window.remove(s[left]);
left++;
}
}
return res;
現在就剩下一個比較棘手的問題:如何判斷 window 即子串 s[left…right] 是否符合要求,是否包含 t 的所有字符呢?
可以用兩個哈希表當作計數器解決。用一個哈希表 needs 記錄字符串 t 中包含的字符及出現次數,用另一個哈希表 window 記錄當前「窗口」中包含的字符及出現的次數,如果 window 包含所有 needs 中的鍵,且這些鍵對應的值都大於等於 needs 中的值,那麼就可以知道當前「窗口」符合要求了,可以開始移動 left 指針了。
程序代碼
public String minWindow(String s, String t) {
String result = ""; // 最小子串
Integer length = Integer.MAX_VALUE; // 最小子串長度
Integer left = 0;Integer right = 0; // left指向滑動窗口的起始索引,right指向滑動窗口的結束索引
HashMap<Character,Integer> need = new HashMap<Character,Integer>(); // 定義一個哈希表need,存儲窗口中所需要各個字符的數目
HashMap<Character,Integer> window = new HashMap<Character,Integer>(); // 定義一個哈希表window,存儲窗口中已遍歷各個字符的數目
if(s.length() == 0 || t.length() == 0)return result;
// 初始化need哈希表,存儲滿足題意的字符及字符個數
for(int i=0;i<t.length();i++)
need.put(t.charAt(i), need.containsKey(t.charAt(i))?need.get(t.charAt(i))+1:1);
while(right < s.length()){
// 向後遍歷字符串S,逐漸增加right指針擴大窗口,直到窗口中的字符串符合要求(包含T中所有字符)
Character cur_right_char = s.charAt(right);
right++;
if(t.contains(cur_right_char+"")) { // 更新已遍歷所需要的字母出現次數
window.put(cur_right_char, window.containsKey(cur_right_char)?window.get(cur_right_char)+1:1);
}
// 當window滿足題設條件(包含T中所有字符)
// 此時停止增加right,轉而不斷增加left指針縮小窗口,知道窗口中的字符串不再符合要求(不包含T中所有字符)
if(isSatisfyNeed(need,window)) {
while(isSatisfyNeed(need,window)) {
Character cur_left_char = s.charAt(left);
left++;
if(t.contains(cur_left_char+"")) {// 更新已遍歷所需要的字母出現次數
if(window.containsKey(cur_left_char) && window.get(cur_left_char)==1)window.remove(cur_left_char);
else if(window.containsKey(cur_left_char))window.put(cur_left_char, window.get(cur_left_char)-1);
}
}
// 不符合要求時,則left-1是當前符合要求的最短字符串,將該字符串與當前記錄結果比較
// 若比上一次結果長度短,則更新結果
if(left>0)left--;
Integer cur_length = right-left+1;
if(cur_length < length) {
length = cur_length;
result = s.substring(left,right);
}
left++;
}}
return result;
}
public boolean isSatisfyNeed(HashMap<Character,Integer> need,HashMap<Character,Integer> window) {
// 若滑動窗口的大小與所需字符大小相同,且所含字符數>=所需字符數,則包含所需字符。滿足條件
if(need.size()!=window.size())return false;
Iterator<Character> iterator = need.keySet().iterator();
while (iterator.hasNext()) {
Character key = iterator.next();
Integer value = need.get(key);
if(value > window.get(key))return false;
}
return true;
}
劍指offer
例1:替換空格(2)
題目描述
請實現一個函數,將一個字符串中的每個空格替換成“%20”。例如,當字符串爲We Are Happy.則經過替換之後的字符串爲We%20Are%20Happy。
算法思路
遍歷字符串,如果遇到空格,則替換爲%20
程序代碼
public String replaceSpace(StringBuffer str) {
if(str == null)return null;
// 遍歷字符串,如果遇到空格,則替換爲%20
for(int i=0;i<str.length();i++) {
if(str.charAt(i) == ' ')
str.replace(i, i+1, "%20");
}
return str.toString();
}
// 補充:StringBuffer/StringBuilder 擴展方法
// append(String s): 將指定字符串追加到此字符序列
// reverse(): 將此字符換列用反轉形式取代
// delete(int start,int end): 刪除此序列的子字符串中的字符
// insert(int offset, int i): 將 int 參數的字符串表示形式插入此序列中。
// replace(int start, int end, String str): 使用給定 String 中的字符替換此序列的子字符串中的字符。
// String長度不可變,StringBuffer/StringBuilder 是長度可變的,其中StringBuffer線程安全,StringBuilder線程不安全
例2:數組中出現次數超過一半的數字(28)
題目描述
數組中有一個數字出現的次數超過數組長度的一半,請找出這個數字。例如輸入一個長度爲9的數組{1,2,3,2,2,2,5,4,2}。由於數字2在數組中出現了5次,超過數組長度的一半,因此輸出2。如果不存在則輸出0。
程序代碼
// 28. 數組中出現次數超過一半的數字
// 數組中有一個數字出現的次數超過數組長度的一半,請找出這個數字。
// 例如輸入一個長度爲9的數組{1,2,3,2,2,2,5,4,2}。由於數字2在數組中出現了5次,超過數組長度的一半,因此輸出2。如果不存在則輸出0。
public int MoreThanHalfNum_Solution(int [] array) {
// 定義HashMap 存儲每個數字在數組中出現的次數
// 每次遍歷一個數字都能獲得他此時出現的次數,若次數超過數組長度的一半,則返回結果
if(array == null || array.length == 0)return 0; // 數據爲空,不存在
if(array.length == 1)return array[0]; // 只有一個數據時返回該數據
Map<Integer,Integer> countHash = new HashMap<Integer,Integer>();
for(int i=0;i<array.length;i++) {
int key = array[i];
if(countHash.containsKey(key)) {
int value = countHash.get(key) + 1;
if(value > array.length/2)return key;
countHash.replace(key, value);
}
else countHash.put(key, 1);
}
return 0;
}
例3:最小的K個數(29)
題目描述
輸入n個整數,找出其中最小的K個數。例如輸入4,5,1,6,2,7,3,8這8個數字,則最小的4個數字是1,2,3,4,。
程序代碼
// 29.最小的K個數
// 輸入n個整數,找出其中最小的K個數。例如輸入4,5,1,6,2,7,3,8這8個數字,則最小的4個數字是1,2,3,4,。
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
// Arrays.sort(int[]) 使用雙軸快速排序算法,時間複雜度爲0(logn)
// Collections.sort(List) 是一種優化過的合併排序算法,時間複雜度是O(n)
ArrayList<Integer> result = new ArrayList<Integer>();
if(k==0 || k > input.length)return result;
Arrays.sort(input);
for(int i=0;i<k;i++)result.add(input[i]);
return result;
}
例4:第一個只出現一次的字符(34)
題目描述
在一個字符串(0<=字符串長度<=10000,全部由字母組成)中找到第一個只出現一次的字符,並返回它的位置, 如果沒有則返回 -1(需要區分大小寫).
程序代碼
// 34.第一個只出現一次的字符
// 在一個字符串(0<=字符串長度<=10000,全部由字母組成)中找到第一個只出現一次的字符,並返回它的位置,
// 如果沒有則返回 -1(需要區分大小寫).
public int FirstNotRepeatingChar(String str) {
// 定義一個HashMap 存儲每個字符出現的次數
// 第一次遍歷記錄字符的出現次數
// 第二次遍歷,若字符出現次數爲1則返回該字符的位置
Map<Character,Integer> charCount = new HashMap<Character,Integer>(); // 存儲每個字符出現次數
for(int i=0;i<str.length();i++) {
Character key = str.charAt(i);
if(charCount.containsKey(key))
charCount.replace(key, charCount.get(key)+1);
else charCount.put(key, 1);
}
for(int i=0;i<str.length();i++) {
Character key = str.charAt(i);
if(charCount.containsKey(key) && charCount.get(key) == 1)
return i;
}
return -1;
}
例5:數組中只出現一次的數字(39)
題目描述
一個整型數組裏除了兩個數字之外,其他的數字都出現了兩次。請寫程序找出這兩個只出現一次的數字。
程序代碼
// 39. 數組中出現一次的數字
// 一個整型數組裏除了兩個數字之外,其他的數字都出現了兩次。請寫程序找出這兩個只出現一次的數字。
// num1,num2分別爲長度爲1的數組。傳出參數
// 將num1[0],num2[0]設置爲返回結果
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
// 1. 定義一個HashSet,存儲各個數字
// 2. 若重複訪問,則刪除該數字
// 3. HashSet中存儲的即爲只出現一次的數字
HashSet<Integer> countSet = new HashSet<Integer>();
for(int i=0;i<array.length;i++) {
int key = array[i];
if(countSet.contains(key)) countSet.remove(key);
else countSet.add(key);
}
Iterator<Integer> iterator = countSet.iterator();
num1[0] = iterator.next();
num2[0] = iterator.next();
}
例6:左旋轉字符串(42)
題目描述
彙編語言中有一種移位指令叫做循環左移(ROL),現在有個簡單的任務,就是用字符串模擬這個指令的運算結果。對於一個給定的字符序列S,請你把其循環左移K位後的序列輸出。例如,字符序列S=”abcXYZdef”,要求輸出循環左移3位後的結果,即“XYZdefabc”。是不是很簡單?OK,搞定它!
程序代碼
public String LeftRotateString(String str,int n) {
if(n>str.length())return "";
String leftStr = str.substring(0,n);
String rightStr = str.substring(n,str.length());
return rightStr + leftStr;
}
例7:反轉單詞順序序列(43)
題目描述
牛客最近來了一個新員工Fish,每天早晨總是會拿着一本英文雜誌,寫些句子在本子上。同事Cat對Fish寫的內容頗感興趣,有一天他向Fish借來翻看,但卻讀不懂它的意思。例如,“student. a am I”。後來才意識到,這傢伙原來把句子單詞的順序翻轉了,正確的句子應該是“I am a student.”。Cat對一一的翻轉這些單詞順序可不在行,你能幫助他麼?
程序代碼
public String ReverseSentence(String str) {
// 1. 獲取字符串中各個單詞
// 2. 將各個單詞序列倒轉
// 3. 將順序倒轉後的單詞進行拼接
String result = "";
String[] strList = str.split(" "); // 獲取字符串中各個單詞
if(strList == null || strList.length == 0)return str;
for(int i=0;i<strList.length/2;i++) {
String temp = strList[i];
strList[i] = strList[strList.length-1-i];
strList[strList.length-1-i] = temp;
}
for(int i=0;i<strList.length;i++) {
if(i == strList.length-1)result += strList[i];
else result += strList[i]+" ";
}
return result;
}
例8:把字符串轉換成整數(48)
題目描述
將一個字符串轉換成一個整數(實現Integer.valueOf(string)的功能,但是string不符合數字要求時返回0),要求不能使用字符串轉換整數的庫函數。 數值爲0或者字符串不是一個合法的數值則返回0。
輸入描述:
輸入一個字符串,包括數字字母符號,可以爲空
輸出描述:
如果是合法的數值表達則返回該數字,否則返回0
程序代碼
// 48. 把字符串轉換成整數
// 將一個字符串轉換成一個整數(實現Integer.valueOf(string)的功能,但是string不符合數字要求時返回0),要求不能使用字符串轉換整數的庫函數。 數值爲0或者字符串不是一個合法的數值則返回0。
// 輸入描述:
// 輸入一個字符串,包括數字字母符號,可以爲空
// 輸出描述:
// 如果是合法的數值表達則返回該數字,否則返回0
public int StrToInt(String str) {
// 字符串逐個遍歷
// 若該字符位於'0'~'9'之間,則轉換爲數字,否則返回0
// 第一個字符單獨處理
int result = 0;
boolean isPostive = true;
if(str!=null && str.length()>0) {
Character firstChar = str.charAt(0);
if(convertCharToInt(firstChar)==-1) {
if(firstChar == '+' || firstChar == '-') {
if(firstChar == '-')isPostive = false;
}
else return 0;
}else result = convertCharToInt(firstChar);
for(int i=1;i<str.length();i++) {
if(convertCharToInt(str.charAt(i))==-1) return 0;
else result = result*10 + convertCharToInt(str.charAt(i));
}
}
return isPostive?result:0-result;
}
public int convertCharToInt(Character c) {
if(c>='0' && c<='9')return c-'0';
else return -1;
}
例9:數組中重複的數字(49)
題目描述
在一個長度爲n的數組裏的所有數字都在0到n-1的範圍內。 數組中某些數字是重複的,但不知道有幾個數字是重複的。也不知道每個數字重複幾次。請找出數組中任意一個重複的數字。 例如,如果輸入長度爲7的數組{2,3,1,0,2,5,3},那麼對應的輸出是第一個重複的數字2。
程序代碼
// 49.數組中重複的數字
// 在一個長度爲n的數組裏的所有數字都在0到n-1的範圍內。 數組中某些數字是重複的,但不知道有幾個數字是重複的。也不知道每個數字重複幾次。
// 請找出數組中任意一個重複的數字。 例如,如果輸入長度爲7的數組{2,3,1,0,2,5,3},那麼對應的輸出是第一個重複的數字2。
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
// Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
// 這裏要特別注意~返回任意重複的一個,賦值duplication[0]
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
public boolean duplicate(int numbers[],int length,int [] duplication) {
// 利用HashSet作爲字符字典存儲字符
// 每遍歷一個字符查看其是否位於字典中,若位於則放入duplication數組
// 若第一次訪問,則放入字典中
boolean isDuplication = false;
HashSet<Integer> dictionary = new HashSet<Integer>();
int j = 0; // 重複
for(int i=0;i<length;i++) {
if(dictionary.contains(numbers[i])) {
duplication[j] = numbers[i];
isDuplication = true;
}
else dictionary.add(numbers[i]);
}
return isDuplication;
}
例10:字符流中第一個不重複的字符(53)
題目描述
請實現一個函數用來找出字符流中第一個只出現一次的字符。例如,當從字符流中只讀出前兩個字符"go"時,第一個只出現一次的字符是"g"。當從該字符流中讀出前六個字符“google"時,第一個只出現一次的字符是"l"。
輸出描述:
如果當前字符流沒有存在出現一次的字符,返回#字符。
程序代碼
// 53.字符流中第一個不重複的字符
// 請實現一個函數用來找出字符流中第一個只出現一次的字符。
// 例如,當從字符流中只讀出前兩個字符"go"時,第一個只出現一次的字符是"g"。
// 當從該字符流中讀出前六個字符“google"時,第一個只出現一次的字符是"l"。
// 輸出描述:
// 如果當前字符流沒有存在出現一次的字符,返回#字符。
Character[] visited = new Character[128]; // 存儲訪問過的元素,數組下標爲對應字符ASCII
Character[] dup = new Character[128]; // 存儲重複的元素
List<Character> datas = new ArrayList<Character>(); // 存儲數據
//Insert one char from stringstream
public void Insert(char ch)
{
if(visited[ch] != null)dup[ch] = ch;
else visited[ch] = ch;
datas.add(ch);
}
//return the first appearence once char in current stringstream
public char FirstAppearingOnce()
{
for(int i=0;i<datas.size();i++)
if(dup[datas.get(i)] == null)return datas.get(i);
return '#';
}