第七章 哈希表與字符串

哈希表與字符串

哈希表基礎知識

哈希表(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. 基本使用

(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. 遍歷

(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 中的字符,任意組合,生成新的字符串,若生成的字符串爲迴文字符串,需要除了中心字符,其餘字符只要頭部出現,尾部就要對應出現

  1. 利用字符哈希方法,統計字符串中所有的字符數量;
  2. 設置最長迴文串偶數字符長度爲max_length = 0;
  3. 設置是否有中心標記flag = 0;
  4. 遍歷每一個字符,字符數爲count,若count爲偶數,max_length += count;若count爲奇數,max_length+=count-1,flag=1;
  5. 最終最長迴文子串長度: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字符串中字符數量相同
算法思路:

  1. 設置單詞(字符串)到pattern字符的映射(哈希);使用數組used[128]記錄pattern字符是否使用。
  2. 遍歷str,按照空格拆分單詞,同時對應的向前移動指向pattern字符的指針,每拆分出一個單詞,判斷
    如果該單詞從未出現在哈希表中:
    如果當前的pattern字符已被使用,返回false
    將單詞與當前指向pattern字符做映射;標記當前指向pattern字符已使用
    否則:
    如果當前單詞在哈希表中的映射字符不是當前指向的pattern字符,則返回false
  3. 若單詞個數與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]:

  1. 設置臨時變量str = strs[i],對str進行排序。
  2. 若str未出現在anagram中,設置str到一個空字符串向量的映射。
  3. 將strs[i]添加至字符串向量anagram[str]中。
  4. 遍歷哈希表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。
算法思路

  1. 定義result爲當前最長子串的長度
  2. 定義begin指向滑動窗口的起始索引,end指向滑動窗口的結束索引
  3. 定義哈希表記錄當前各字符的位置
  4. 指針向後逐個掃描字符串中的字符,並用哈希表記錄當前字符的位置。
  5. 如果該字符在哈希表中,說明該字符重複遍歷,此時起始索引爲 當前起始索引 與 上一次遍歷該字符的索引 中較大值。
  6. 如果該字符不在哈希表中,則在哈希表中記錄該字符索引。
  7. 整個過程中,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

  1. 定義一個set(不包含重複元素的容器)作爲結果容器result,存放所有出現超過一次且長度爲10的序列
  2. 定義一個set作爲哈希數組hash_map,存放所遍歷過的長度爲10的字母串
  3. 滑動窗口爲長度爲10的字母子串。
  4. 每次向後遍歷一個位置,若此時滑動窗口中的字母串已存在hash_map,則說明出現超過1次,則放入結果數組
  5. 將該滑動窗口字母串加入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 中存在這樣的子串,我們保證它是唯一的答案。

解題思路
滑動窗口算法的思路是這樣:

  1. 我們在字符串 S 中使用雙指針中的左右指針技巧,初始化 left = right = 0,把索引閉區間 [left, right] 稱爲一個「窗口」。
  2. 我們先不斷地增加 right 指針擴大窗口 [left, right],直到窗口中的字符串符合要求(包含了 T 中的所有字符)。
  3. 此時,我們停止增加 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 '#';
			    }
發佈了37 篇原創文章 · 獲贊 7 · 訪問量 5252
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章