前端笔试凉经(一) - 58同城 + 微众银行

244 阅读8分钟

前文

今天来看看作者是怎么被前端笔试题目吊打的,估计已经凉凉,等来年春招!

微众银行(一)

题目描述: 随机播放器 时间限制:3000MS 内存限制:589824KB

喜欢听歌的小明觉得系统的随机播放总是不能让他满意,于是小明自己想了一种全新的随机播放歌曲方法。

小明会播放歌单中的第一首曲子,然后将播放的这首曲子移除出歌单,如果歌单里还有曲子,就把剩余曲子中的第一首拿到最后一首去。小明会如此播放直到所有曲子都被移除出歌单 (也就是都被播放了一遍) 。

小明播了几次就累了,他不想一遍又一遍操作,于是向你求助: 他把初始的歌单给你,希望你能直接把按照小明规则下的真实播放顺序告诉给他。

输入描述
第一行 1 个整数 n,表示歌单的长度
第二行 n 个整数 a1 a2...an,表示从第一首到最后一首顺序的初始歌单中各歌曲的ID。对于100%的数据,1 <= n <= 50000 ,1 <= ai <= 10^9

输出描述
输出一行n个整数表示按照规则后真正的放歌顺序

样例输入:
6
2 3 2 1 4 5

样例输出:
2 2 4 3 5 1

提示:开始播放歌单中第一首,即歌曲2。将其移除出歌单后为[3 2 1 4 5],接着将剩余歌单中第一首移动到最后一首,得到[2 1 4 5 3]。 播放剩余歌单的第一首,即歌曲2。将其移除出歌单后为[1 4 5 3],接着将剩余歌单中第一首移动到最后一首,得到[4 5 3 1]。 播放剩余歌单的第一首,即歌曲4。将其移除出歌单后为[5 3 1],接着将剩余歌单中第一首移动到最后一首,得到[3 1 5]。 播放剩余歌单的第一首,即歌曲3。将其移除出歌单后为[1 5],接着将剩余歌单中第一首移动到最后一首,得到[5 1]。 播放剩余歌单的第一首,即歌曲5。将其移除出歌单后为[1],接着将剩余歌单中第一首移动到最后一首,得到[1]。 播放最后一首,即歌曲1。 实际播放顺序为: 2 2 4 3 5 1

解题思路:

思路很简单,使用数组的模仿队列方法,设置一定条件,使得队列数组始终隔代删除头部,添加尾部

代码实现:

 function a(n, songs) {
            let result = []

            while(songs.length > 0) {
              const song1 = songs.shift()
              result.push(song1)

              if (result.length > 0) {
                const song2 = songs.shift()
                songs.push(song2)
              }
              
            }
               return result
        }

代码分析

初始化一个空数组 `result`,用来存储最终处理后的歌曲列表。
进入一个循环,循环的条件是 `songs` 数组的长度大于 0,也就是还有未处理的歌曲。
在每次循环迭代中,从 `songs` 数组中取出第一首歌曲 `song1`,然后将其添加到 `result` 数组中。这表示每次处理都会将一个歌曲放入 `result`。
接下来,检查 `result` 数组的长度是否大于 0。如果大于 0,说明 `result` 中至少有一首歌曲。在这种情况下,再次从 `songs` 数组中取出一首歌曲 `song2`,然后将其添加回到 `songs` 数组的末尾。这一步的目的是将处理过的歌曲重新放回到 `songs` 数组的末尾,以便在下一次循环中继续处理。
循环继续,重复步骤 3 和步骤 4,直到 `songs` 数组为空,即所有歌曲都被处理完毕。
最后,函数返回存储在 `result` 数组中的歌曲列表,这个列表的顺序是按照一定规则处理后的结果。

时间与空间复杂度分析:

时间复杂度:

  • 在 while 循环中,我们不断地从 songs 数组中取出歌曲,然后再添加回去。每次循环迭代,我们从 songs 中取出两个歌曲(song1 和 song2),然后将 song2 添加回数组的末尾。
  • 这意味着每个循环迭代都需要执行常数次操作,具体来说,每个循环迭代执行了两次 shift 操作和一次 push 操作。
  • 因此,while 循环的总迭代次数取决于 songs 数组的长度,假设 songs 的长度为 n,则循环迭代的次数约为 n/2。
  • 所以,整个代码的时间复杂度为 O(n)。

空间复杂度:

  • 代码中使用了一个额外的数组 result 来存储重新排列后的歌曲。
  • 此外,还有一些常数级的额外空间开销,如变量 song1 和 song2
  • 因此,整个代码的空间复杂度主要由 result 数组决定,其空间复杂度为 O(n)。

58同城(一)

题目描述:

举例说明:现在有一个整数数组nums,找到其中的最长严格递增 (即两个相等的项不算递增)的子序列的长度。

子序列是由数组派生而来的序列,删除(或不删除) 数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例1:输入: nums = [3,6,4,7,0,7,4,5,8,9]输出: 5
解释: 最长的递增子序列有两个[3,4,5,8,9][0,4,5,8,9][3,6,7,8,9],因此长度为5

示例2
输入:nums = [5,4,3,2,1]
输出:1
解释:最长的递增子序列为[5][4][3][2][1],因此长度为1

示例3
输入:nums = [2,2,2,2,2]
输出::1
解释:最长递增子序列为[2],因此长度为1

示例4
输入:nums = [10,9,2,5,3,7,101,18]
输出: 4

解题思路:

通过动态维护一个递增的数组 arr,使用二分查找来找到合适的位置插入或更新元素,以求得最长递增子序列。

由于需要每迭代一次就要给出一个最优解,其次需要查找每次递增数组中的元素,我们可以考虑二分查找结合贪心算法的思想去解决本题

代码实现

function lengthOfLIS(nums) {
  let arr = [],
  left,
  right
   
  for (const num of nums) {
      left = 0
      right = arr.length

      while (left < right) {    
          const mid = Math.floor((left + right) / 2)
          if (arr[mid] < num) {
             left = mid + 1
          } else {
             right = mid
          }
      }
  
      if (left === arr.length) {
        arr.push(num)
      }  else {
        arr[right] = num 
      }
  }
  return arr
}

代码分析

一、原理解析

创建一个空数组 `arr`,用来存储最长递增子序列。
遍历输入数组 `nums` 中的每个元素 `num`。
对于每个元素 `num`,使用二分查找来确定它在数组 `arr` 中的插入位置。这个二分查找的目标是在 `arr` 中找到第一个大于或等于 `num` 的元素的位置。
如果找到的位置 `left` 等于 `arr` 的长度,说明 `num` 大于 `arr` 中的所有元素,因此将 `num` 添加到 `arr` 的末尾,扩展了最长递增子序列。
否则,如果找到的位置 `left` 不等于 `arr` 的长度,说明 `num` 应该插入到 `arr` 中某个位置,这个位置是 `left`,然后将 `arr` 中的该位置的元素更新为 `num`。这样,不会增加 `arr` 的长度,但可能改变了其中的某个元素,从而构成更长的递增子序列。
最终,函数返回数组 `arr`,该数组包含了最长递增子序列。

二、核心思想:贪心算法

体现在以下几个方面:

  1. 逐步构建最优解: 代码中通过迭代遍历输入数组 nums,逐步构建最长递增子序列 arr。每次迭代都选择当前元素 num,然后通过二分查找找到 num 应该插入的位置,并将其插入 arr 中。这个过程是逐步构建最优解的典型特征,每一步都选择当前最佳的元素来扩展子序列。
  2. 贪心选择策略: 代码中采用了贪心的选择策略,即始终选择能够使得当前递增子序列更长的元素。如果 num 大于当前子序列的最后一个元素,那么它被添加到子序列的末尾,从而使子序列更长。如果 num 不大于最后一个元素,那么通过二分查找找到合适的插入位置,以便后续的元素有更多的机会加入。
  3. 无后效性: 贪心法通常具有无后效性,即当前的选择不会影响以后的选择。在这段代码中,每次插入一个元素后,都不会影响后续元素的插入位置,因为后续元素仍然会根据自身的值和当前子序列的状态进行插入。

时间与空间复杂度分析:

时间复杂度:

  • 遍历输入数组 nums 中的每个元素需要 O(n) 的时间,其中 n 是输入数组的长度。
  • 在内部的 while 循环中,进行二分查找,每次查找需要 O(log(n)) 的时间,最多执行 n 次查找。
  • 因此,整个算法的时间复杂度为 O(n * log(n))。

空间复杂度:

  • 声明了一个数组 arr 用于存储最长递增子序列,其长度最多为 n。
  • 此外,还有一些辅助变量 left 和 right,它们的空间复杂度可以忽略不计。
  • 因此,整个算法的空间复杂度为 O(n)。