牛客面经每日一总结(算法部分)

84 阅读3分钟

快速排序

原理

  • 通过递归实现。每次都设置标定点之前的所有元素和之后的所有元素第一个元素的正确位置。
  • 假设[l, r]区间的第一个元素为标定元素。
  • 让其前面的元素都小于标定元素,后面的元素都大于标定元素。这就找到了标定元素应该放置的位置。
  • j = l
  • i = l + 1
  • 当当前i位置的元素小于判断元素,那么将让j++, 然后后交换i 和 j位置的元素。
  • 最后,当i越界后,即交换 j 和 l 位置的元素。
    function quickSort(arr) {
      sort(arr, 0, arr.length - 1)
    }

    function sort(arr, l, r) {
      if (l >= r) return

      let p = partition(arr, l, r)
      // partition判断元素之前的所有元素
      sort(arr, l, p - 1)
      // partition判断元素之后的所有元素
      sort(arr, p + 1, r)
    }

    // 假设[l, r]区间的第一个元素为判断元素。让其前面的元素都小于当前元素,后面的元素都大于当前元素
    // j = l
    // i = l + 1

    function partition(arr, l, r) {
      let j = l
      for (let i = l + 1; i <= r; i++) {
        // 当当前i位置的元素小于判断元素,那么将让j++, 然后后交换i 和 j位置的元素。
        if (arr[l] > arr[i]) {
          j++
          ;[arr[j], arr[i]] = [arr[i], arr[j]]
        }
      }
      // 最后,当i越界后,即交换 j 和 l 位置的元素。
      ;[arr[j], arr[l]] = [arr[l], arr[j]]
      // 返回当前的j的位置
      return j
    }

    const arr = [4, 32, 2, 9, 5, 90]
    quickSort(arr)
    console.log(arr) // [ 2, 4, 5, 9, 32, 90 ]

LeetCode 49. 字母异位词分组

这种题目,就是两种方式吧。

  • 排序(作为hash的键名) + hash表
  • 通过创建一个数组(长度26)存放a - z字母的个数。刚好对应到指定的位置。
    /**
     * @param {string[]} strs
     * @return {string[][]}
     */
    var groupAnagrams = function(strs) {
        // 先对字符串进行排序
        const map = new Map()
        for(let i of strs) {
            // 先对字符串进行排序
            const strArr = i.split("").sort();
            const key = strArr.join();
            if(map.has(key)) {
                map.set(key, [...map.get(key), i])
            }else {
                map.set(key, [i])
            }
        }
        // 获取结果,然后返回
        const res = []
        for(let [key, value] of map) {
            res.push(value)
        }
        return res
    }; 
    
    // 通过创建长度为26的数组。
    var groupAnagrams = function(strs) {
        const map = new Object();
        for (let s of strs) {
            const count = new Array(26).fill(0);
            for (let c of s) {
                count[c.charCodeAt() - 'a'.charCodeAt()]++;
            }
            // 这里需要注意一下,count数组会调用toString()转为字符串作为键名
            map[count] ? map[count].push(s) : map[count] = [s];
        }
        return Object.values(map);

    };

数组乱序

就是在循环中,每次都创建一个和数组长度一样的随机数r,然后交换len和r的位置元素。

    // 这种方式并不能做到很彻底的乱序
    function randomArr(arr) {
      return arr.sort(() => Math.random() - 0.5)
    }
    // 洗牌算法
    function randomArr(arr) {
      let len = arr.length
      while (len--) {
        // 创建一个和数组长度范围一样的随机数,然后交换二者的位置。
        const r = Math.floor(Math.random() * arr.length);
        ;[arr[r], arr[len]] = [arr[len], arr[r]]
      }
    }

LRU算法

  • 定义一个容器(map), 容器的最大缓存数。
  • 当缓存的容器满时,删除第一个元素,然后再加入元素。
  • 如果容器未满,我们先将其删除,然后再将其添加到最后。
    class LRU {
      constructor(capacity) {
        // 缓存的最大数量
        this.capacity = capacity
        // 缓存容器
        this.cache = new Map()
      }

      // 获取缓存的数据
      get(key) {
        // 先查找看其是否有
        if (this.cache.has(key)) {
          // 取出这个数据
          const cur = this.cache.get(key)
          // 删除这个数据
          this.cache.delete(key)
          // 将当前数据插入到最后。因为我们最后超出容量是删除最久未使用的元素
          this.cache.set(key, cur)
          return cur
        }
        return -1
      }

      // 设置缓存数据
      put(key, value) {
        if (this.cache.has(key)) {
          // 先删除,再将其添加到最后
          this.cache.delete(key)
        } else if (this.capacity < this.cache.size) {
          // 删除cache中的第一个元素
          this.cache.delete(this.cache.keys().next().value)
        }
        // 然后添加
        this.cache.set(key, value)
      }

      toString() {
        console.table(this.cache)
      }
    }

LeetCode 77. 组合

1,2,3举例子。 拿出一个1,放入path中,然后当path中的元素收集到等于k时,将后一个元素弹出,然后继续匹配下一组元素。

    var combine = function(n, k) {
        let res = [];
        // 存放每次获取的结果(单个集合)
        let path = [];
        // n 表示集合的元素个数
        // k 表示收集由几个元素组成的集合。
        function backTracking(n, k, startIndex) {
            if(path.length == k) {
                res.push([...path])
                return;
            }
            for(let i = startIndex; i <= n; i++) { // 这里并不是去从0开始
                path.push(i);
                // 递归。注意对于集合来说,下次开始的元素是上次元素值加一
                backTracking(n, k, i + 1);
                // 回溯,当遍历到收集结果的时候,将后一个元素弹出,然后在加入其它元素组成结果。
                path.pop();
            }
        }
        backTracking(n, k, 1)
        return res;
    };

移除数组元素(快慢指针)LeetCode 27. 移除元素

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置

这样的方式并没有真正的删除元素,他只是利用慢指针来模拟重新开辟一个数组空间。即把返回元素都放在前面。

暴力解法:

  • 它主要的注意点就是需要改变size的大小。
/**
 * @param {number[]} nums
 * @param {number} val
 * @return {number}
 */
var removeElement = function(nums, val) {
    // 暴力解法
    let size = nums.length;
    for(let i = 0; i < size; i++) {
        if(nums[i] === val) {
           for(let j = i; j + 1 < size; j++) {
                nums[j] = nums[j + 1];
           }
            i = i - 1;
            size--; // 因为我们将元素前移,所以让数组长度减一。
        }
    }
    return size;
  
  
  
    // 这里我们并不是真的删除元素
    // 快指针:查找每一个元素
    // 慢指针:确定数组中的新元素。不会开辟空间
​
    let slowIndex = 0;
    for(let fastIndex = 0; fastIndex < nums.length; fastIndex++) {
        if(nums[fastIndex] != val) {
            nums[slowIndex] = nums[fastIndex];
            // 这个就相当于新数组的长度。
            slowIndex++;
        }
    }
    return slowIndex;
};

LeetCode 209. 长度最小的子数组(滑动窗口)

滑动窗口:表示查找的区间。

sum总和表示这个区间中所有元素的和。

  • 判断sum >= target。停止尾指针的偏移,移动头指针的偏移。然后再次计算sum值。内部是while循环。(因为他是一个持续的过程,主要是判断sum 是不是有可能小于target)
  • 滑动窗口的长度 r - l + 1。
    /**
     * @param {number} target
     * @param {number[]} nums
     * @return {number}
     */
    var minSubArrayLen = function(target, nums) {
        // 滑动窗口
        let slowIndex = 0;
        let len = nums.length + 1; // 这里最好不要写0
        let sum = 0;
        for(let fastIndex = 0; fastIndex < nums.length; fastIndex++) {
            sum += nums[fastIndex]; // 计算总和 [slow, fast]区间中,直到sum >= target
            while(sum >= target) { // 这里使用while是慢指针需要持续的向后移动。
                // 计算长度
                // len = len < fastIndex - slowIndex + 1 ? len : fastIndex - slowIndex + 1
                len = Math.min(len, fastIndex - slowIndex + 1);
                sum -= nums[slowIndex]; // 在slowIndex++的过程中,持续计算sum值,直到sum < target
                slowIndex++;
            }
        }
        return len > nums.length ? 0 : len;
    };

其实对于算法的学习,我是特别推荐跟着carl(卡哥)的代码随想录学习的。

还有就是他在b站录的一些视频。个人主页