每日一道算法题+面试题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,增、删、查都会先进行一次二分查找。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值