字節跳動的算法面試題是什麼難度?

因爲 lucifer 我是一個小前端, 最近也在準備寫一個《前端如何搞定算法面試》的專欄,所以最近沒少看各大公司的面試題。都說字節跳動算法題比較難,我就先拿 ta 下手 。此次咱們就拿一套 2018 年的前端校招(第四批)來看下字節的算法筆試題的難度幾何。地址:https://www.nowcoder.com/test...前端

這套題一共四道題, 兩道問答題, 兩道編程題。git

其中一道問答題是 LeetCode 426 的原題,只不過題型變成了找茬(改錯)。惋惜的是 LeetCode 的 426 題是一個會員題目,沒有會員的就看不來了。不過,劍指 Offer 正好也有這個題,而且力扣將劍指 Offer 所有的題目都 OJ 化了。 這道題你們能夠去 https://leetcode-cn.com/probl... 提交答案。簡單說一下這個題目的思路,咱們只須要中序遍歷便可獲得一個有序的數列,同時在中序遍歷過程當中將 pre 和 cur 節點經過指針串起來便可。github

另外一個問答是紅包題目,這裏很少說了。咱們重點看一下剩下兩個算法編程題。面試

兩個問答題因爲不能在線判題,我沒有作,只作了剩下兩個編程題。

球隊比賽

第一個編程題是一個球隊比賽的題目。算法

題目描述

有三隻球隊,每隻球隊編號分別爲球隊 1,球隊 2,球隊 3,這三隻球隊一共須要進行 n 場比賽。如今已經踢完了 k 場比賽,每場比賽不能打平,踢贏一場比賽得一分,輸了不起分不減分。已知球隊 1 和球隊 2 的比分相差 d1 分,球隊 2 和球隊 3 的比分相差 d2 分,每場比賽能夠任意選擇兩隻隊伍進行。求若是打完最後的 (n-k) 場比賽,有沒有可能三隻球隊的分數打平。編程

思路

假設球隊 1,球隊 2,球隊 3 此時的勝利次數分別爲 a,b,c,球隊 1,球隊 2,球隊 3 總的勝利次數分別爲 n1,n2,n3。數組

我一開始的想法是隻要保證 n1,n2,n3 相等且都小於等於 n / 3 便可。若是題目給了 n1,n2,n3 的值就直接:app

print(n1 == n2 == n3 == n / 3)

但是不只 n1,n2,n3 沒給, a,b,c 也沒有給。ide

實際上此時咱們的信息僅僅是:ui

① a + b + c = k
② a - b = d1 or b - a = d1
③ b - c = d2 or c - b = d2

其中 k 和 d1,d2 是已知的。a ,b,c 是未知的。 也就是說咱們須要枚舉全部的 a,b,c 可能性,解方程求出合法的 a,b,c,而且 合法的 a,b,c 都小於等於 n / 3 便可。

這個 a,b,c 的求解數學方程就是中學數學難度, 三個等式化簡一下便可,具體見下方代碼區域。
  • a 只須要再次贏得 n / 3 - a 次
  • b 只須要再次贏得 n / 3 - b 次
  • c 只須要再次贏得 n / 3 - c 次
n1 = a + n / 3 - a = n / 3
n2 = b + (n / 3 - b) = n / 3
n3 = c + (n / 3 - c) = n / 3

代碼(Python)

牛客有點讓人不爽, 須要 print 而不是 return
t = int(input())
for i in range(t):
    n, k, d1, d2 = map(int, input().split(" "))
    if n % 3 != 0:
        print('no')
        continue
    abcs = []
    for r1 in [-1, 1]:
        for r2 in [-1, 1]:
            a = (k + 2 * r1 * d1 + r2 * d2) / 3
            b = (k + -1 * r1 * d1 + r2 * d2) / 3
            c = (k + -1 * r1 * d1 + -2 * r2 * d2) / 3
            a + r1
            if  0 <= a <= k and 0 <= b <= k and 0 <= c <= k and a.is_integer() and b.is_integer() and c.is_integer():
                abcs.append([a, b, c])
    flag = False
    for abc in abcs:
        if len(abc) > 0 and max(abc) <= n / 3:
            flag = True
            break
    if flag:
        print('yes')
    else:
        print('no')

複雜度分析

  • 時間複雜度:$O(t)$
  • 空間複雜度:$O(t)$

小結

感受這個難度也就是力扣中等水平吧,力扣也有一些數學等式轉換的題目, 好比 494.target-sum

轉換字符串

題目描述

有一個僅包含’a’和’b’兩種字符的字符串 s,長度爲 n,每次操做能夠把一個字符作一次轉換(把一個’a’設置爲’b’,或者把一個’b’置成’a’);可是操做的次數有上限 m,問在有限的操做數範圍內,可以獲得最大連續的相同字符的子串的長度是多少。

思路

看完題我就有種似曾相識的感受。

每次對妹子說出這句話的時候,她們都會以爲好假 ^_^

不過此次是真的。 」哦,不!每次都是真的「。 這道題其實就是我以前寫的滑動窗口的一道題【1004. 最大連續 1 的個數 III】滑動窗口(Python3)的換皮題。 專題地址:https://github.com/azl3979858...

因此說,若是這道題你徹底沒有思路的話。說明:

  • 抽象能力不夠。
  • 滑動窗口問題理解不到位。

第二個問題能夠看我上面貼的地址,仔細讀讀,並完成課後練習便可解決。

第一個問題就比較困難了, 不過多看個人題解也能夠慢慢提高的。好比:

迴歸這道題。其實咱們只須要稍微抽象一下, 就是一個純算法題。 抽象的另一個好處則是將不少不一樣的題目返璞歸真,從而能夠在茫茫題海中逃脫。這也是我開啓《我是你的媽媽呀》 的緣由之一。

若是咱們把 a 當作是 0 , b 當作是 1。或者將 b 當作 1, a 當作 0。不就抽象成了:

給定一個由若干 0 和 1 組成的數組 A,咱們最多能夠將 m 個值從 0 變成 1 。

返回僅包含 1 的最長(連續)子數組的長度。

這就是 力扣 1004. 最大連續 1 的個數 III 原題。

所以實際上咱們要求的是上面兩種狀況:

  1. a 表示 0, b 表示 1
  2. a 表示 1, b 表示 0

的較大值。

lucifer 小提示: 其實咱們也能夠僅僅考慮一種狀況,好比 a 當作是 0 , b 當作是 1。這個時候, 咱們操做變成了兩種狀況,0 變成 1 或者 1 變成 0,同時求解的也變成了最長連續 0 或者 最長連續 1 。 因爲這種抽象操做起來更麻煩, 咱們不考慮。

問題獲得了抽象就好解決了。咱們只須要記錄下加入窗口的是 0 仍是 1:

  • 若是是 1,咱們什麼都不用作
  • 若是是 0,咱們將 m 減 1

相應地,咱們須要記錄移除窗口的是 0 仍是 1:

  • 若是是 1,咱們什麼都不作
  • 若是是 0,說明加進來的時候就是 1,加進來的時候咱們 m 減去了 1,這個時候咱們再加 1。
lucifer 小提示: 實際上題目中是求 連續 a 或者 b 的長度。看到連續,你們也應該有滑動窗口的敏感度, 別管行不行, 想到總該有的。

咱們拿 A = [1, 1, 0, 1, 0, 1], m = 1 來講。看下算法的具體過程:

lucifer 小提示: 左側的數字表示此時窗口大小,黃色格子表示修補的牆,黑色方框表示的是窗口。

這裏我形象地將 0 當作是洞,1 當作是牆, 咱們的目標就是補洞,使得連續的牆最長。

每次碰到一個洞,咱們都去不加選擇地修補。因爲 m 等於 1, 也就是說咱們最多補一個洞。所以須要在修補超過一個洞的時候,咱們須要調整窗口範圍,使得窗口內最多修補一個牆。因爲窗口表示的就是連續的牆(已有的或者修補的),所以最終咱們返回窗口的最大值便可。

因爲下面的圖窗口內有兩個洞,這和」最多補一個洞「衝突, 咱們須要收縮窗口使得知足「最多補一個洞」的先決條件。

所以最大的窗口就是 max(2, 3, 4, ...) = 4。

lucifer 小提示: 能夠看出咱們不加選擇地修補了全部的洞,並調整窗口,使得窗口內最多有 m 個修補的洞,所以窗口的最大值就是答案。然而實際上,咱們並不須要真的」修補「(0 變成 1),而是僅僅修改 m 的值便可。

咱們先來看下抽象以後的其中一種狀況的代碼:

class Solution:
    def longestOnes(self, A: List[int], m: int) -> int:
        i = 0
        for j in range(len(A)):
            m -= 1 - A[j]
            if m < 0:
                m += 1 - A[i]
                i += 1
        return j - i + 1

所以完整代碼就是:

class Solution:
    def longestOnes(self, A: List[int], m: int) -> int:
        i = 0
        for j in range(len(A)):
            m -= 1 - A[j]
            if m < 0:
                m += 1 - A[i]
                i += 1
        return j - i + 1
    def longestAorB(self, A:List[int], m: int) -> int:
        return max(self.longestOnes(map(lambda x: 0 if x == 'a' else 1, A) ,m), self.longestOnes(map(lambda x: 1 if x == 'a' else 0, A),m))

這裏的兩個 map 會生成兩個不一樣的數組。 我只是爲了方便你們理解才新建的兩個數組, 實際上根本不須要,具體見後面的代碼.

代碼(Python)

i = 0
n, m = map(int, input().split(" "))
s = input()
ans = 0
k = m #   存一下,後面也要用這個初始值
# 修補 b
for j in range(n):
    m -= ord(s[j]) - ord('a')
    if m < 0:
        m += ord(s[i]) - ord('a')
        i += 1
ans = j - i + 1
i = 0
# 修補 a
for j in range(n):
    k += ord(s[j]) - ord('b')
    if k < 0:
        k -= ord(s[i]) - ord('b')
        i += 1
print(max(ans, j - i + 1))

複雜度分析

  • 時間複雜度:$O(N)$
  • 空間複雜度:$O(1)$

小結

這道題就是一道換了皮力扣題,難度中等。若是你能將問題抽象,同時又懂得滑動窗口,那這道題就很容易。我看了題解區的參考答案, 內容比較混亂,不夠清晰。這也是我寫下這篇文章的緣由之一。

總結

這一套字節跳動的題目一共四道,一道設計題,三道算法題。

其中三道算法題從難度上來講,基本都是中等難度。從內容來看,基本都是力扣的換皮題。可是若是我不說他們是換皮題, 大家能發現麼? 若是你能夠的話,說明你的抽象能力已經略有小成了。若是看不出來也沒有關係,關注我。 手把手扒皮給大家看,扒多了慢慢就會了。切記,不要盲目作題!若是你作了不少題, 這幾道題仍是看不出套路,說明你該緩緩,改變下刷題方式了。

更多題解能夠訪問個人 LeetCode 題解倉庫:https://github.com/azl3979858... 。 目前已經 36K+ star 啦。

關注公衆號力扣加加,努力用清晰直白的語言還原解題思路,而且有大量圖解,手把手教你識別套路,高效刷題。

相關文章
相關標籤/搜索