ARTS #003

ARTS #003

Posted by Dan on August 15, 2018

ARTS是由左耳朵耗子--陈皓发起的一个活动: 每周至少做一个leetcode的算法题、阅读并点评至少一篇英文技术文章、学习至少一个技术技巧、分享一篇有观点和思考的文章。(也就是Algorithm、Review、Tip、Share简称ARTS),至少坚持一年。

ARTS 003

这是第三篇,写的比较水,希望以后越来越好。

Algorihm 算法题

leetcode算法第151题 Reverse Words in a String(翻转字符串里的单词) 难道:中等

Given an input string, reverse the string word by word.

Example:  

Input: "the sky is blue",
Output: "blue is sky the".
Note:

A word is defined as a sequence of non-space characters.
Input string may contain leading or trailing spaces. However, your reversed string should not contain leading or trailing spaces.
You need to reduce multiple spaces between two words to a single space in the reversed string.
Follow up: For C programmers, try to solve it in-place in O(1) space.

给定一个字符串,逐个翻转字符串中的每个单词。

示例:  

输入: "the sky is blue",
输出: "blue is sky the".
说明:

无空格字符构成一个单词。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
进阶: 请选用C语言的用户尝试使用 O(1) 时间复杂度的原地解法。

如果不考虑空格问题,这个算法还是比较简单的,把整个字符串翻转,然后再翻转每一个单词,这道题的难点是怎么去除多余的空格,包括 开头的空格、结尾的空格,单词之间多余的空格,实现如下:

void ReverseString(char* str,int min,int max) {
    int i = min;
    int j = max;
    while (i < j) {
        char temp = * (str + i);
        * (str + i) = * (str + j);
        * (str + j)  = temp;
        i++;
        j--;
    }
}

//翻转字符串
void reverseWords(char *fdg) {
    RemoveEmptyChar(fdg);
    ReverseString(fdg,0,strlen(fdg) -1);
    int i = 0;
    int j = 0;
    while (*(fdg + j)) {
        if (*(fdg + j) == ' ') {
            ReverseString(fdg,i,j-1);
            i=j+1;
        }
        j++;
        
    }
    ReverseString(fdg,i,j-1);
}

//去掉空格
void RemoveEmptyChar(char* str) {
     //先去掉尾部的空格
    int j  = strlen(str) -1;
    while (*(str + j) == ' ') {
        *(str + j) = '\0';
        j--;
    }
    
    //去掉头部的空格
    int i = 0;
    while (*(str + i) == ' ') {
        i++;
    }
    if(i > 0){
        int count = 0;
        while ((count < strlen(str) - i) && *(str +i + count)) {
            *(str + count) = *(str +i + count);
            count++;
        }
        *(str + count) = '\0';
    }
    
    //去掉中间的空格
    int m  = 0;
    int flag = 0;
    while (m < strlen(str)) {
        if (*(str + m) == ' '){
            flag++;
            m++;
            continue;
        }
        if (flag > 1) {
            int count = 0;
            while ((count < strlen(str) -m)) {
                *(str + m - flag + 1 + count) = *(str + m + count);
                count++;
            }
            *(str + m - flag + 1 + count) = '\0';
            m = m - flag ;
        }
        flag = 0;
        m++;
    }
}

这个算法在leetcode有个问题是会超时,从上面可以看到,翻转字符串的方法没有优化的空间了,导致超时的应该是去除空格的方法,去除空格的方法的实现是,如果遇到多余的空格,就把空格后面的字符串向前移动,最差的情况下字符移动的次数差不多应该是,第2个字符移动 1次,第3个字符移动 2次,第n个字符移动 n-1次,时间复杂度是O(n2),实际上不用这么多次移动的,一个字符移动一次就可以了,改进后的去除空格的方法方法如下:

void RemoveEmptyChar1(char* str) {
    char * i = str;
    char * j = str;
    while (*i  && (*i == ' ')) {
        if (i == str) {
            j = i;
            while (*j && (*j == ' ')) {
                j++;
            }
            
            if (!*j) {//说明全是空格
                i = j;
            }
            
            while (*j && (*j != ' ')) {
                *i = *j;
                *j = ' ';
                i++;
                j++;
            }

        }
        else{
            if (*(i+1) == ' ') {
                j = i+ 1;
                while (*j && (*j == ' ')) {
                    j++;
                }
                i++;
                
                while (*j && (*j != ' ')) {
                    *i = *j;
                    *j = ' ';
                    i++;
                    j++;
                }
            }
            else{
                i++;
            }
        }
    }
    
    //先去掉尾部的空格
    int m  = strlen(str) -1;
    while (*(str + m) == ' ') {
        *(str + m) = '\0';
        m--;
    }
}

改进之后再在leetcode的运行时间是4ms,而leetcode上提交的答案中最好的是0ms,我看了一下,翻转字符串的方法和我的是一样的,不一样的还是去除空格的方法,0ms的去除空格的方法如下:

void updateString(char *s) {
    int i = 0;
    int end = 0;
    int index = 0;
    bool flag = false;
    while (s[i] == ' ') {
        i++;
    }
    while (s[i] != '\0') {
        if (s[i] == ' ') {
            flag = true;
        } else {
            if (flag) {
                index = end + 1;
                end = index + 1;
                flag = false;
            } else {
                index = end;
                end++;
            }
            if (i > index) {
                s[index] = s[i];
                s[i] = ' ';
            }
        }
        i++;
    }
    s[end] = '\0';
}

可以看到出去他的实现中是没有循环嵌套的,也就是说他只用了一趟循环就搞定了,他实现的时候一次最多移动一个字符。而我上面的实现,在一次循环中,如果需要移动的话,至少移动一个单词,所有有循环的嵌套。

Review

下面这篇文章来自:https://littlebitesofcocoa.com/251-face-aware-image-views-with-aspectfillfaceaware; 讲的是使用aspectfillfaceaware,来实现给imageView设置图片的时候,如果图片中包含人脸,可以自动让人脸居中显示。

Face Aware Image Views with AspectFillFaceAware (用AspectFillFaceAware让imageView识别人脸)

When using UIImageViews, sometimes the built-in content modes can cramp our style.

使用UIImageViews时,有时内置的内容模式会破坏我们的风格,不能满足我们的需求。

Many times, we’re displaying photos of people. In these cases, it’d be great if the image view could somehow be told to intelligently crop the photo around the person’s face.

很多时候,我们展示的是人物的照片。 在这些情况下,如果图像视图可以以某种方式智能地在人的脸部周围裁剪照片,那就太棒了。

Today we’ll check out a library from Beau Nouvelle called AspectFillFaceAware. It’s super simple, let’s take a look.

今天我们将看看Beau Nouvelle的一个名为AspectFillFaceAware的类库。 它非常简单,让我们来看看。

AspectFillFaceAware is essentially just an extension on UIImageView. It provides two ways to configure an image view to be “face aware”.

AspectFillFaceAware本质上只是UIImageView的扩展。 它提供了两种将图像视图配置为“面部识别”的方法。

The first is in Interface Builder, we can enable the feature by flipping on the feature in the Inspector. (Not seeing the option? Run your project once, then it should appear).

第一种方法是在Interface Builder中,我们可以通过翻阅Inspector中的功能来启用该功能。 (没有看到选项?运行您的项目一次,然后它应该出现)。

Here’s the how it looks:

We can also enable the functionality in code by setting the image view’s image using this new function:

我们还可以通过代码的方式使用此新功能:

imageView.set(image: avatar, focusOnFaces: true) We can even throw a quick corner radius on the image view’s layer to try out the “face aware” functionality on a circular view. (i.e. user avatars):

我们甚至可以在图像视图的图层上抛出一个快速角半径,以在圆形视图上尝试“面部识别”功能。 (即用户头像):

let radius = imageView.bounds.size.width / 2.0 imageView.layer.cornerRadius = radius

Under the hood, the library is using a low accuracy CIDetector with a type of CIDetectorTypeFace to handle the actual face detection. Want to dive deeper here? We covered CIDetectors way back in Bite #87.

在引擎盖下,库正在使用具有CIDetectorTypeFace类型的低精度CIDetector来处理实际的面部检测。 想深入研究吗? 我们在Bite #87.中介绍了CIDetectors的方法

Tip

####1 使用Xcode调试的时候怎么给系统方法打断点? 答案是breakpoint,比如给setContentInset方法,打断点,可以使用如下命令:

breakpoint set -S  setContentInset:

具体可参考:https://www.jianshu.com/p/8e9fc9a8ab78?from=jiantop.com

####2 UITableView 优化 UITableView 优化可以说是iOS开发中永远不会过时的话题,这里总结一下优化思路,具体做法可以自行Google。 总体思路如下:

  1. 减少CPU/GPU计算量 1.1 cell的重用机制 1.2 将 cell 高度和 cell 里的控件的 frame 缓存在 model 里 1.3 减少 cell 内部控件的层级 1.4 通过覆盖圆角图片来实现头像的圆角效果

  2. 按需加载 cell 2.1 在 cellForRow: 方法里只加载可见 cell 2.2 监听 tableview 的快速滚动,保存目标滚动范围的前后三行的索引

  3. 异步处理cell 3.1 异步加载网络图片 3.2 异步绘制本地图片 3.3 异步绘制 UIView 3.4 异步绘制 NSString 3.5 异步绘制 UILabel

Share

今天分享一个问题,今天在一个群里看到这个一个问题

你会选择哪个呢,选D吗?

从以往的知识来讲应该选择D,但是看到有人说选B,我就试了一下,测试代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    for (int i =0; i < MAXFLOAT; i++) {
         @autoreleasepool{
             UIImage *img = [[UIView alloc] init];// A
             
             UILabel *lab = [[UILabel alloc] init];// B
             
             NSString *str = @"abc";
             str = [str stringByAppendingString:@"xyz"];// C
        }
    }
}

结果真是B,B会导致内存一直增加,经过测试发现,不管是有autoreleasepool,B都会导致内存一直增加,另外两个都不会导致内存一直增加,奇怪,按理说,加了@autoreleasepool后,当内存到达一定值后会自动释放的,在群里看的解释是:

  1. 和线程有关
  2. Apple 对 for 循环有优化, 假如不是 UIView 及其子类, 会放到子线程处理
  3. UIView 对象会自动在主线程创建
  4. 你把UIImage 放到主线程处理, 内存一样会飙升
  5. 主线程的自动释放池释放方式和子线程不一样

但是还是没明白为什么: 1 lab是局部变量,除了for循环的右括号应该释放的。 2 加了@autoreleasepool,当内存达到峰值的时候也应该释放的。 ???