每日一道算法題+面試題day 5-6

文章只是總結,便於面試和手寫算法。細節和詳細解釋,請看: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函數。

哈希碰撞:哈希表中,不同的關鍵字對應同一個存儲位置的現象,叫哈希碰撞。

解決辦法:

  1. 拉鍊法:hash相同就把Value存儲到一條線性鏈表中。
  2. 開放定址法:如果算出的hash對應位置已經有值,那麼根據一定規則去找新的存儲地址
  3. 桶定址法:爲每一個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 事件分發中的幾個關鍵方法

  1. dispatchTouchEvent(MotionEvent ev)

     三個方法中最先被調用的,用來進行事件的分發,如果事件能夠傳給當前View/ViewGroup,則該方法一定會被調用。
    
  2. onInterceptTouchEvent(MotionEvent ev)

     該方法用來判斷是否攔截某個事件(ViewGroup中的方法,View中沒有)。如果當前View攔截了某個事件,則該事件序列將不會再調用該方法。
    
  3. 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特點

  1. 該數組只適用於key是int的情況,避免了key的自動裝箱
  2. 內部存儲使用了兩個數組,比HashMap更節省內存
  3. 由於key值是按從小到大排序好的,所以,在查找、插入時,會先進行二分查找,找到對應key的位置,所以獲取數據較快。
  4. 數據量大的情況下,性能優勢並不明顯:插入、查找、刪除都需要先進行一次二分查找,所以數據量大的時候,性能會減少至少50%
  5. 如果key不是int類型,可以適用ArrayMap替代,其內部實現和SparseArray類似,都是兩個數組,一個數組存儲的是key的hash,另一個數組記錄Value,增、刪、查都會先進行一次二分查找。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章