美文网首页
算法集合

算法集合

作者: PharkiLL | 来源:发表于2020-07-13 11:08 被阅读0次

1查找表问题

两个数组的交集 II-350

给定两个数组,编写一个函数来计算它们的交集。

示例 1:

输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]
示例 2:

输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]

为两个数组分别建立 map,用来存储 num -> count 的键值对,统计每个数字出现的数量。
然后对其中一个 map 进行遍历,查看这个数字在两个数组中分别出现的数量,取出现的最小的那个数量(比如数组 1 中出现了 1 次,数组 2 中出现了 2 次,那么交集应该取 1 次),push 到结果数组中即可。

/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number[]}
 */
let intersect = function (nums1, nums2) {
  let map1 = makeCountMap(nums1)
  let map2 = makeCountMap(nums2)
  let res = []
  for (let num of map1.keys()) {
    const count1 = map1.get(num)
    const count2 = map2.get(num)

    if (count2) {
      const pushCount = Math.min(count1, count2)
      for (let i = 0; i < pushCount; i++) {
        res.push(num)
      }
    }
  }
  return res
}

function makeCountMap(nums) {
  let map = new Map()
  for (let i = 0; i < nums.length; i++) {
    let num = nums[i]
    let count = map.get(num)
    if (count) {
      map.set(num, count + 1)
    } else {
      map.set(num, 1)
    }
  }
  return map
}

2 双指针问题

最接近的三数之和-16

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

示例:

输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

先按照升序排序,然后分别从左往右依次选择一个基础点 i(0 <= i <= nums.length - 3),在基础点的右侧用双指针去不断的找最小的差值。
假设基础点是 i,初始化的时候,双指针分别是:

left:i + 1,基础点右边一位。
right: nums.length - 1 数组最后一位。

然后求此时的和,如果和大于 target,那么可以把右指针左移一位,去试试更小一点的值,反之则把左指针右移。
在这个过程中,不断更新全局的最小差值 min,和此时记录下来的和 res。
最后返回 res 即可。

/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
let threeSumClosest = function (nums, target) {
 let n = nums.length
 if (n === 3) {
   return getSum(nums)
 }
 // 先升序排序 此为解题的前置条件
 nums.sort((a, b) => a - b)

 let min = Infinity // 和 target 的最小差
 let res

 // 从左往右依次尝试定一个基础指针 右边至少再保留两位 否则无法凑成3个
 for (let i = 0; i <= nums.length - 3; i++) {
   let basic = nums[i]
   let left = i + 1 // 左指针先从 i 右侧的第一位开始尝试
   let right = n - 1 // 右指针先从数组最后一项开始尝试

   while (left < right) {
     let sum = basic + nums[left] + nums[right] // 三数求和
     // 更新最小差
     let diff = Math.abs(sum - target)
     if (diff < min) {
       min = diff
       res = sum
     }
     if (sum < target) {
       // 求出的和如果小于目标值的话 可以尝试把左指针右移 扩大值
       left++
     } else if (sum > target) {
       // 反之则右指针左移
       right--
     } else {
       // 相等的话 差就为0 一定是答案
       return sum
     }
   }
 }

 return res
}

function getSum(nums) {
 return nums.reduce((total, cur) => total + cur, 0)
}

3滑动窗口问题

无重复字符的最长子串-3

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

这题是比较典型的滑动窗口问题,定义一个左边界 left 和一个右边界 right,形成一个窗口,并且在这个窗口中保证不出现重复的字符串。
这需要用到一个新的变量 freqMap,用来记录窗口中的字母出现的频率数。在此基础上,先尝试取窗口的右边界再右边一个位置的值,也就是 str[right + 1],然后拿这个值去 freqMap 中查找:

这个值没有出现过,那就直接把 right ++,扩大窗口右边界。
如果这个值出现过,那么把 left ++,缩进左边界,并且记得把 str[left] 位置的值在 freqMap 中减掉。

循环条件是 left < str.length,允许左边界一直滑动到字符串的右界。

/**
* @param {string} s
* @return {number}
*/
let lengthOfLongestSubstring = function (str) {
 let n = str.length
 // 滑动窗口为s[left...right]
 let left = 0
 let right = -1
 let freqMap = {} // 记录当前子串中下标对应的出现频率
 let max = 0 // 找到的满足条件子串的最长长度

 while (left < n) {
   let nextLetter = str[right + 1]
   if (!freqMap[nextLetter] && nextLetter !== undefined) {
     freqMap[nextLetter] = 1
     right++
   } else {
     freqMap[str[left]] = 0
     left++
   }
   max = Math.max(max, right - left + 1)
 }

 return max
}

4链表问题

两两交换链表中的节点-24

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例:

给定 1->2->3->4, 你应该返回 2->1->4->3.

这题本意比较简单,1 -> 2 -> 3 -> 4 的情况下可以定义一个递归的辅助函数 helper,这个辅助函数对于节点和它的下一个节点进行交换,比如 helper(1) 处理 1 -> 2,并且把交换变成 2 -> 1 的尾节点 1的next继续指向 helper(3)也就是交换后的 4 -> 3。
边界情况在于,如果顺利的作了两两交换,那么交换后我们的函数返回出去的是 交换后的头部节点,但是如果是奇数剩余项的情况下,没办法做交换,那就需要直接返回 原本的头部节点。这个在 helper函数和主函数中都有体现。

let swapPairs = function (head) {
  if (!head) return null
  let helper = function (node) {
    let tempNext = node.next
    if (tempNext) {
      let tempNextNext = node.next.next
      node.next.next = node
      if (tempNextNext) {
        node.next = helper(tempNextNext)
      } else {
        node.next = null
      }
    }
    return tempNext || node
  }

  let res = helper(head)

  return res || head
}

5深度优先遍历问题

二叉树的所有路径-257

给定一个二叉树,返回所有从根节点到叶子节点的路径。

说明: 叶子节点是指没有子节点的节点。

示例:

输入:

   1
 /   \
2     3
 \
  5

输出: ["1->2->5", "1->3"]

解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3

用当前节点的值去拼接左右子树递归调用当前函数获得的所有路径。

也就是根节点拼上以左子树为根节点得到的路径,加上根节点拼上以右子树为根节点得到的所有路径。

直到叶子节点,仅仅返回包含当前节点的值的数组。

let binaryTreePaths = function (root) {
  let res = []
  if (!root) {
    return res
  }

  if (!root.left && !root.right) {
    return [`${root.val}`]
  }

  let leftPaths = binaryTreePaths(root.left)
  let rightPaths = binaryTreePaths(root.right)

  leftPaths.forEach((leftPath) => {
    res.push(`${root.val}->${leftPath}`)
  })
  rightPaths.forEach((rightPath) => {
    res.push(`${root.val}->${rightPath}`)
  })

  return res
}

6广度优先遍历(BFS)问题

您需要在二叉树的每一行中找到最大的值。

输入:

          1
         / \
        3   2
       / \   \
      5   3   9

输出: [1, 3, 9]


这是一道典型的 BFS 题目,BFS 的套路其实就是维护一个 queue 队列,在读取子节点的时候同时把发现的孙子节点 push 到队列中,但是先不处理,等到这一轮队列中的子节点处理完成以后,下一轮再继续处理的就是孙子节点了,这就实现了层序遍历,也就是一层层的去处理。
但是这里有一个问题卡住我了一会,就是如何知道当前处理的节点是哪个层级的,在最开始的时候我尝试写了一下二叉树求某个 index 所在层级的公式,但是发现这种公式只能处理「平衡二叉树」。
后面看题解发现他们都没有专门维护层级,再仔细一看才明白层级的思路:
其实就是在每一轮 while 循环里,再开一个 for 循环,这个 for 循环的终点是「提前缓存好的 length 快照」,也就是进入这轮 while 循环时,queue 的长度。其实这个长度就恰好代表了「一个层级的长度」。
缓存后,for 循环里可以安全的把子节点 push 到数组里而不影响缓存的当前层级长度。
另外有一个小 tips,在 for 循环处理完成后,应该要把 queue 的长度截取掉上述的缓存长度。一开始我使用的是 queue.splice(0, len),结果速度只击败了 33%的人。后面换成 for 循环中去一个一个shift来截取,速度就击败了 77%的人。

/**
 * @param {TreeNode} root
 * @return {number[]}
 */
let largestValues = function (root) {
  if (!root) return []
  let queue = [root]
  let maximums = []

  while (queue.length) {
    let max = Number.MIN_SAFE_INTEGER
    // 这里需要先缓存length 这个length代表当前层级的所有节点
    // 在循环开始后 会push新的节点 length就不稳定了
    let len = queue.length
    for (let i = 0; i < len; i++) {
      let node = queue[i]
      max = Math.max(node.val, max)

      if (node.left) {
        queue.push(node.left)
      }
      if (node.right) {
        queue.push(node.right)
      }
    }

    // 本「层级」处理完毕,截取掉。
    for (let i = 0; i < len; i++) {
      queue.shift()
    }

    // 这个for循环结束后 代表当前层级的节点全部处理完毕
    // 直接把计算出来的最大值push到数组里即可。
    maximums.push(max)
  }

  return maximums
}

作者:晨曦时梦见兮
链接:https://juejin.im/post/5f05087cf265da22d466f60f
来源:掘金

相关文章

  • 常用的集合

    集合的由来 集合框架接口特性 集合中的算法Interator (迭代器) 和Colletions 算法类,也ut...

  • 算法集合

    1查找表问题 两个数组的交集 II-350 给定两个数组,编写一个函数来计算它们的交集。 为两个数组分别建立 ma...

  • 算法 - 集合

    集合 一种无序且唯一的数据结构 ES6中有集合,名为Set 集合的常用操作:去重、判断某元素是否在集合中、求交集 ...

  • java.day10

    集合的内容 集合框架 接口 实现 算法 collection 是所有集合接口的根接口,通用的集合操作 set 是不...

  • 集合竞价,连续竞价

    集合竞价成交规则,了解一下? 集合竞价与连续竞价集合竞价算法集合,连续竞价交易委托账本 order book 集合...

  • 计算机科学导论

    第八章 算法 算法:算法是一组明确步骤的有序集合,它产生结果并在有限的时间内终止。 要点有四: 1 有序集合 2 ...

  • 哈希算法

    哈希(Hash)算法就是单向散列算法,它把某个较大的集合P映射到另一个较小的集合Q中,假如这个算法叫H,那么就有Q...

  • Java集合框架 -- 03 hash算法在集合中的应用及分析

    对于HashSet及其子类而言,它们采用hash算法来决定集合中元素的存储位置,并通过hash算法来控制集合的大小...

  • 数据结构与算法 全家桶

    数据结构:数据集合的存储结构。算法:操作数据的方法集合。相互关系:数据结构是为算法服务的,算法要作用在特定的数据结...

  • 200116 基本数据结构

    0.算法操作中的集合是动态的,支持算法操作的动态集合被称为字典(dictionary)。1.用数组储存队列也可以让...

网友评论

      本文标题:算法集合

      本文链接:https://www.haomeiwen.com/subject/xvxacktx.html