iOS面试题收纳-算法

87 阅读10分钟

获取两个View最近的共同父View和所有的共同父View

// 获取一个view的所有父view,将他们放到set里面
- (NSSet*)parentViewsForView:(UIView*)view {
    NSMutableSet *parentViewsSet = [NSMutableSet set];
    UIView *parentView = view;
    while (parentView != nil) {
        [parentViewsSet addObject:parentView];
        parentView = parentView.superview;
    }
    return [NSSet setWithSet:parentViewsSet];
}

// 最近的共同父View
- (UIView*)nearestParentViewForView1:(UIView*)view1 view2:(UIView*)view2 {
    NSSet *view1ParentsSet = [self parentViewsForView:view1];
    
    UIView *parentView = view2;
    while (parentView != nil) {
        if ([view1ParentsSet containsObject:parentView]) {
            return parentView;
        }
        else {
            parentView = parentView.superview;
        }
    }
    
    return nil;
}
// 所有的共同父View
- (NSSet*)commonParentsViewForView1:(UIView*)view1 view2:(UIView*)view2 {
    NSMutableSet *view1ParentsSet = [NSMutableSet setWithSet:[self parentViewsForView:view1]];
    [view1ParentsSet intersectSet:[self parentViewsForView:view2]];
    return [NSSet setWithSet:view1ParentsSet];
}

合并两个已排好序的数组

// 双指针
- (NSArray*)mergeSortedArray1:(NSMutableArray*)sortedArray1
             sortedArray2:(NSMutableArray*)sortedArray2 {
    int i = 0;
    int j = 0;
    NSMutableArray *mergedArray = [NSMutableArray arrayWithCapacity:sortedArray1.count+sortedArray2.count];
    while (i < sortedArray1.count && j < sortedArray2.count) {
        if ([sortedArray1[i] integerValue] < [sortedArray2[j] integerValue]) {
            mergedArray[i+j] = sortedArray1[i];
            i += 1;
        }
        else {
            mergedArray[i+j] = sortedArray2[j];
            j += 1;
        }
    }
    
    // 这里其实可以修改成 if语句,添加数组的剩余数据
    while (i < sortedArray1.count) {
        mergedArray[i+j]= sortedArray1[i];
        i += 1;
    }
    
    while (j < sortedArray2.count) {
        mergedArray[i+j]= sortedArray2[j];
        j += 1;
    }
    
    return [NSArray arrayWithArray:mergedArray];
}

已知单向链表,其中每个节点有一个int类型的data字段(0~9)和一个next指针,按照如下的方式连接3->5->2表示352,请写出两个链表做加法

// 定义链表节点
@interface Node : NSObject
@property (nonatomic, assign) int data;
@property (nonatomic, strong) Node *next;
@end
@implementation Node
- (NSString *)description {
    NSMutableString *str = [NSMutableString string];
    Node *node = self;
    while (node) {
        [str appendFormat:@"%d",node.data];
        node = node.next;
    }
    return [str copy];
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    // 构建链表1
    Node *node1 = [[Node alloc] init];
    node1.data = 3;
    
    Node *tmp = node1;
    NSArray *nums = @[@(7),@(8),@(2)];
    for (NSNumber *num in nums) {
        Node *nextNode = [[Node alloc] init];
        nextNode.data = [num intValue];
        tmp.next = nextNode;
        
        tmp = nextNode;
    }
    // 构建链表2
    Node *node2 = [[Node alloc] init];
    node2.data = 0;
    
    tmp = node2;
    nums = @[@(6),@(1),@(5),@(8),@(0)];
    for (NSNumber *num in nums) {
        Node *nextNode = [[Node alloc] init];
        nextNode.data = [num intValue];
        tmp.next = nextNode;
        
        tmp = nextNode;
    }
    // 调用链表加法算法
    [self nodeAdd:node1 node2:node2];
}

- (Node*)nodeAdd:(Node*)node1 node2:(Node*)node2 {
    // 3->5->1 要算加法,肯定要倒序链表
    NSMutableArray *rNode1 = [NSMutableArray arrayWithCapacity:20];
    Node *tmp = node1;
    // 反转node1
    while (tmp != nil) {
        [rNode1 addObject:tmp];
        tmp = tmp.next;
    }
    // 反转node2
    NSMutableArray *rNode2 = [NSMutableArray arrayWithCapacity:20];
    tmp = node2;
    while (tmp != nil) {
        [rNode2 addObject:tmp];
        tmp = tmp.next;
    }
    
    NSEnumerator *rNode1E = [rNode1 reverseObjectEnumerator];
    NSEnumerator *rNode2E = [rNode2 reverseObjectEnumerator];
    // 进行计算
    NSMutableArray *calculateNodes = [NSMutableArray arrayWithCapacity:MAX(rNode1.count, rNode2.count)];
    Node *calculate1 = rNode1E.nextObject;
    Node *calculate2 = rNode2E.nextObject;
    
    // 进位标识
    int flag = 0;
    while (calculate1 != nil && calculate2 != nil) {
        int num = calculate1.data + calculate2.data + flag;
        if (num >= 10) {
            flag = 1;
            num -= 10;
        }
        else {
            flag = 0;
        }
        
        Node *tmp = [[Node alloc] init];
        tmp.data = num;
        [calculateNodes addObject:tmp];
        
        calculate1 = rNode1E.nextObject;
        calculate2 = rNode2E.nextObject;
    }
    
    while (calculate1 != nil) {
        int num = calculate1.data + flag;
        if (num >= 10) {
            flag = 1;
            num -= 10;
        }
        else {
            flag = 0;
        }
        
        Node *tmp = [[Node alloc] init];
        tmp.data = num;
        [calculateNodes addObject:tmp];
        
        calculate1 = rNode1E.nextObject;
    }
    
    while (calculate2 != nil) {
        int num = calculate2.data + flag;
        if (num >= 10) {
            flag = 1;
            num -= 10;
        }
        else {
            flag = 0;
        }
        
        Node *tmp = [[Node alloc] init];
        tmp.data = num;
        [calculateNodes addObject:tmp];
        
        calculate2 = rNode2E.nextObject;
    }
    
    //反转所得结果
    NSEnumerator *node3E = [calculateNodes reverseObjectEnumerator];
    tmp = node3E.nextObject;
    Node *node3 = nil;
    
    while (tmp != nil) {
        if (node3 == nil && tmp.data > 0) {
            node3 = tmp;
        }
        Node *m = node3E.nextObject;
        tmp.next = m;
        tmp = m;
    }
#if DEBUG
    NSLog(@"\n%@\n+\n%@\n=\n%@",node1,node2,node3);
#endif
    return node3;
}

写一个算法获取十进制数进行二进制表示后1的个数

- (NSUInteger)countOfBinary1In:(NSInteger)num {
    //(最优)采用位运算(x&(x-1),每次将给定的数的最右边的1变位0)
    // 采用给定的数与给定的数-1逻辑与操作,把给定的数的最后边的1变成0,操作一次,count自增一次,知道给定的数的1全部变为0,退出循环
    NSUInteger count = 0;
    
    NSInteger mNum = num;
    while (mNum != 0) {
        mNum = mNum & (mNum - 1);
        count++;
    }
    return count;
}

寻找一个字符串中不包含重复字符的最长子串的长度

// 滑动窗口方法
- (void)maxNoRepeatStrLengthFor:(NSString*)str {
    NSMutableSet * set = [[NSMutableSet alloc] init];
    int ans = 0;// 两个临近的相同字符的间距
    int i = 0; //前序走的index
    int j = 0; // 后序走的index
    
    NSUInteger length = str.length;
    while (j < length) {
        NSString *subStrAtJ = [str substringWithRange:NSMakeRange(j,1)];
        if (![set containsObject:subStrAtJ]) {
            [set addObject:subStrAtJ];
            j += 1;
            
            // 判断两个相同字符的差距是不是和之前的更大
            if (j - i > ans) {
                ans = j - i;
            }
        }
        else {
            [set removeObject:[str substringWithRange:NSMakeRange(i, 1)]];
            i += 1;
        }
        
        NSLog(@"111111111=== i %d j %d",i,j);
    }
    NSLog(@"%d", ans);
}

// 优化的滑动窗口
- (void)maxNoRepeatStrLengthFor1:(NSString*)str {
    NSUInteger length = str.length;
    // str : index
    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:length];
    
    int ans = 0;// 两个临近的相同字符的间距
    int i = 0; //前序走的index
    int j = 0; // 后序走的index
    
    while (j < length) {
        NSString *subStrAtJ = [str substringWithRange:NSMakeRange(j,1)];
        if ([dict.allKeys containsObject:subStrAtJ]) {
            //应该保证滑动窗口的起始位置依次向前,不能倒退
            i = MAX(i, [dict[subStrAtJ] intValue] + 1);//max的作用 :考虑字符串abba这个例子
        }
        NSLog(@"2222222=== i %d j %d",i,j);
        
        dict[subStrAtJ] = @(j);
        j += 1;
        ans = MAX(ans, j - i);
    }
    NSLog(@"%d", ans);
}

交换两个NSInteger的值你能想到几种方法

// 交换A和B的值
// 1.使用中间变量
- (void)swap0a:(NSInteger)a b:(NSInteger)b {
    NSInteger temp = a;
    a = b;
    b = temp;
    NSLog(@"%ld %ld",a,b);
}

// 2.加法
- (void)swap1a:(NSInteger)a b:(NSInteger)b {
    a = a + b;
    b = a - b;
    a = a - b;
    NSLog(@"%ld %ld",a,b);
}

// 3.异或(相同为0,不同为1. 可以理解为不进位加法)
- (void)swap2a:(NSInteger)a b:(NSInteger)b {
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    NSLog(@"%ld %ld",a,b);
}

求两个NSInteger的最大公约数,这里假定a>b

/** 1.直接遍历法 */
- (NSInteger)maxCommonDivisorFor0A:(NSInteger)a b:(NSInteger)b {
    NSInteger max = 0;
    for (NSInteger i = 1; i <= b; i++) {
        if (a % i == 0 && b % i == 0) {
            max = i;
        }
    }
    NSLog(@"%ld",max);
    return max;
}

/** 2.辗转相除法 其中a为大数,b为小数 */
- (NSInteger)maxCommonDivisorFor1A:(NSInteger)a b:(NSInteger)b {
    NSInteger r = a % b;
    while(r > 0) {
        a = b;
        b = r;
        r = a % b;
    }
    NSLog(@"%ld",b);
    return b;
}

判断一个数是否为质数(只能被1和自身整除的叫质数)

- (BOOL)isPrime:(NSInteger)n {
    for(int i = 2; i <= sqrt(n); i++) {
        if(n % i == 0) {
            return NO;
        }
    }
    return YES;
}

给定一个字符串,输出本字符串中只出现一次并且最靠前的那个字符的位置

- (NSInteger)indexForFirstDisguistCharIn:(NSString*)str {
    if (str == nil || str.length == 0) {
        return -1;
    }
    if (str.length == 1) {
        return 0;
    }
    // str : str's count 存储字符->字符出现的数量
    NSMutableDictionary *charCountDict = [NSMutableDictionary dictionaryWithCapacity:str.length];
    // 只出现1次的字符
    NSMutableSet *difficultSet = [NSMutableSet setWithCapacity:str.length];
    
    for (int i = 0; i < str.length; i++) {
        NSString *subStr = [str substringWithRange:NSMakeRange(i, 1)];
        if ([charCountDict.allKeys containsObject:subStr]) {
            int count = [charCountDict[subStr] intValue];
            charCountDict[subStr] = @(count + 1);
        }
        else {
            charCountDict[subStr] = @(1);
        }
        
        if ([charCountDict[subStr] intValue] == 1) {
            if (![difficultSet containsObject:subStr]) {
                [difficultSet addObject:subStr];
            }
        }
        else {
            if ([difficultSet containsObject:subStr]) {
                [difficultSet removeObject:subStr];
            }
        }
    }
    
    for (int i = 0; i < str.length; i++) {
        NSString *subStr = [str substringWithRange:NSMakeRange(i, 1)];
        if ([difficultSet containsObject:subStr]) {
            return i;
        }
    }
    return -1;
}

返回正整数N的二进制周期,如果没有周期就返回-1

func getBinaryPeriodForInt(_ n: Int) -> Int {
    var nn = n
    var d = [Int]()
    var l = 0, res = -1
    while l > 0 {
        d[l] = nn % 2
        nn /= 2
        l += 1
    }
    for p in 1..<l {
        if p < l / 2 {
            var ok = true
            for i in 0..<l-p {
                if d[i] != d[i+p] {
                    ok = false
                    break
                }
            }
            if ok {
                res = p
            }
        }
    }
    return res
}

选择排序、冒泡排序、插入排序

都将数组分为已排序部分和未排序部分

选择排序

将已排序部分定义在左端,然后选择未排序部分的最小元素和未排序部分的第一个元素交换。

/** 
 *  【选择排序】:最值出现在起始端
 *  
 *  第1趟:在n个数中找到最小(大)数与第一个数交换位置
 *  第2趟:在剩下n-1个数中找到最小(大)数与第二个数交换位置
 *  重复这样的操作...依次与第三个、第四个...数交换位置
 *  第n-1趟,最终可实现数据的升序(降序)排列。
 *
 */
void selectSort(int *arr, int length) {
    for (int i = 0; i < length - 1; i++) { //趟数
        for (int j = i + 1; j < length; j++) { //比较次数
            if (arr[i] > arr[j]) {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
}

冒泡排序

将已排序部分定义在右端,在遍历未排序部分的过程执行交换,将最大元素交换到最右端。

/** 
 *  【冒泡排序】:相邻元素两两比较,比较完一趟,最值出现在末尾
 *  第1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n个元素位置
 *  第2趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n-1个元素位置
 *   ……   ……
 *  第n-1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第2个元素位置 
 */
void bublleSort(int *arr, int length) {
    for(int i = 0; i < length - 1; i++) { //趟数
        for(int j = 0; j < length - i - 1; j++) { //比较次数
            if(arr[j] > arr[j+1]) {
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        } 
    }
}

插入排序

将已排序部分定义在左端,将未排序部分元的第一个元素插入到已排序部分合适的位置。

 /**
 *  折半查找:优化查找时间(不用遍历全部数据)
 *
 *  折半查找的原理:
 *   1> 数组必须是有序的
 *   2> 必须已知min和max(知道范围)
 *   3> 动态计算mid的值,取出mid对应的值进行比较
 *   4> 如果mid对应的值大于要查找的值,那么max要变小为mid-1
 *   5> 如果mid对应的值小于要查找的值,那么min要变大为mid+1
 *
 */ 
 
// 已知一个有序数组, 和一个key, 要求从数组中找到key对应的索引位置 
int findKey(int *arr, int length, int key) {
    int min = 0, max = length - 1, mid;
    while (min <= max) {
        mid = (min + max) / 2; //计算中间值
        if (key > arr[mid]) {
            min = mid + 1;
        } else if (key < arr[mid]) {
            max = mid - 1;
        } else {
            return mid;
        }
    }
    return -1;
}

二分查找

折半查找:优化查找时间(不用遍历全部数据)

折半查找的原理

  • 1> 数组必须是有序的
  • 2> 必须已知min和max(知道范围)
  • 3> 动态计算mid的值,取出mid对应的值进行比较
  • 4> 如果mid对应的值大于要查找的值,那么max要变小为mid-1
  • 5> 如果mid对应的值小于要查找的值,那么min要变大为mid+1

// 已知一个有序数组, 和一个key, 要求从数组中找到key对应的索引位置

int findKey(int *arr, int length, int key) {
    int min = 0, max = length - 1, mid;
    while (min <= max) {
        mid = (min + max) / 2; //计算中间值
        if (key > arr[mid]) {
            min = mid + 1;
        } else if (key < arr[mid]) {
            max = mid - 1;
        } else {
            return mid;
        }
    }
    return -1;
}

将一个字符串进行反转(考虑系统表情符号)

- (NSString*)reverseString:(NSString*)str {
    NSMutableString *reverString = [NSMutableString stringWithCapacity:str.length];
    NSMutableArray  *strRanges = [NSMutableArray arrayWithCapacity:str.length];
    NSRange range;
    for(NSInteger i = 0; i < str.length; i += range.length) {
        range = [str rangeOfComposedCharacterSequenceAtIndex:i];
        [strRanges addObject:[NSValue valueWithRange:NSMakeRange(i, range.length)]];
    }
    
    for (NSInteger i = strRanges.count - 1; i >= 0; i -= 1) {
        range = [[strRanges objectAtIndex:i] rangeValue];
        [reverString appendString:[str substringWithRange:range]];
    }
    
    return [reverString copy];
}

在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的,也不知道每个数字重复几次。请找出数组中任意一个重复的数字

- (NSInteger)duplicateInArray:(NSMutableArray<NSNumber*>*)array {
    if (array == nil || array.count == 0) {
        return -1;
    }
    
    for (int i = 0; i < array.count; i++) {
        NSInteger valueAtI = [[array objectAtIndex:i] integerValue];
        while (valueAtI != i) {
            
            NSInteger valueAtArrayI = [[array objectAtIndex:valueAtI] integerValue];
            NSLog(@"----%d  valueAtArrayI %ld = %ld",i,valueAtI,valueAtArrayI);
            if (valueAtI == valueAtArrayI) {
                return valueAtI;
            }
            [array exchangeObjectAtIndex:i withObjectAtIndex:valueAtI];
            valueAtI = valueAtArrayI;
        }
    }
    return -1;
}

其余算法

LRU算法是否了解,如何实现一套LRU算法?

LRU(Least recently used 最近最少使用)算法是一个缓存淘汰算法,其作用就是当缓存很多时,该淘汰哪些内容,见名知意,它的核心思想是淘汰最近使用最少的内容。实现它的关键步骤是:

  • 新数据插入到链表的头部
  • 每当缓存命中时,则将数据移动到链表头部
  • 链表满时,将尾部数据清除

面试中出现过的算法

判断一颗二叉树是否是平衡二叉树

手写LFU

求N!

  • 字符串转整形

  • 反转链表(递归和非递归)

  • 将两个有序链表合并成一个有序链表

  • 给定一个数字n 求出全部集合(n = 3 输出 [[],[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]])

  • Lc 200 求岛屿个数

  • 判断一个字符串是不是对称的字符串,比如 abba 或者 aba 这样的就是对称的

  • 二叉树逐层打印

  • 判断一个字符串是不是 ipv6 地址

  • 找出一个页面中漏出部分面积最大的试图,重合的部分按照最上层的面积算漏出,会有时间复杂度的要求。

  • 一个坦克从一个空间的起点到终点,中间在某些位置上有阻隔的情况下,判断从起点到终点是否有可行路径的算法题。

  • 二叉树翻转

  • 每K个节点翻转链表

  • 顺时针打印矩阵

  • 自实现pow(double, double)

  • 两个排序数组的中位数