文章只是总结,便于面试和手写算法。细节和详细解释,请看: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,增、删、查都会先进行一次二分查找。