文章只是總結,便於面試和手寫算法。細節和詳細解釋,請看:https://leetcode-cn.com/
1. 題目
算法題:
1. 有效的字母異位詞:給定兩個字符串,判斷是否是字母異位詞(字母打亂)
2. 兩數之和:給定一個數組,和一個target值,返回兩數和爲target的角標
3. 三數之和:給定一個數組,判斷是否存在a+b+c=0的元素
面試題:
1. View的事件分發機制
2. sparseArray
2. 基本知識
2.1 映射(Map)和集合(Set)
2.1.1 Hash函數
使hash均值,均勻分佈在哈希表中的算法,叫hash函數。
哈希碰撞:哈希表中,不同的關鍵字對應同一個存儲位置的現象,叫哈希碰撞。
解決辦法:
- 拉鍊法:hash相同就把Value存儲到一條線性鏈表中。
- 開放定址法:如果算出的hash對應位置已經有值,那麼根據一定規則去找新的存儲地址
- 桶定址法:爲每一個hash值關聯一個足夠大的存儲空間,只要hash值一樣就往裏面存,如果桶滿了,則使用開放定址法。
2.1.2 List vs Map vs Set
- List: 元素有序,可重複的數組或者鏈表
- Map:以key-value形式存儲的數據集
- Set:元素無序,數據不可重複的集合
2.1.3 Map和Set的實現
HashMap vs TreeMap
HashSet vs TreeSet
分別由hash表和二叉樹實現
- HashMap和HashSet:增、刪、查時間複雜度:O(1)(最差是O(n))
- TreeMap和Treeset:增、刪、查時間複雜度:O(logn)(最差是O(n))
- Tree是有序的,所以如果是有序數據結構則選Tree實現的TreeMap和TreeSet
3. 算法題解題
3.1 有效的字母異位詞:給定兩個字符串,判斷是否是字母異位詞(字母打亂)
示例:
輸入 s = “rat”,t = “atr”
輸出:true
解法1 :排序
將兩個字符串中的字母進行排序,排序後進行比較如果排序後的字符數組一樣則,表示是有效的字母異位詞
該解法,時間複雜度O(nlogn),空間複雜度O(1)(時間消耗主要在排序上,比較的O(n)可以忽略)
public boolean isAnagram(String s, String t){
char[] sChars = s.toCharArray();
char[] tChars = t.toCharArray();
Arrays.sort(sChars);
Arrays.sort(tChars);
return Arrays.equals(sChars,tChars);
}
解法2 :HashMap
將字符串分別生成字符數組,然後將字符作爲key,分別進行計數,如果其中一個字符出現的次數不一樣,則認爲是無效的異位詞。
該解法,時間複雜度:O(n),空間複雜度:O(n)
public boolean isAnagram(String s, String t){
char[] sChars = s.toCharArray();
char[] tChars = t.toCharArray();
if (sChars.length != tChars.length){
return false;
}
HashMap<String, Integer> sHashMap = new HashMap<>();
HashMap<String, Integer> tHashMap = new HashMap<>();
for (int i = 0; i< sChars.length;i++){
String s1 = String.valueOf(sChars[i]);
if (sHashMap.containsKey(s1)){
sHashMap.put(s1, sHashMap.get(sChars[i]) +1 );
}
String s2 = String.valueOf(tChars[i]);
if (tHashMap.containsKey(s2)){
tHashMap.put(s2, tHashMap.get(tChars[i]) +1 );
}
}
for (int i = 0; i< sChars.length;i++){
String s1 = String.valueOf(sChars[i]);
if (sHashMap.get(s1) != tHashMap.get(s1)){
return false;
}
}
return true;
}
解法3 :哈希表
將字符串分別生成字符數組,計算兩個字符串中的字符出現次數是否相等,因爲字符串都是小寫字母,自定義一個26大小的int數組,自定義hash函數(當前字符-‘a’)落在0-25中,一個字符數組根據角標進行+1計算,另一個字符數組進行-1計算,遍歷int數組,只要值不爲0,則不是有效的異位詞。
該解法:時間複雜度:O(n),空間複雜度:O(1)(雖然使用的額外的空間,但其空間複雜度是O(1))
public boolean isAnagram(String s, String t) {
char[] sChars = s.toCharArray();
char[] tChars = t.toCharArray();
if (sChars.length != tChars.length) {
return false;
}
int[] count = new int[26];
for (int i = 0; i < sChars.length; i++) {
count[sChars[i] - 'a']++;
count[tChars[i] - 'a']--;
}
for (int c :count) {
if (c != 0) {
return false;
}
}
return true;
}
3.2 兩數之和:給定一個數組,和一個target值,返回兩數和爲target的角標
解法1 :暴力解法
雙層循環,找到 x+y=target的角標,返回即可。
該解法:時間複雜度O(n2),空間複雜度O(1)
public int[] twoSum(int[] nums, int target){
for (int i= 0; i< nums.length; i++){
for (int j = i+1;j<nums.length;j++){
if (nums[i] + nums[j] == target){
return new int[]{i, j};
}
}
}
throw new IllegalArgumentException("no two solution");
}
解法2 :使用Hash表替代其中一層循環
通過以空間換時間的方式,將所有數組內容存到一個hash表中,value作爲key,角標作爲值。循環遍歷,目標值是否在hash表中存在,如果存在直接返回。
該解法:時間複雜度O(n),空間複雜度O(n)
public int[] twoSum2(int[] nums, int target){
HashMap<Integer, Integer> map = new HashMap<>();
for (int i= 0; i< nums.length; i++){
map.put(nums[i], i);
}
for (int i= 0; i< nums.length; i++){
int temp = target - nums[i];
if (map.containsKey(temp) && i != map.get(temp)){
return new int[]{i,map.get(temp)};
}
}
throw new IllegalArgumentException("no two solution");
}
3.3 三數之和:給定一個數組,判斷是否存在a+b+c=0的元素
解法1:暴力解法,3層循環
該解法:時間複雜度O(n3),空間複雜度O(1)
public List<List<Integer>> threeSum(int [] nums){
List<List<Integer>> result = new ArrayList<>();
if (nums.length < 3) return null;
for (int i=0;i < nums.length; i++){
for (int j = i + 1; j < nums.length; j++){
for (int k = j + 1;k < nums.length; k++){
if (nums[i] + nums[j] + nums[k] == 0){
result.add(Arrays.asList(Integer.valueOf(nums[i]),Integer.valueOf(nums[j]),Integer.valueOf(nums[k]));
}
}
}
}
return result;
}
解法2:兩層循環,最後一層循環以空間換時間,改成查找
該解法:時間複雜度O(n2),空間複雜度O(n)
public List<List<Integer>> threeSum(int [] nums){
List<List<Integer>> result = new ArrayList<>();
HashSet<Integer> hashSet = new HashSet<>();
for (int i = 0; i < nums.length; i++){
hashSet.add(Integer.valueOf(nums[i]));
}
if (nums.length < 3) return null;
for (int i=0;i < nums.length; i++){
for (int j = i + 1; j < nums.length; j++){
int temp = nums[i] + nums[j];
if (hashSet.contains(-temp)){
result.add(Arrays.asList(Integer.valueOf(nums[i]), Integer.valueOf(nums[j]), Integer.valueOf(-temp)));
}
}
}
return result;
}
解法3:先排序,然後遍歷,固定第一個參數,左邊從當前數後一個開始,右邊從數組最後一個數開始,當前數和兩頭相加,如果數大於0,則右角標左移,小於0,則左下標右移,直到三數和相加爲0 爲止
該解法:時間複雜度O(n2),空間複雜度O(1)
public List<List<Integer>> threeSum(int [] nums){
List<List<Integer>> result = new ArrayList<>();
int numLen = nums.length;
if (numLen < 3) return null;
Arrays.sort(nums);
for (int i=0;i < numLen - 1; i++){
// 最小數大於0,無解
if (nums[i] > 0) break;
//左邊從當前數後一個開始,右邊從數組最後一個數開始,兩頭相加
int l = i + 1;
int r = numLen - 1;
while (l < r){
int sum = nums[i] + nums[l] + nums[r];
if (sum == 0){
result.add(Arrays.asList(Integer.valueOf(nums[i]), Integer.valueOf(nums[l]), Integer.valueOf(nums[r])));
}else if (sum < 0){
// 數太小,需要左下標右移
l ++;
}else if (sum > 0){
//數太大,需要右下標左移
r --;
}
}
}
return result;
}
4. 面試題解題
4.1 View的事件分發機制
View的事件分發是指對
MotionEvent
的事件的分發過程,即當一個MotionEvent
產生後,系統需要把它傳遞給具體的View,這就是事件分發過程。此處不深究源碼,只總結。
4.1.1 事件分發中的幾個關鍵方法
-
dispatchTouchEvent(MotionEvent ev)
三個方法中最先被調用的,用來進行事件的分發,如果事件能夠傳給當前View/ViewGroup,則該方法一定會被調用。
-
onInterceptTouchEvent(MotionEvent ev)
該方法用來判斷是否攔截某個事件(ViewGroup中的方法,View中沒有)。如果當前View攔截了某個事件,則該事件序列將不會再調用該方法。
-
onTouchEvent(MotionEvent event)
用來處理點擊事件,如果`dispatchTouchEvent`返回false,則該方法不會被調用。
4.1.2 使用僞代碼描述事件分發流程
//代表是否會消耗掉事件
boolean comsume = false;
/**
* 1. 事件產生後會先調用 dispatchTouchEvent
* @param event
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
// 2. 判斷是否攔截事件
if (onInterceptTouchEvent(event)){
// a. 如果攔截,則調用onTouchEvent處理
comsume = onTouchEvent(event);
}else{
// b. 如果不攔截,那麼調用子View的 dispatchTouchEvent 重複上述過程
comsume = child.dispatchTouchEvent(event);
}
//3. 返回是否被消費的標識
return comsume;
}
4.2 SparseArray
Android推出的,爲了更好的性能,使用SparseArray和ArrayMap來替代HashMap
4.2.1 SparseArray特點
- 該數組只適用於key是int的情況,避免了key的自動裝箱
- 內部存儲使用了兩個數組,比HashMap更節省內存
- 由於key值是按從小到大排序好的,所以,在查找、插入時,會先進行二分查找,找到對應key的位置,所以獲取數據較快。
- 數據量大的情況下,性能優勢並不明顯:插入、查找、刪除都需要先進行一次二分查找,所以數據量大的時候,性能會減少至少50%
- 如果key不是int類型,可以適用ArrayMap替代,其內部實現和SparseArray類似,都是兩個數組,一個數組存儲的是key的hash,另一個數組記錄Value,增、刪、查都會先進行一次二分查找。