Skip to content

Latest commit

 

History

History
409 lines (336 loc) · 12.4 KB

ArrowOffer.md

File metadata and controls

409 lines (336 loc) · 12.4 KB

数组中重复的数字

在一个长度是 n 的数组中,所有的数字都在 0 - n-1 范围内,数组中的某些数字是重复的,但是不知道几个数字是重复的,也不知道重复了几次,请找出数组中任意一个重复的数字 要求时间复杂度 O(n),空间复杂度 O(1) 这个题目的解题方法还是很多的

  1. 如果不限制空间复杂度和时间复杂度的话,就可以直接用循环将所有的元素循环一遍,然后声明一个新变量,将重复的元素 ++ 这样的话就可以找到重复的元素了,但是这样的话,空间复杂度就提高了。不符合要求,要让空间复杂度降低的话,就需要循环自己,不能有新的变量出现
  2. 先做排序,然后数组元素做相邻对比,这样的话也可以得到结果。但是不符合要求。
  3. 对数组做循环,将值是 i 的元素,调整到第 i 个位置上,当遇到第 i 个位置已经有相同的元素的时候,就得到对应的值了。

我们写第三种方案的代码

const data = [1, 2, 3, 4, 5, 2, 4, 3];

function findIndexFromArray(arr) {
  // 这部分可以直接省略,因为后面的循环,如果是没有找到的话,下面会直接返回 -1
  //   if (!arr.length) {
  //     return -1;
  //   }

  for (let i = 0; i < arr.length; i++) {
    // 将值是 i 的元素调整到第 i 个位置上
    const j = arr[i];
    while (i !== j) {
      // 当 i 元素和arr【i】的值不一致的时候,
      // 这个算法的核心是这样的,找到值是 j 的元素,在 arr[j] 这个位置上,有没有已经存在的值
      console.log("swap while before return j === arr[j]", { i, j });
      if (j === arr[j]) {
        console.log("j === arr[j]", { i, j }, arr[j]);
        return j;
      }

      // 这里就需要交换 arr[i] 位置 和 arr[j] 位置的值
      console.log("swap while after return j === arr[j]", { i, j });
      swap(arr, i, j);
    }
    console.log("swap  out of while return j === arr[j]", { i, j });
    swap(arr, i, j);
  }
  console.log("out of for loop");
  return -1;
}

function swap(arr, i, j) {
  const temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
}

findIndexFromArray(data);

第一个只出现一次的字符位置

在一个字符串中找到第一个只出现一次的字符。

// 第一种方案,利用 weak Map 做哈希,然后循环找到 值是1 的第一个就可以

function findCharactor(str) {
  // 这里不能使用 weak map ,因为 weak map 只接受对象引用做为 key ,
  const charMap = new Map();
  for (let i = 0; i < str.length; i++) {
    if (charMap.has(str.charAt(i))) {
      charMap.set(str.charAt(i), false);
    } else {
      charMap.set(str.charAt(i), true);
    }
  }

  for (const [key, value] of charMap.entries()) {
    if (value) {
      return key;
    }
  }

  return -1;
}

二维数组中的查找

给定一个二维数组,每一行是从左到右递增,每一列是从上到下递增,现在给定一个数,判断在不在这个二维数组中。

思路: 一个二维数组,从左到右递增,从上到下也是递增,说明这个二维数组的较大值是在最右边,最大值是最后的那个值。现在给了一个值,那么就从第一行的最右边做对比,如果是大,那么从第二行开始,以此类推,当找到行数以后,再从这一样开始做对比,也是从右开始,一个一个的往左对比,直到找到那个值为止,如果找不到,说明就没有这个值,返回 -1

/**
 * @description
 * @author lipc
 * @date 24/06/2021
 * @param {*} arr
 * @param {*} target
 * @return {*} 思路:二维数组的左边的小,右边的大,在查找的时候,先和第一行的最后一个值比较,
 * 看大小情况,如果大于这个值,那么就往第二行的最后一个值比较,他如果小于第二行的最后一个值,说明这个
 * 目标值可能在第二行的左边,或者是第三行左侧,然后对比第二行倒数第二个值,如果这个值还是大于目标值
 * 继续比较第三个值,如果第三个值小于目标值,说明是在第三行的第三要继续找,因为这个二维数组的行和列
 * 都是递增的。
[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]
 */
function findTargetNumber(arr, target) {
  // 首先判断 arr 的状态,是不是符合一个二维数组
  if (!arr || !arr.length || arr[0].lenght) {
    return -1;
  }

  // 这里就是确定这个是二维数组,满足我们的要求的。
  // 按照一个从左到右依次递增的状态,那么首先我们要做的是找到总共有多少行,
  const row = arr.length;
  const col = arr[0].length;

  let indexRow = 0;
  let indexCol = col - 1;

  while (indexRow < row - 1 && indexCol >= 0) {
    if (arr[indexRow][indexCol] === target) {
      return true;
    } else if (arr[indexRow][indexCol] < target) {
      indexRow++;
    } else {
      indexCol--;
    }
  }

  console.log({ indexRow, indexCol });

  return false;
}

重建二叉树

根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

二叉树的遍历分三种情况,前序遍历,中序遍历,后序遍历。

  • 前序遍历:中 -> 左 -> 右
  • 中序遍历:左 -> 中 -> 右
  • 后续遍历:右 -> 中 -> 左
// 缓存中序遍历中数组每个值对应的索引

const mapMidIndex = new WeakMap()

function reconclier

斐波那契数列

斐波那契数列又称作黄金分割数列,指的是这样的一组数列, 0 1 1 2 3 5 8 13 21 。。。

f(0) = 0; f(1) = 1; f(2) = 1;f(n) = f(n-1) + f(n-2) n>=2 现在要求,输入 n,找到 f(n) = ? n < 39;

  // 斐波那契的算法很多,其中最简单粗暴的就是用递归去做 ,但是递归有个问题,就是很容易爆栈
  // 现在算法的核心思想是 f(n) = f(n-1) + f(n-2) n>2
  function recursion(n){
    if(n < 2) {
        return n;
    }
    return recursion(n -1) + recursion(n -2)
  }
  // 这个解法,有个问题,就是很容易爆栈,因为我们的函数在调用的时候,会形成一个调用栈,每一个栈里面都会保存函数调用时候的变量等等
  // 当调用的栈很深的时候,就会出现爆栈的情况.这时候需要做优化,一般处理的情况是使用尾递归,所谓的尾递归,就是最后递归的那一步
  // 调用的是一个函数,不是返回值,那么这时候,就出现我不管调用栈是多深,最后那一个调用是一个函数,函数在执行完以后,就会被出栈
  // 这样就不会有变量被保存了、现在我们优化一下上面的代码
  function recursionBetter(n,ac1=1,ac2=1){
    if(n < 2) {
        return ac2;
    }
    return recursionBetter(n-1,ac2,ac1+ac2)
  }

跳台阶

一个楼梯有 n 阶,现在能一步两步上去,那么总共有多少种方法可以上去 这是一个动态规划的问题,这个问题的核心思想是 f(n) = f(n-1) + f(n-2) 和上面提到的 斐波那契 一样,可以用递归解决,我们这里采用另外的一种方案。

function jump2 (n,step1 = 2,step2 = 1) {
  if(n <=2) return step2;
  return jump2(n-1,step1,step1+step2)
}

function jump3(n){
  if(n === 1) return 1;
  if(n === 2) return 2;
  return jump3(n-1) + jump3(n-2);
}

function jump4(n) {
  if(n <= 2) return n;
  let step1 = 1;
  let step2 = 2;
  let res = 0;
  for(let i =2;i<n;i++){
    res = step1 + step2;
    // 这一步的理解要这样,上面的执行完以后,这时候 res = 1 + 2 = 3,那么就要用 res 的值作为新的 step2 的值,那么以前的 setp2 的值变成 step1 的值
    // 这样的话,就成了一个循环
    step1 = step2;
    step2 = res;
  }
  return res;
}

跳楼梯 变态版

描述:既可以一次跳一格,也可以一次跳N个,那么最多是多少种方法

// 用动态规划做
function jump5(n) {

  const arr = new Array(n).fill(1);


  for(let i = 1; i < n; i++) {

    for(let j = 0; j < i; j++) {
      arr[i] += arr[j]
    }
  }
  return arr[n-1]
}


function jump6(n) {
  return Math.pow(2,n-1)
}

连续子数组的最大和

{6, -3, -2, 7, -15, 1, 2, 2},连续子数组的最大和为多少,并输出这个连续的子数。这里的连续很重要,指的是从零开始,一直加和, 0 + 1 ? 0 + 1 + 2 ? 0 + 1 + 2 + 3 ?就这样一直加和到全部都没有值为止。

function findMaxSumSubArray(arr){
  if(!arr || !arr.length) return 0;
  // 按照上面的说法,这样的话,我们就需要有这样的一个变量去存储,每次累加的值
  const dp = [arr[0]];

  for(let i = 1; i < arr.length; i++) {
      dp[i] = dp[i-1] + arr[i]
  }
  return dp;
}

function findMaxSumSubArray2 (arr) {
  if(!arr || !arr.length) return 0;

  let tempMax = 0;
  let realMax = 0;

  for (let i = 0;i < arr.length; i++) {
    // f n = f n-1 + f n-2
     tempMax = tempMax + arr[i];

     if(tempMax > realMax) {
        realMax =  tempMax
     }else if(tempMax < 0){
       tempMax = 0;
     }
  }

  return realMax;
}

字符串中连续重复的字符

abbcccddddd -> 字符中连续重复的字符,如:ddddd

这个算法是找到连续重复的字符,那么就要求是在循环的时候,出现 i 元素和已经暂存的最长的不一致的时候,将i做调换

function findRepeatString (str = '') {
  let temp = '';
  let max = '';
  for (let i = 0; i < str.length; i++) {
    // 将字符进行拼接,这时候的 temp 可能是我们要的值
      if(temp.endsWith(str.charAt(i)) ) {
        temp = temp + str.charAt(i);
        if(max.length < temp.length) {
          max = temp;
        }
      }else {
        temp =  str.charAt(i);
      }
  }

  return max;
}

字符串中最长的不重复的子串

比如有个字符串 'acvbdcvvcxxs' 那么这时候最长的不重复的子串就是 acvb

function findMaxLengthSubString (str = '') {
  let max = '';
  let temp = '';
  for (let i = 0; i < str.length; i++){
    if(!temp.includes(str.charAt(i))) {
      temp = temp + str.charAt(i);
      if(temp.length > max.length) {
        max = temp;
      }
    }else {
      temp = str.charAt(i);
    }
  }
  return max;
}

丑数

我们把可以分解成 1 2 3 5 相乘的数字称之为丑数,比如 2 * 1 = 2 ,2 * 2 = 4 ,2 * 3 = 6 等,求解,当 n 的时候,对应的丑数是多少

function uglyNumber(num) {

    // 当小于等于 6 的时候,输入的值就是返回的值。
    if (num <= 6) {
        return num;
    }

    const dp = [1];
    let i2 = 0, i3 = 0, i5 = 0, nextUglyIndex = 1;

    while (nextUglyIndex < num) {
        const min = Math.min(dp[i2] * 2, Math.min(dp[i3] * 3, dp[i5] * 5));
        dp[nextUglyIndex] = min;

        if (dp[i2] * 2 === min) {
            i2++;
        }
        if (dp[i3] * 3 === min) {
            i3++;
        }
        if (dp[i5] * 5 === min) {
            i5++;
        }

        nextUglyIndex++;
    }

    return dp;
}

实现一个 MemoryCache 字节跳动

MemoryCache 实现存储数据,清除数据,当数据满了以后自动清除数据。

// 字节的面试中,没有提及是要用 使用次数去作为权重,删除数据,我这里想了一下,没有比使用次数作为权重更好的方案了。
// 使用次数多,就意味着优先级高,没有使用的就可以自动去删除。
class MemoryCache {
  constructor (max = 10) {
    this.data = new Map();
    this.usedCount = new Map();
    this.max = max;
  }

  set(key,value){
    const keyUseCount = this.usedCount.get(key)
    const size = this.usedCount.size;
    if(!keyUseCount) {
      this.usedCount.set(key,0)
    }

    if(size === 10) {
      // 删除使用次数低的
      let initValue = 0;
      const valueList = [];
      for (const [key,value] of this.usedCount.entries()) {
          valueList.push(value);
      }
     const useLess =  valueList.sort()[0];
       for (const [key,value] of this.usedCount.entries()) {
          if(value === useLess) {
            this.clearByKey(key);
          }
      }
    }else {
      this.data.set(key,value)
    }
  }

  get(key){
    let keyUseCount = this.usedCount.get(key)
    this.usedCount.set(key, keyUseCount++)
    return this.data.get(key)
  }

  clearByKey(key){
    this.data.delete(key)
    this.usedCount.delete(key)

  }

  clearAll (){
    this.data.clear();
    this.usedCount.clear()
  }
}