刷题记录

121 阅读10分钟

分类:leetcode-cn.com/leetbook/re…

for(let i in array),i返回的是数组的下标

源码

模拟实现 new 操作符

// 优化后 new 实现
function create() {
  // 1、获得构造函数,同时删除 arguments 中第一个参数
  Con = [].shift.call(arguments);
  // 2、创建一个空的对象并链接到原型,obj 可以访问构造函数原型中的属性
  let obj = Object.create(Con.prototype);
  // 3、绑定 this 实现继承,obj 可以访问到构造函数中的属性
  let ret = Con.apply(obj, arguments);
  // 4、优先返回构造函数返回的对象
  return ret instanceof Object ? ret : obj;
};

模拟实现 instanceOf

// instanceof 的内部实现 
function instance_of(L, R) {//L 表左表达式,R 表示右表达式,即L为变量,R为类型
// 取 R 的显示原型
var prototype = R.prototype
// 取 L 的隐式原型
L = L.__proto__
// 判断对象(L)的类型是否严格等于类型(R)的显式原型
while (true) { 
 if (L === null) {
   return false
 }
   
 // 这里重点:当 prototype 严格等于 L 时,返回 true
 if (prototype === L) {
   return true
 } 
 
 L = L.__proto__
} 
}

数据结构

二叉树节点的定义

用一个函数表示

        // Definition for a binary tree node.
        function TreeNode(val, left, right) {
            this.val = (val === undefined ? 0 : val)
            this.left = (left === undefined ? null : left)
            this.right = (right === undefined ? null : right)
        }

单链表的定义

     // Definition for singly-linked list.
     function ListNode(val, next) {
         this.val = (val===undefined ? 0 : val)
         this.next = (next===undefined ? null : next)
     }

1

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

哈希表

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
    const map = new Map()
    for(let i=0;i<nums.length;i++) {
        const complement = target - nums[i]
        if(map.has(complement)) {
            return [map.get(complement),i]
        } else {
            map.set(nums[i],i)
        } 
    }
    return []
};

15

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

排序 + 双指针

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
    let ans = [];
    if(!nums) return ans;
    const len = nums.length;
    if(len < 3) return ans;
    nums.sort((a, b) => a - b); // 排序
    for (let i = 0; i < len ; i++) {
        if(nums[i] > 0) break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
        if(i > 0 && nums[i] == nums[i-1]) continue; // 去重
        let L = i+1;
        let R = len-1;
        while(L < R){
            const sum = nums[i] + nums[L] + nums[R];
            if(sum == 0){
                ans.push([nums[i],nums[L],nums[R]]);
                while (L<R && nums[L] == nums[L+1]) L++; // 去重
                while (L<R && nums[R] == nums[R-1]) R--; // 去重
                L++;
                R--;
            }
            else if (sum < 0) L++;
            else if (sum > 0) R--;
        }
    }        
    return ans;
};

146

请你设计并实现一个 LRUCache 类,要求函数 getput 必须以 O(1) 的平均时间复杂度运行。

为了实现高效的增删改查,使用哈希表和双向链表的数据结构

class DoubleNode {
  constructor(key, val) {
    this.key = key
    this.val = val
    this.prev = null
    this.next = null
  }
}
​
class LRUCache {
  constructor(max) {
    this.max = max
    this.map = new Map()
​
    this.head = null
    this.tail = null
  }
​
  get(key) {
    const node = this.map.get(key)
    if (!node) {
      return -1
    } else {
      const res = node.val
      this.remove(node)
      this.appendHead(node)
      return res
    }
  }
​
  put(key, value) {
    let node = this.map.get(key)
    // 有这个缓存
    if (node) {
      node.val = value
      // 新加入的 放在最前面
      this.remove(node)
      this.appendHead(node)
    } else {
      // 没有这个缓存
      node = new DoubleNode(key, value)
      // 如果超出容量了 删除最后一个 再放到头部
      if (this.map.size >= this.max) {
        this.map.delete(this.tail.key)
        this.remove(this.tail)
        this.appendHead(node)
        this.map.set(key, node)
      } else {
        // 未超出容量 就直接放到头部
        this.appendHead(node)
        this.map.set(key, node)
      }
    }
  }
​
  /**
   * 把头部指针的改变成新的node
   * @param {DoubleNode} node
   */
  appendHead(node) {
    if (this.head === null) {
      this.head = this.tail = node
    } else {
      node.next = this.head
      this.head.prev = node
      this.head = node
    }
  }
​
  /**
   * 删除某个节点
   * @param {DoubleNode} node
   */
  remove(node) {
    if (this.head === this.tail) {
      this.head = this.tail = null
    } else {
      // 删除头部
      if (this.head === node) {
        this.head = this.head.next
        node.next = null
      } else if (this.tail === node) {
        this.tail = this.tail.prev
        this.tail.next = null
        node.prev = null
      } else {
        node.prev.next = node.next
        node.next.prev = node.prev
        node.prev = node.next = null
      }
    }
  }
}

链表

21

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

var mergeTwoLists = function(l1, l2) {
    if(l1 === null){
        return l2;
    }
    if(l2 === null){
        return l1;
    }
    if(l1.val < l2.val){
        l1.next = mergeTwoLists(l1.next, l2);
        return l1;
    }else{
        l2.next = mergeTwoLists(l1, l2.next);
        return l2;
    }
};

141

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。

方法一:利用 JSON.stringify() 不能序列化含有循环引用的结构

var hasCycle = function (head) {
    try {
        JSON.stringify(head)
    } catch{
        return true
    }
    return false
};

方法二:遍历法

给遍历过的节点打记号,如果遍历过程中遇到有记号的说明已环

const hasCycle = function(head) {
  while (head) {
    if (head.tag) {
      return true;
    }
    head.tag = true;
    head = head.next;
  }
  return false;
};

方法三:快慢指针

var hasCycle = (head) => {
  let fast = head;
  let slow = head;
  while (fast) {                        
    if (fast.next == null) return false; 
    slow = slow.next;                 
    fast = fast.next.next;             
    if (slow == fast) return true;   
  }
  return false;                   
}

206

反转链表

方法一:迭代

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
const reverseList = function(head) {
    if(!head || !head.next) return head
    let prev = null, curr = head
    while(curr) {
        // 用于临时存储 curr 后继节点
        var next = curr.next
        // 反转 curr 的后继指针
        curr.next = prev
        // 变更prev、curr 
        // 待反转节点指向下一个节点 
        prev = curr
        curr = next
    }
    head = prev
    return head
};

方法二:递归

var reverseList = function(head) {
    if (head == null || head.next == null) return head
    const p = reverseList(head.next)
    head.next.next = head
    head.next = null
    return p
};

876

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var middleNode = function(head) {
    let slow=head,fast=head
    // 判断当前节点和当前节点的下一个节点
    while(fast && fast.next) {
        slow=slow.next
        fast=fast.next.next
    }
    return slow
};

19

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
    let pre= new ListNode(0)
    pre.next=head
    let slow=pre,fast=pre
    while(n--) {
        fast=fast.next
    }
    while(fast&&fast.next) {
        fast=fast.next
        slow=slow.next
    }
    slow.next=slow.next.next
    return pre.next
};

160

相交链表

方法一:标记法

const getIntersectionNode = function(headA, headB) {
    while(headA) {
        headA.flag = true
        headA = headA.next
    }
    while(headB) {
        if (headB.flag) return headB
        headB = headB.next
    }
    return null
};

方法二:双指针

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function(headA, headB) {
    if(headA===null || headB===null) {
        return null
    }
    let pA=headA,pB=headB
    while(pA!=pB) {
        pA = pA === null ? headB : pA.next
        pB = pB === null ? headA : pB.next
    }
    return pA
};

611

给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数。

/**
 * @param {number[]} nums
 * @return {number}
 */
const triangleNumber = function(nums) {
    if(!nums || nums.length < 3) return 0
    let count = 0
    // 排序
    nums.sort((a, b) => a - b) 
    for(let k = nums.length - 1; k > 1; k--){
        let i = 0, j = k - 1
        while(i < j){ 
            if(nums[i] + nums[j] > nums[k]){
                count += j - i
                j--
            } else {
                i++
            }
        }
    }       
    return count
}

2

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
const addTwoNumbers = function(l1, l2) {
    let carry = 0
    let root = new ListNode(0)
    let p = root
    while (l1 || l2) {
        let sum = 0
        if (l1) {
            sum += l1.val
            l1 = l1.next
        }
        if (l2) {
            sum += l2.val
            l2 = l2.next
        }
        sum += carry
        carry = Math.floor(sum / 10)
        p.next = new ListNode(sum % 10)
        p = p.next
    }
    if (carry === 1) {
        p.next = new ListNode(carry)
        p = p.next
    }
    return root.next
};

同步任务都在主线程(这里的主线程就是 JavaScript 引擎线程)上执行,会形成一个 调用栈

除了主线程外,还有一个任务队列(也称消息队列),用于管理异步任务的 事件回调 ,在 调用栈 的任务执行完毕之后,系统会检查任务队列,看是否有可以执行的异步任务。

基本类型是保存在栈内存中的简单数据段,而引用类型保存在堆内存中

回收堆空间

  • 标记: 标记堆空间中的活动对象(正在使用)与非活动对象(可回收)
  • 垃圾清理: 回收非活动对象所占用的内存空间
  • 内存整理: 当进行频繁的垃圾回收时,内存中可能存在大量不连续的内存碎片,当需要分配一个需要占用较大连续内存空间的对象时,可能存在内存不足的现象,所以,这时就需要整理这些内存碎片。

V8 采用增量 标记算法回收,把垃圾回收拆成一个个小任务,穿插在 JavaScript 中执行。

155

设计一个支持 pushpoptop 操作,并能在常数时间内检索到最小元素的栈。

var MinStack = function() {
    this.x_stack = [];
    this.min_stack = [Infinity];
};

MinStack.prototype.push = function(x) {
    this.x_stack.push(x);
    this.min_stack.push(Math.min(this.min_stack[this.min_stack.length - 1], x));
};

MinStack.prototype.pop = function() {
    this.x_stack.pop();
    this.min_stack.pop();
};

MinStack.prototype.top = function() {
    return this.x_stack[this.x_stack.length - 1];
};

MinStack.prototype.getMin = function() {
    return this.min_stack[this.min_stack.length - 1];
};

20

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function(s) {
   let map = {
        '{': '}',
        '(': ')',
        '[': ']'
    }
    let stack = []
    for(let i = 0; i < s.length ; i++) {
        if(map[s[i]]) {
            stack.push(s[i])
        } else if(s[i] !== map[stack.pop()]){
            return false
        }
    }
    return stack.length === 0
};

1047

给出由小写字母组成的字符串 S重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 S 上反复执行重复项删除操作,直到无法继续删除。

/**
 * @param {string} s
 * @return {string}
 */
var removeDuplicates = function(s) {
    let str = [s[0]]
    let num = 1
    for(let i=1;i<s.length;i++) {
        if(s[i]===str[num-1]) {
            num--
        } else {
            str[num]=s[i]
            num++
        }
    }
    let res = ''
    for(let i=0;i<num;i++) {
        res += str[i]
    }
    return res
};

优化:

/**
 * @param {string} s
 * @return {string}
 */
const removeDuplicates = function(S) {
    let stack = []
    for(c of S) {
        let prev = stack.pop()
        if(prev !== c) {
            stack.push(prev)
            stack.push(c)
        }
    }
    return stack.join('')
};

1209

给你一个字符串 s,「k 倍重复项删除操作」将会从 s 中选择 k 个相邻且相等的字母,并删除它们,使被删去的字符串的左侧和右侧连在一起。

你需要对 s 重复进行无限次这样的删除操作,直到无法继续为止。

/**
 * @param {string} s
 * @param {number} k
 * @return {string}
 */
var removeDuplicates = function (s, k) {
    let stack = [] //字母栈
    let countStack = [] //数字栈
    let i = 0
    while(i < s.length) {
        if(stack[stack.length-1] == s[i]) {
            stack.push(s[i])
            countStack[countStack.length-1] +=   1
            if(countStack[countStack.length-1] == k) {
                for(let j= 0; j < k;j++) { //字母栈出栈
                    stack.pop()
                }
                countStack.pop() //数字栈出栈
            }
        } else {
            stack.push(s[i])
            countStack.push(1)
        }
        i++
    }
    return stack.join('')
};

删除字符串中出现次数 >= 2 次的相邻字符

/**
 * 删除字符串中出现次数 >= 2 次的相邻字符
 * @param {string}s
 */
function removeDuplicate(s) {
  const stack = [] // Space: O(n)
  let top
  let next
  let i = 0
  while (i < s.length) { // Time: O(n)
    top = stack[stack.length - 1]
    next = s[i]
    if (next === top) {
      // 字符串中出现了相邻字符
      // 1. 移除栈顶字符
      // 2. 移动指针, 指向下一个不同的字符
      stack.pop()
      while (s[i] === top) i += 1
    } else {
      stack.push(next)
      i += 1
    }
  }

  return stack.join('')  // Time: O(n)
}

队列

常用的有双端队列、滑动窗口

151

给你一个字符串 s ,颠倒字符串中 单词 的顺序。

注意: 输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。

方法一:正则表达式

var reverseWords = function(s) {
    return s.trim().split(/\s+/).reverse().join(' ');
};

方法二:双端队列

/**
 * @param {string} s
 * @return {string}
 */
const reverseWords = function(s) {
    let left = 0
    let right = s.length - 1
    let queue = []
    let word = ''
    while (s.charAt(left) === ' ') left ++
    while (s.charAt(right) === ' ') right --
    while (left <= right) {
        let char = s.charAt(left)
        if (char === ' ' && word) {
            queue.unshift(word)
            word = ''
        } else if (char !== ' '){
            word += char
        }
        left++
    }
    queue.unshift(word)
    return queue.join(' ')
};

3

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

解法一:

遍历字符串,判断字符是否在滑动窗口数组里

不在则 push 进数组 在则删除滑动窗口数组里相同字符及相同字符前的字符,然后将当前字符 push 进数组 然后将 max 更新为当前最长子串的长度

遍历完,返回 max 即可

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    let arr = [], max = 0
    for(let i = 0; i < s.length; i++) {
        let index = arr.indexOf(s[i])
        if(index !== -1) {
            arr.splice(0, index+1);
        }
        arr.push(s.charAt(i))
        max = Math.max(arr.length, max) 
    }
    return max
};

解法二:

使用 map 来存储当前已经遍历过的字符,key 为字符,value 为下标

使用 i 来标记无重复子串开始下标,j 为当前遍历字符下标

遍历字符串,判断当前字符是否已经在 map 中存在,存在则更新无重复子串开始下标 i 为相同字符的下一位置,此时从 i 到 j 为最新的无重复子串,更新 max ,将当前字符与下标放入 map 中

最后,返回 max 即可

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    let map = new Map(), max = 0
    for(let i = 0, j = 0; j < s.length; j++) {
        if(map.has(s[j])) {
            i = Math.max(map.get(s[j]) + 1, i)
        }
        max = Math.max(max, j - i + 1)
        map.set(s[j], j)
    }
    return max
};

剑9

用两个栈实现一个队列

const CQueue = function() {
    this.stack1 = []
    this.stack2 = []
};
CQueue.prototype.appendTail = function(value) {
    this.stack1.push(value)
};
CQueue.prototype.deleteHead = function() {
    if(this.stack2.length) {
        return this.stack2.pop()
    }
    if(!this.stack1.length) return -1
    while(this.stack1.length) {
        this.stack2.push(this.stack1.pop())
    }
    return this.stack2.pop()
};

209

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组,并返回其长度。如果不存在符合条件的子数组,返回 0 。

/**
 * @param {number} target
 * @param {number[]} nums
 * @return {number}
 */
var minSubArrayLen = function(target, nums) {
    let l=0,r=0,sum=0,res=Number.MAX_SAFE_INTEGER
    while(r<nums.length) {   // 主旋律是扩张,找可行解
        sum += nums[r]
        while(sum>=target) { // 间歇性收缩,优化可行解
            res = Math.min(res,r-l+1)
            sum -= nums[l]
            l++
        }
        r++
    }
    return res === Number.MAX_SAFE_INTEGER ? 0 : res // 从未找到可行解,返回0
};

239

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

方法一:暴力解法(快要超时)

用Number.MIN_SAFE_INTEGER表示最小值

性能优化一:使用Math.max代替if判断最大值

性能优化二:将函数分开写

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var maxSlidingWindow = function(nums, k) {
    let l=0, r=k-1
    res = []
​
    while(r<=nums.length-1) {
        res.push(findMax(nums,l,r))
        l++
        r++    
    }
​
    return res
};
function findMax(nums,l,r) {
    let max = Number.MIN_SAFE_INTEGER
    for(let i=l;i<=r;i++) {
        max = Math.max(max,nums[i])
    }
    return max
}

方法二:单调队列

解题思路: 使用一个双端队列存储窗口中值的 索引 ,并且保证双端队列中第一个元素永远是最大值,那么只需要遍历一次 nums,就可以取到每次移动时的最大值。

  • 比较当前元素 i 和双端队列第一个元素(索引值),相差 >= k 时队首出列
  • 依次比较双端队列的队尾与当前元素 i 对应的值,队尾元素值较小时出列,直至不小于当前元素 i 的值时,或者队列为空,这是为了保证当队头出队时,新的队头依旧是最大值
  • 当前元素入队
  • 从第 K 次遍历开始,依次把最大值(双端队列的队头)添加到结果 result
const maxSlidingWindow = function (nums, k) {
    const deque = []
    const result = []
    for (let i = 0; i < nums.length; i++) {
        // 把滑动窗口之外的踢出
        if (i - deque[0] >= k) {
            deque.shift()
        }
        while (nums[deque[deque.length - 1]] <= nums[i]) {
            deque.pop()
        }
        deque.push(i)
        if (i >= k - 1) {
            result.push(nums[deque[0]])
        }
    }
    return result
}

438

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

/**
 * @param {string} s
 * @param {string} p
 * @return {number[]}
 */
var findAnagrams = function(s, p) {
    function arrayEqual(a, b){
        for(let i=0;i<a.length;i++)
            if(a[i] !== b[i])
                return false
        return true
    }
​
    const m = s.length, n = p.length
    ans = []
    if(m < n)
        return ans
    const cntsP = new Array(26), cnts = new Array(26)
    cntsP.fill(0)
    cnts.fill(0)
    for(let i=0;i<n;i++)
        // charCodeAt()返回字符的ASCII码,如'a'.charCodeAt()为97
        cntsP[p.charCodeAt(i) - 'a'.charCodeAt()]++
    for(let i=0;i<=m;i++){
        cnts[s.charCodeAt(i)-'a'.charCodeAt()]++
        if(i>=n-1){
            if(arrayEqual(cntsP, cnts))
                ans.push(i-n+1)
            cnts[s.charCodeAt(i-n+1)-'a'.charCodeAt()]--
        }
    }
    return ans
};

76

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

const minWindow = (s, t) => {
  let minLen = s.length + 1;
  let start = s.length;     // 结果子串的起始位置
  let map = {};             // 存储目标字符和对应的缺失个数
  let missingType = 0;      // 当前缺失的字符种类数
  for (const c of t) {      // t为baac的话,map为{a:2,b:1,c:1}
    if (!map[c]) {
      missingType++;        // 需要找齐的种类数 +1
      map[c] = 1;
    } else {
      map[c]++;
    }
  }
  let l = 0, r = 0;                // 左右指针
  for (; r < s.length; r++) {      // 主旋律扩张窗口,超出s串就结束
    let rightChar = s[r];          // 获取right指向的新字符
    if (map[rightChar] !== undefined) map[rightChar]--; // 是目标字符,它的缺失个数-1
    if (map[rightChar] == 0) missingType--;   // 它的缺失个数新变为0,缺失的种类数就-1
    while (missingType == 0) {                // 当前窗口包含所有字符的前提下,尽量收缩窗口
      if (r - l + 1 < minLen) {    // 窗口宽度如果比minLen小,就更新minLen
        minLen = r - l + 1;
        start = l;                 // 更新最小窗口的起点
      }
      let leftChar = s[l];          // 左指针要右移,左指针指向的字符要被丢弃
      if (map[leftChar] !== undefined) map[leftChar]++; // 被舍弃的是目标字符,缺失个数+1
      if (map[leftChar] > 0) missingType++;      // 如果缺失个数新变为>0,缺失的种类+1
      l++;                          // 左指针要右移 收缩窗口
    }
  }
  if (start == s.length) return "";
  return s.substring(start, start + minLen); // 根据起点和minLen截取子串
};

哈希表

散列函数的作用就是给定一个键值,然后返回值在表中的地址。

// 散列表
function HashTable() {
  let table = []
  this.put = function(key, value) {}
  this.get = function(key) {}
  this.remove = function(key) {}
}

BFS

515

给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。

        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);
                    }
                }
                // 本「层级」处理完毕,截取掉。
                queue.splice(0, len);
                // 这个for循环结束后 代表当前层级的节点全部处理完毕
                // 直接把计算出来的最大值push到数组里即可。
                maximums.push(max);
            }

            return maximums;
        };

1306

这里有一个非负整数数组 arr,你最开始位于该数组的起始下标 start 处。当你位于下标 i 处时,你可以跳到 i + arr[i] 或者 i - arr[i]。

请你判断自己是否能够跳到对应元素值为 0 的 任一 下标处。

注意,不管是什么情况下,你都无法跳到数组之外。

var canReach = function(arr, start) {
    let length = arr.length
    // 保存所有走过的索引
    let visited = []
    // 保存将要遍历的索引
    let queue = [start]

	// 一次至多插入两个索引
    while(queue.length) {
        let index = queue.pop()
        let value = arr[index]
        if(value === 0) {
            return true
        }
        let leftIndex = index - value
        let rightIndex = index + value
        if(leftIndex>=0 && !visited[leftIndex]) {
            queue.push(leftIndex)
        }
        if(rightIndex<length && !visited[rightIndex]) {
            queue.push(rightIndex)
        }
        visited[index] = true
    } 

    return false
};

733

有一幅以m x n 的二维整数数组表示的图画 image ,其中image[i][j]表示该图画的像素值大小。

你也被给予三个整数 sr , sc 和 newColor 。你应该从像素 image[sr][sc]开始对图像进行上色填充 。

从起始像素向上下左右扩散,只要相邻的点存在并和起始点颜色相同,就染成新的颜色,并继续扩散。

// 从起始像素向上下左右扩散,只要相邻的点存在并和起始点颜色相同,就染成新的颜色,并继续扩散。

// 借助一个队列去遍历节点,考察出列的节点,带出满足条件的节点入列
// 已经染成新色的节点不会入列,避免重复访问节点。

const floodFill = (image, sr, sc, newColor) => {
  const m = image.length;
  const n = image[0].length;
  const oldColor = image[sr][sc];

  if (oldColor == newColor) return image;

  const queue = [[sr, sc]];

  while (queue.length) {
    const [i, j] = queue.shift();
    image[i][j] = newColor;

    if (i - 1 >= 0 && image[i - 1][j] == oldColor) queue.push([i - 1, j]);
    if (i + 1 < m && image[i + 1][j] == oldColor) queue.push([i + 1, j]);
    if (j - 1 >= 0 && image[i][j - 1] == oldColor) queue.push([i, j - 1]);
    if (j + 1 < n && image[i][j + 1] == oldColor) queue.push([i, j + 1]);
  }

  return image;
};

DFS

733

有一幅以m x n 的二维整数数组表示的图画 image ,其中image[i][j]表示该图画的像素值大小。

你也被给予三个整数 sr , sc 和 newColor 。你应该从像素 image[sr][sc]开始对图像进行上色填充 。

从起始像素向上下左右扩散,只要相邻的点存在并和起始点颜色相同,就染成新的颜色,并继续扩散。

var floodFill = function(image, sr, sc, newColor) {
    const m = image.length
    const n = image[0].length
    const oldColor = image[sr][sc]
    if(oldColor === newColor) return image

    var dfs = (i,j) => {
        if(i<0 || i>=m || j<0 || j>=n || image[i][j]!==oldColor) {
            return
        }
        image[i][j] = newColor
        dfs(i-1,j)
        dfs(i+1,j)
        dfs(i,j-1)
        dfs(i,j+1)
    }

    dfs(sr,sc)

    return image
};

200

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

/**
 * @param {character[][]} grid
 * @return {number}
 */
 // 注意题意 连续大陆就是一个岛屿
 // 遍历二维数组,每当遇到1开启搜索模式,从当前节点向左/右/上/下,每次分别移动一步,如果是1则替换为0
var numIslands = function(grid) {
    const m = grid.length
    const n = grid[0].length
    let answer = 0

    var dfs = (i,j) => {
        if(i<0 || i>=m || j<0 || j>=n || grid[i][j]==='0') return
        grid[i][j] = '0'
        dfs(i+1,j)
        dfs(i-1,j)
        dfs(i,j+1)
        dfs(i,j-1)
    }

    for(let i=0;i<m;i++) {
        for(let j=0;j<n;j++) {
            if(grid[i][j]==='1') {
                answer++;
                dfs(i,j)
            }
        }
    }

    return answer
};

463

给定一个 row x col 的二维网格地图 grid ,其中:gridi = 1 表示陆地, gridi = 0 表示水域。

网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。

岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。

方法一:遍历

总周长 = 4 * 土地个数 - 2 * 接壤边的条数。

var islandPerimeter = function(grid) {
    let land = 0
    let border = 0
    let answer = 0
    const m = grid.length
    const n = grid[0].length
    for(let i=0;i<m;i++) {
        for(let j=0;j<n;j++) {
            if(grid[i][j] === 1) {
                land++
                if(i+1<m && grid[i+1][j]===1) {
                    border++
                }
                if(j+1<n && grid[i][j+1]===1) {
                    border++
                }
            }
        }
    }
    answer = 4*land - 2*border
    return answer
};

方法二:DFS

从土地到土地,之间不会产生周长,但从土地迈入海洋,之间会产生 1 个周长,从土地迈出矩阵边界,也会产生 1 个周长。

dfs 的过程中,对当前点的上下左右递归,下一个递归的点又对上下左右递归,就会造成重复访问,造成周长的重复计算。

遍历过的土地节点,将值改为 2,区分于 1 和 0,代表访问过了。

const islandPerimeter = (grid) => {
  const dfs = (i, j) => {
    if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length) {
      return 1; // 当前正好越界,说明穿过了一个边界,周长+1
    }
    if (grid[i][j] == 0) { // 从土地来到了海水,说明穿过了一个边界,周长+1
      return 1;
    }
    if (grid[i][j] == 2) { // 之前访问过,直接返回,返回0,无周长收益
      return 0;
    }
    // 到此,当前点为1,将它改为2,代表已访问
    grid[i][j] = 2; 
    // 继续往四个方向“扩散”,目标是遇到边界和海水,答案随着递归出栈向上返回,得出大的答案
    return dfs(i - 1, j) + dfs(i + 1, j) + dfs(i, j - 1) + dfs(i, j + 1);
  };

  for (let i = 0; i < grid.length; i++) {
    for (let j = 0; j < grid[0].length; j++) {
      if (grid[i][j] == 1) {
        return dfs(i, j);   // dfs的入口
      }
    }
  }
  return 0;
};

分治

50

实现 pow(x,n),即计算 xn 次幂函数(即,x^n )。

/**
 * @param {number} x
 * @param {number} n
 * @return {number}
 */
var myPow = function(x, n) {
   if(n===0) return 1 // n=0直接返回1
   if(n<0) {   				//n<0时 x的n次方等于1除以x的-n次方分
       return 1/myPow(x,-n)
   }
   if(n%2) {    //n是奇数时 x的n次方 = x*x的n-1次方
       return x*myPow(x,n-1)
   }
   return myPow(x*x,n/2) //n是偶数,使用分治,一分为二,等于x*x的n/2次方 
}

二分查找

二分法模板

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
    let l=0, m, r=nums.length-1
    while(l<=r) {
        m = Math.floor((l+r)/2)
        if(nums[m] === target) return m
        if(nums[m] < target) l = m + 1
        if(nums[m] > target) r = m - 1
    }
    return -1;
};

69

给你一个非负整数 x ,计算并返回 x算术平方根

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

/**
 * @param {number} x
 * @return {number}
 */
var mySqrt = function(x) {
    if(x<2) return x
    let left =1, mid, right = Math.floor(x/2)
    while(left<=right) {
        mid = Math.floor((left+right)/2)
        if(mid*mid === x) return mid
        if(mid*mid < x) left = mid + 1
        if(mid*mid > x) right = mid - 1
    }
    return right
};

\