太可惜了,四面字节跳动,我的offer竟被一道“算法题”给拦截了

简介: 算法,在行业里越来越重要,一线互联网公司也非常注重算法,所以在面试时基本上都有涉及到。字节跳动是出了名的爱问算法题,几乎每一面都要问到算法。实际上,现在很多公司都会问算法,尤其是对于应届生来说,要求更高,所以想要进大厂,搞定算法是很重要的。前段时间,我就去面了一次字节跳动,好不容易进到第四面,眼看offer就要拿到手了,可惜竟然被一道“算法题”给拦截了,与字节的offer失之交臂。今天就分享一下这道算法题,让我们好好聊一聊,怎么搞定字节跳动的面试吧!

前言

算法,在行业里越来越重要,一线互联网公司也非常注重算法,所以在面试时基本上都有涉及到。字节跳动是出了名的爱问算法题,几乎每一面都要问到算法。实际上,现在很多公司都会问算法,尤其是对于应届生来说,要求更高,所以想要进大厂,搞定算法是很重要的。

前段时间,我就去面了一次字节跳动,好不容易进到第四面,眼看offer就要拿到手了,可惜竟然被一道“算法题”给拦截了,与字节的offer失之交臂。今天就分享一下这道算法题,让我们好好聊一聊,怎么搞定字节跳动的面试吧!

网络异常,图片无法展示
|

拦截我的offer,竟是这道算法题

给定一个只包括 '(',')'的字符串,判断字符串是否有效。注:空字符串属于有效字符串

示例 1:
输入: "(())"
输出: true
 实例 2:
 输入: "())("
输出: false

第一眼看到这道题,我去,这么简单,稳了(因为一面的时候,刚刚被面试官怼过勇者斗恶龙的DP题,在 leetcdoe 属于 hard 级别)。

于是我也不假思索直接用来解决了,相信 99% 都会用栈解决吧?这里我稍微说一下过程吧,步骤如下:

1、在遍历字符串的过程中,遇到 "(" 就让它入栈,遇到 ")" 就判断下栈里面有没有 "(" :

(1)、如果有,则把处于栈顶的 "("  弹出,相当于和 ")" 进行匹配,然后继续往后遍历字符串
(2)、如果没有,则匹配失败。相当于字符串的最前面出现了 ")",显然这是不合理的。

2、当字符串遍历完成,判断栈是否为空,如果为空则表示字符串有效,否则无效。

为了兼顾小白,我该给你们画了个图演示,,,,我太良心了。

网络异常,图片无法展示
|

代码如下所示:

public static boolean isValid(String s){
    if(s == null || s.length() < 1)
        return true;
    int n = s.length();// 字符串长度
    // 创建一个栈来装字符
    Stack<Character> stack = new Stack<>();
    // 遍历字符串
    for(int i = 0; i < n; i++){
        // 获取字符串的第 i 个字符
        char c = s.charAt(i);
        if(c == '('){
            stack.push(c);
        }else{
            if(stack.isEmpty())
                return false;
            else
                stack.pop();
        }
    }
    // 判断是否为空
    if(stack.isEmpty())
        return true;
    return false;
}

接着面试官说我这道题的空间复杂度是 O(n),问我能优化一下吗?

说实话,如果你做过 leetcode 的第 20 题,可能你的脑子会被定向也不一定,因为那道题用栈来处理就是最优解的了。不过这道题属于简化版,其实可以把空间复杂度优化成 O(1),大家可以想一下哦。

由于我们栈里面存放的都是同一种字符 "(" ,其实我们可以用一个变量来取代栈的,这个变量就记录 "(" 的个数,遇到 "(" 变量就加 1,遇到 ")" 变量就减 1,栈为空就相当于变量的值为 0。

当时脑子有点不知为啥,就马上想到这个方法了,于是一分钟就修改好了代码,如下:

public static boolean isValid(String s){
    if(s == null || s.length() < 1)
        return true;
    int n = s.length();// 字符串长度
    // 用来记录遇到的 "(" 的个数
    int sum = 0;
    // 遍历字符串
    for(int i = 0; i < n; i++){
        // 获取字符串的第 i 个字符
        char c = s.charAt(i);
        if(c == '('){
            sum++;
        }else{
            if(sum == 0)
                return false;
            else
                sum--;
        }
    }
    return sum == 0 ? true : false;
}

这样子的话,时间复杂度为 O(n),空间复杂度为 O(1)。

接着面试官就继续就这道题继续加大难度,问题改为如下

给定一个只包含 '(' 和 ')' 的字符串,找出最长的包含有效括号的子串的长度。

示例 1:
输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"
示例 2:
输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"

其实这道题就是 leetcode 的原题,第 32 题,难度为 hard。

这道题由于我之前做过,微微一笑,假装用严肃的表情思考一下,然后马上给出了思路,刚开始我是用暴力法的。

1、暴力法

暴力法其实很简单,就是最开始把第一个字符当做最长有效括号的首字符来遍历字符串,接着把第二个字符当做最长有效括号的首字符来遍历字符串,接着把第三个字符......

例如对于 s = "( ) ) ( ( ) )"。

把第一个字符作为首字符,则 max = 2 (遇到第三个字符 ')' 就匹配不了了) 把第二个字符作为首字符,则 max = 0 (一开始就是 ')',显然啥也匹配不了) 把第三个字符作为首字符,则 max = 0 把第四个字符作为首字符,则 max = 4 ..... 这种做法的时间复杂度为 O(n^2),空间复杂度为 O(1)

基本上面那道题一样,只是做了 n 次遍历。

接着面试官问,还能优化吗?

早就知道会问优化的了,我自己之前也做过这道题,于是假装思考了一下,马上给出了优化。

2、优化

这道题的优化版本我们仍然是用栈来做,不过入栈的时候,不是让 "(" 入栈,而是让 "(" 的下标入栈。步骤如下:

1、先把 -1 放入栈内。(至于为什么?看到后面你就知道了) 2、、对于遇到的每个 '(' ,我们将它的下标放入栈中。 3、对于遇到的每个 ‘)’ ,我们弹出栈顶的元素并将当前元素的下标与弹出元素下标作差,得出当前有效括号字符串的长度

通过这种方法,我们继续计算有效子字符串的长度,并最终返回最长有效子字符串的长度。

看不懂?没事,我弄个例子画几个图,例如 s = "( ) ) ( ( ) )",并且用变量 max 来保存最长有效字符串的程度,i 表示当前字符串的下标

0、初始化:max = 0; i = 0。-1 放入栈内

网络异常,图片无法展示
|

1、i = 0,s[i] = '(',下标 i = 0 入栈

网络异常,图片无法展示
|

2、i = 1,s[i] = ')',出栈; i - 栈顶元素 = 1 - (-1) = 2,此时 max = 2

网络异常,图片无法展示
|

3、i = 2,s[i] = ')',出栈;这个时候要注意:由于 -1 出栈后,栈顶没有元素了,所以这个时候我们必须把 ')' 的下标入栈,相当于最开始的初始化。

网络异常,图片无法展示
|

4、i = 3,s[i] = '(',入栈;

网络异常,图片无法展示
|

5、i = 4,s[i] = '(',入栈;

网络异常,图片无法展示
|

6、i = 5,s[i] = ')',出栈;i - 栈顶 = 5 - 3 = 2;此时 max = 2;

网络异常,图片无法展示
|

7、i = 6,s[i] = ')',出栈;i - 栈顶 = 6 - 2 = 4;此时 max = 4;

网络异常,图片无法展示
|

8、遍历结束,最长有效括号为 4。

看不大懂?没事,看下代码加深理解了,代码如下:

public int longestValidParentheses(String s) {
    int max = 0;
    Stack<Integer> stack = new Stack<>();
    stack.push(-1);
    for (int i = 0; i < s.length(); i++) {
        if (s.charAt(i) == '(') {
        //下标入栈
            stack.push(i);
        } else {
        // 出栈
            stack.pop();
            // 看栈顶是否为空,为空的话就不能作差了
            if (stack.empty()) {
                stack.push(i);
            } else {
            // i - 栈顶,获得档期有效括号长度
                max = Math.max(max, i - stack.peek());
            }
        }
    }
    return maxans;
}

这种做法的时间复杂度为 O(n),空间复杂度为 O(n),能想到用栈来处理,算是很不错的了。

最后一击

我以为我给出这个解法算是可以的了,面试官应该换一道题的了,然后,面试官又来了一句:还能再优化吗?。这个时候我陷入了沉思.......

看文章的各位大佬们可以想一想在空间上是否还能优化,因为在时间上是不可能优化的了。

想了一会,居然不可以用栈,优化的方案肯定是类似于上面那道题一样,用记录数量的变量来代替栈,然后就被我想出了,具体如下:

实际上,这道题仍然可以像上面那样,用变量来代替栈来优化,不过这个时候我们需要两个变量,我们假设变量为 left 和 right。

我们在从从左到右遍历字符串的过程中,用 left 记录 '(' 的数量,用 right 记录 ')' 的数量。并且在遍历的过程中:

1、如果 left == right,显然这个时候 right 个 ')' 都将一定能够得到匹配。所以当前的有效括号长度为 2 * right。然后更新 max。

2、如果 left < right,显然这个时候部分 ')' 一定得不到匹配,此时我们把 left 和 right 都置为 0。

当遍历完字符串,我们是否就得到最大长度的有效括号了呢?大家可以想一下

答是不可以的,我们还需要从右到左遍历计算一下。

为什么呢?

因为实际上 '(' 和 ')' 其实是等价的,为什么就不可以倒过来遍历计算呢?所以,千万别忽略了哈。

最后的代码如下:

public int longestValidParentheses(String s) {
    int left = 0, right = 0, max = 0;
    // 从左到右
    for (int i = 0; i < s.length(); i++) {
        if (s.charAt(i) == '(') {
            left++;
        } else {
            right++;
        }
        if (left == right) {
            max = Math.max(max, 2 * right);
        } else if(right > left){
            left = right = 0;
        }
    }
    left = right = 0;
    // 从右到左
    for (int i = s.length() - 1; i >= 0; i--) {
        if (s.charAt(i) == '(') {
            left++;
        } else {
            right++;
        }
        if (left == right) {
            max = Math.max(max, 2 * left);
        } else if (left > right) {
            left = right = 0;
        }
    }
    return max;
}

这种做法的时间复杂度为 O(n),空间复杂度为 O(1)。


复习算法,再战字节跳动

对于字节跳动,心中总是有一股执念的,所以之后的计划是打算二战字节跳动,但要想过算法这关,还是得需要花一些心思的,所以算法的复习就尤为重要了。对于算法的学习,我主要是阅读一些书籍和文档资料,具体如下:

(1)LeetCode中文版

网络异常,图片无法展示
|

网络异常,图片无法展示
|

网络异常,图片无法展示
|

(2)算法的乐趣

网络异常,图片无法展示
|

网络异常,图片无法展示
|

网络异常,图片无法展示
|

(3)算法

网络异常,图片无法展示
|

网络异常,图片无法展示
|

更多算法资料:

网络异常,图片无法展示
|


总结

算法是需要大量练习的,首先我们需要选择基本比较好的教材来加强我们的理论知识,然后需要在这里理论基础上进行上机练习。

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

相关文章
|
7月前
|
消息中间件 算法 NoSQL
二十不惑的年纪,我简直走了狗屎运(4面拿字节跳动offer)
二十岁的年纪,青春张扬,无拘无束,这种状态自然是好事,不过在某种意义上,也并不能太过乐观。实际上,许多二十岁左右的年轻人,容易陷入误区,把无所事事当成自然状态,太过随意的生活容易让我们没有明确的人生目标和真实的人生理想,以至于毕业之后找不到工作,回家啃老,然后还要悻悻地安慰自己“谁的青春不迷茫”?
|
1月前
|
缓存 前端开发 JavaScript
"面试通关秘籍:深度解析浏览器面试必考问题,从重绘回流到事件委托,让你一举拿下前端 Offer!"
【10月更文挑战第23天】在前端开发面试中,浏览器相关知识是必考内容。本文总结了四个常见问题:浏览器渲染机制、重绘与回流、性能优化及事件委托。通过具体示例和对比分析,帮助求职者更好地理解和准备面试。掌握这些知识点,有助于提升面试表现和实际工作能力。
63 1
太可惜了,四面字节跳动,我的offer竟被一道“算法题”给拦截了
算法,在行业里越来越重要,一线互联网公司也非常注重算法,所以在面试时基本上都有涉及到。字节跳动是出了名的爱问算法题,几乎每一面都要问到算法。实际上,现在很多公司都会问算法,尤其是对于应届生来说,要求更高,所以想要进大厂,搞定算法是很重要的。
|
7月前
|
算法 Java 关系型数据库
在家“闭关”,阿里竟发来视频面试,4面顺利拿下offer
关于个人呢,我是一个普通的双非本科生,在校成绩不错,各方面的表现自我感觉也比较突出,今年大四即将毕业,对自己进入大厂工作是很有信心的,我的方向是Java,也知道现在Java的竞争比较激烈,大厂比较难进,但我丝毫不胆怯。当然,我还是很走“狗屎运”的,没想到闭关在家期间,也能收到阿里发来的视频面,还一路顺利拿下了offer。
|
7月前
|
缓存 算法 NoSQL
凭借一份“面试真经pdf”,我四面字节跳动,拿下1-2级offer
近两年是中国互联网企业组织架构升级的大年,阿里、腾讯、小米、快手等知名互联网企业都进行了相应调整。2020年3月12日,字节跳动成立八周年之际,宣布组织全面升级,而这一消息也彻底激起了我对字节跳动的期待。
|
7月前
|
NoSQL 安全 Java
三面阿里被挂,竟获内推名额,历经5面拿下口碑offer(Java后台)
每一个互联网人心中都有一个大厂梦,百度、阿里巴巴、腾讯是很多互联网人梦寐以求的地方,而我也不例外。但是,BAT等一线互联网大厂并不是想进就能够进的,它对人才的技术能力和学历都是有一定要求的,所以除了学历以外,我们的技术和能力都要过硬才行。
|
7月前
|
NoSQL Java 关系型数据库
刚拿的字节跳动offer“打水漂”,TikTok不去了,我该何去何从?
TikTok,作为字节跳动铺棋国际局的头兵,如今竟与华为有了相同遭遇,但似乎并不让人出乎意料。实际上,TikTok出售美国业务给微软,并非是我们想象的躺平跪倒,而是极端不利的形势下,被逼无奈的退让,所以就这一点来说,我也能给予理解。
|
7月前
|
算法 NoSQL Java
懵逼!阿里一面就被虐了,幸获内推华为技术四面,成功拿到offer
上个月,哥们从某小厂离职,转投阿里云,简历优秀,很顺利地拿到了面试通知,但之后的进展却让哥们怀疑人生了,或者说让哥们懵逼的是,面试阿里云居然第一面就被吊打?让哥们开始怀疑自己,是不是这些年工作下来,自己没有一丁点的进步呢?
|
7月前
|
SQL 算法 NoSQL
三面头条,靠P9级算法大牛分享的两本算法pdf书籍,轻松拿到offer
头条一面(Java+项目) 1.倒排索引 2.讲讲redis里面的哈希表? 3.happen-before的规则? 4.volatile修饰符,synchronize锁 5.java单例模式的实现,懒汉、饿汉? 6.进程与线程的区别,多进程和多线程的区别?
|
7月前
|
负载均衡 网络协议 算法
海投简历一个月无果,机遇巧合得前辈内推,五面蘑菇街终获offer
到目前为止使用Java到现在大概有两年多的时间,所以java算不上很好。刚开始投递的时候,刚辞职,也没准备笔试面试,很多东西都忘记了。所以,刚开始我并没有直接就投递蘑菇街,毕竟心里还是有一点点小害怕的。