題目地址:https://leetcode-cn.com/problems/first-unique-character-in-a-string/java
給定一個字符串,找到它的第一個不重複的字符,並返回它的索引。若是不存在,則返回 -1。數組
示例:數據結構
s = "leetcode" 返回 0 s = "loveleetcode" 返回 2
提示:你能夠假定該字符串只包含小寫字母。測試
雙指針比較,每指定一個值都要全文掃描有無重複優化
public int firstUniqChar(String s) { char[] arr = s.toCharArray(); int n = arr.length; for(int i = 0; i < n; i++){ boolean flag = true; //遍歷每一個值與當前值比較,有重複改false for(int j = 0; j < n; j++){ if(i != j && arr[i] == arr[j]){ flag = false; } } //遍歷完沒有和當前值相同的 if(flag){ return i; } } return -1; }
而後LeetCode的測試用例字符串也是真的長(只截取了部分下面還能夠翻頁),因此在n^2的狀況下超時。指針
上面的解法是有可優化的點的。咱們去查找第一個只出現一次的,那麼一個值找到相同的後咱們就沒必要要再日後了遍歷由於不須要看它有幾個相同的,它不知足就應該看下一個值也就是應該加上break。code
for(int j = 0; j < n; j++){ if(i != j && arr[i] == arr[j]){ flag = false; break; } }
這一點是影響仍是比較大的,第二點就是咱們去判斷遍歷完了沒有重複也就是找到了能夠直接返回的處理咱們除了定義flag在循環體標記,到循環去判斷處理。其實咱們去表達循環完後的處理也能夠在循環體裏面,也就是循環到最後了仍然不知足相等。索引
for(int j = 0; j < n; j++){ if(i != j && arr[i] == arr[j]){ break; }else if(j == n-1){ return i; } }
這樣和前面的就是同一個意思,而且少定義一個變量flag,而且最後咱們的代碼不會超時leetcode
public int firstUniqChar(String s) { char[] arr = s.toCharArray(); int n = arr.length; for(int i = 0; i < n; i++){ for(int j = 0; j < n; j++){ if(i != j && arr[i] == arr[j]){ break; }else if(j == n - 1){ return i; } } } return -1; }
時間O(n^2),空間O(1)字符串
那麼使用hash表存信息,那麼就不用重複遍歷了
public int firstUniqChar(String s) { char[] arr = s.toCharArray(); int n = arr.length; HashMap<Character, Integer> count = new HashMap<Character, Integer>(); //遍歷存下全部信息 for (int i = 0; i < n; i++) { char c = arr[i]; count.put(c, count.getOrDefault(c, 0) + 1); } //遍歷查看key值爲1的 for (int i = 0; i < n; i++) { if (count.get(arr[i]) == 1) return i; } return -1; }
時間O(n),空間O(n),雖然如此但效率反而比雙指針要差,這主要是它的這些方法。
用Hash表能存,那用數組也應該是能夠的,同樣的key位索引值判斷是否是1。同一個字母就是同一個地方對應值就加一。統計完以後遍歷字符串按字符串的順序去數組查率先等於1的就返回
public int firstUniqChar(String s) { int[] chars = new int[26]; char[] arr = s.toCharArray(); for (char c : arr) { chars[c - 'a'] += 1; } for (int i = 0; i < arr.length; ++i) { if (chars[arr[i] - 'a'] == 1) { return i; } } return -1; }
和解法二一樣的一個思路選取數組這種數據結構,效率就直接上去了。
上述數組解法在效率上仍然是有可優化點,由於咱們去比較兩個容器的時候誰短咱們就遍歷誰。更況且這裏只須要拿值到另外一個容器參考只須要一次遍歷,那咱們更應該遍歷短的。那麼當字符串長度小於26和上面同樣遍歷字符串到數組去記錄,最後再遍歷數組看結果,若是字符串長於26那麼咱們就遍歷a-z這26個字母
int result = -1; for (char i = 'a'; i <= 'z'; ++i) { int begin = s.indexOf(i); int end = s.lastIndexOf(i) // 在字符串中存在該字符而且惟一 if (begin != -1 && begin == end { // 不只要惟一,且索引還要小。遍歷完成拿到字符串最前的惟一 result = (result == -1 || result > begin) ? begin : result; } }
那麼在字符串長度很大的狀況下也只須要完整遍歷26次就能找到首個惟一,完整代碼以下:
public int firstUniqChar(String s) { // 字符串長度不超過26 if (s.length() <= 26) { int[] chars = new int[26]; char[] arr = s.toCharArray(); for (char c : arr) { chars[c - 'a'] += 1; } for (int i = 0; i < arr.length; ++i) { if (chars[arr[i] - 'a'] == 1) { return i; } } } //只遍歷26個字母 int result = -1; for (char i = 'a'; i <= 'z'; ++i) { int begin = s.indexOf(i); int end = s.lastIndexOf(i); if (begin != -1 && begin == end) { result = (result == -1 || result > begin) ? begin : result; } } return result; }
題目難度呢屬於簡單,雙指針、hash表這樣成對的解法就出來了,主要是經過此題去回顧一些注意點好比雙循環的優化,循環中字符串的方法頻繁的進出也是有必定的浪費,能夠先拿數組出來操做會好一點。適合體會解題迭代的一個流程。