開始刷leetcode了,算法小渣渣只先從簡單地刷起。leetcode到目前爲止共有944道。由於基礎比較薄弱,打算在easy階段,天天至少刷3道題,三個月完成~html
第一道題就是十分經典的兩數之和的題,雖然代碼量不多,可是須要注意的點仍是有不少的。算法
Given an array of integers, return indices of the two numbers such that they add up to a specific target.數組
You may assume that each input would have exactly one solution, and you may not use the same element twice.app
Example:函數
Given nums = [2, 7, 11, 15], target = 9, Because nums[0] + nums[1] = 2 + 7 = 9, return [0, 1].
class Solution { public int[] twoSum(int[] nums, int target) { for (int i = 0; i < nums.length; i++) { //數組的值的長度:array_name.length for (int j = i + 1; j < nums.length; j++) { if (nums[i] + nums[j] == target) { //❗️==和=不要混淆 return new int[] { i, j }; //數組的初始化賦值,其中有一種是int a[] = new int[]{9,7,21}; 不須要定義數組的大小,數組會根據賦值大小分配空間 } } } throw new IllegalArgumentException("No two sum solution"); //若是不寫這行會報錯:missing return statement,緣由下面給出 //是throw,不是return } }
爲何若是不在函數體內拋出throw就會報錯呢,由於雖然if/else裏面已經有了return,但該邏輯語句在程序執行的過程當中不必定會執行到,例如拋出異常等問題的出現,因此必須在try-catch外加一個return確保不管發生什麼狀況都會return.性能
複雜度分析:優化
因爲這種算法是暴力算法,因此花費不少時間。spa
key---hash--->f(key)code
【經驗】在須要輸出數組的下標,或者要對下標進行操做時,用哈希表能夠事半功倍,大幅度提升時間性能。htm
爲了對運行時間複雜度進行優化,咱們須要一種更有效的方法來檢查數組中是否存在目標元素。若是存在,咱們須要找出它的索引。保持數組中的每一個元素與其索引相互對應的最好方法是什麼?哈希表。
經過以空間換取速度的方式,咱們能夠將查找時間從 O(n)下降到 O(1)。哈希表正是爲此目的而構建的,它支持以 近似 恆定的時間進行快速查找。我用「近似」來描述,是由於一旦出現衝突,查找用時可能會退化到 O(n)。但只要你仔細地挑選哈希函數,在哈希表中進行查找的用時應當被攤銷爲 O(1)。
一個簡單的實現使用了兩次迭代。在第一次迭代中,咱們將每一個元素的值和它的索引添加到表中。而後,在第二次迭代中,咱們將檢查每一個元素所對應的目標元素(target - nums[i]target−nums[i])是否存在於表中。注意,該目標元素不能是 nums[i]nums[i] 自己!
public int[] twoSum(int[] nums, int target) { Map<Integer, Integer> map = new HashMap<>(); //初始化哈希表 Map<type,type> name = new HashMap<>(); 此時type不是int,而是Integer for (int i = 0; i < nums.length; i++) { map.put(nums[i], i); //哈希表賦值: name.put(key,f(key)); 由於咱們須要輸出的是index,因此把f(key)當作索引 } for (int i = 0; i < nums.length; i++) { int complement = target - nums[i];
//containsKey判斷Map集合對象中是否包含指定的鍵名。包含就返回true,不然返回false。
//map.get(key): 輸出的值是f(key)
if (map.containsKey(complement) && map.get(complement) != i) { //能找到和是target的數,且不是它自己 return new int[] { i, map.get(complement) }; } } throw new IllegalArgumentException("No two sum solution"); }
複雜度分析:
先說一下哈希表。參考博文
這裏先說一下哈希表的定義:哈希表是一種根據關鍵碼去尋找值的數據映射結構,該結構經過把關鍵碼映射的位置去尋找存放值的地方,提及來可能感受有點複雜,我想我舉個例子你就會明白了,最典型的的例子就是字典,你們估計小學的時候也用過很多新華字典吧,若是我想要獲取「按」字詳細信息,我確定會去根據拼音an去查找 拼音索引(固然也能夠是偏旁索引),咱們首先去查an在字典的位置,查了一下獲得「安」,結果以下。這過程就是鍵碼映射,在公式裏面,就是經過key去查找f(key)。其中,按就是關鍵字(key),f()就是字典索引,也就是哈希函數,查到的頁碼4就是哈希值。
經過字典查詢數據
可是問題又來了,咱們要查的是「按」,而不是「安,可是他們的拼音都是同樣的。也就是經過關鍵字按和關鍵字安能夠映射到同樣的字典頁碼4的位置,這就是哈希衝突(也叫哈希碰撞),在公式上表達就是key1≠key2,但f(key1)=f(key2)。衝突會給查找帶來麻煩,你想一想,你原本查找的是「按」,可是卻找到「安」字,你又得向後翻一兩頁,在計算機裏面也是同樣道理的。
但哈希衝突是無可避免的,爲何這麼說呢,由於你若是要徹底避開這種狀況,你只能每一個字典去新開一個頁,而後每一個字在索引裏面都有對應的頁碼,這就能夠避免衝突。可是會致使空間增大(每一個字都有一頁)。
既然沒法避免,就只能儘可能減小衝突帶來的損失,而一個好的哈希函數須要有如下特色:
1.儘可能使關鍵字對應的記錄均勻分配在哈希表裏面(好比說某廠商賣30棟房子,均勻劃分ABC3個區域,若是你劃分A區域1個房子,B區域1個房子,C區域28個房子,有人來查找C區域的某個房子最壞的狀況就是要找28次)。
2.關鍵字極小的變化能夠引發哈希值極大的變化。
比較好的哈希函數是time33算法。如今幾乎全部流行的HashMap都採用了DJB Hash Function,俗稱「Time33」算法,Times33實現起來非誠簡單,不斷的與33相乘:nHash = nHash*33 + *key++
核心的算法就是以下:
unsigned long hash(const char* key){ unsigned long hash=0; for(int i=0;i<strlen(key);i++){ hash = hash*33+str[i]; } return hash; }
因爲哈希表高效的特性,查找或者插入的狀況在大多數狀況下能夠達到O(1),時間主要花在計算hash上,固然也有最壞的狀況就是hash值全都映射到同一個地址上,這樣哈希表就會退化成鏈表,查找的時間複雜度變成O(n),可是這種狀況比較少,只要不要把hash計算的公式外漏出去而且有人故意攻擊(用興趣的人能夠搜一下基於哈希衝突的拒絕服務攻擊),通常也不會出現這種狀況。
哈希衝突攻擊致使退化成鏈表
事實證實,咱們能夠一次完成。在進行迭代並將元素插入到表中的同時,咱們還會回過頭來檢查表中是否已經存在當前元素所對應的目標元素。若是它存在,那咱們已經找到了對應解,並當即將其返回。
public int[] twoSum(int[] nums, int target) { Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { int complement = target - nums[i]; if (map.containsKey(complement)) { //若是有這麼個數,返回兩個索引 return new int[] { map.get(complement), i }; } map.put(nums[i], i); } throw new IllegalArgumentException("No two sum solution"); }
複雜度分析: