剑指offer 75道经典算法题js实现

/**
 * 1. 左旋转字符串
 * 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。
 * 请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,
 * 该函数将返回左旋转两位得到的结果"cdefgab"。
 * 输入: s = "abcdefg", k = 2
 * 输出: "cdefgab"
 * 输入: s = "lrloseumgh", k = 6
 * 输出: "umghlrlose"
 */
/**
 * @param {string} s
 * @param {number} k
 * @return {string}
 */
var reverseLeftWords = function(s, k) {
  return s.substring(k) + s.substring(0, k)
}

var reverseLeftWords = function(s, k) {
  let result = ''
  for (let i = k; i < s.length; i++) {
    result += s[i]
  }
  
  for (let i = 0; i < k; i++) {
    result += s[i]
  }

  return result
}

/**
 * 2. 链表中倒数第k个节点
 * 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,
 * 本题从1开始计数,即链表的尾节点是倒数第1个节点。
 * 例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。
 * 这个链表的倒数第3个节点是值为4的节点。
 * 示例:
 * 给定一个链表: 1->2->3->4->5, 和 k = 2.
 * 返回链表 4->5.
 */
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var getKthFromEnd = function(head, k) {
  if (head === null)  return null
  let size = 0
  let node = head
  while (node) {
      size++
      node = node.next
  }
  
  let pos = size - k
  let i = 0
  node = head
  while (i++ < pos) {
      node = node.next
  }

  return node
};

var getKthFromEnd = function(head, k) {
  if (k <= 0) return null
  let fast = head, slow = head
  while (k--) {
    if (!fast) return null
    fast = fast.next
  }

  while(fast) {
    fast = fast.next
    slow = slow.next
  }

  return slow
};

/**
 * 3. 二叉树的深度
 * 输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,
 * 最长路径的长度为树的深度。
例如:

给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
返回它的最大深度 3 。
 */
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root) {
  if (root == null) return 0
  
  return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1
};

/**
 * 4. 二叉树的镜像
 * 请完成一个函数,输入一个二叉树,该函数输出它的镜像。

例如输入:

     4
   /   \
  2     7
 / \   / \
1   3 6   9
镜像输出:

     4
   /   \
  7     2
 / \   / \
9   6 3   1

示例 1:
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
*/

/**
 * @param {TreeNode} root
 * @return {TreeNode}
 */
var mirrorTree = function(root) {
  if (root === null)  return null

  let temp = root.left
  root.left  = root.right
  root.right = temp

  mirrorTree(root.left)
  mirrorTree(root.right)

  return root
};

/**
 * 5. 打印从1到最大的n位数
 * 输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

示例 1:
输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]
*/
/**
 * @param {number} n
 * @return {number[]}
 */
var printNumbers = function(n) {
  let result = [], max = Math.pow(10, n)
  for (let i = 1; i < max; i++) {
      result.push(i)
  }

  return result
};

/**
 * 6. 替换空格
 * 请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
示例 1:
输入:s = "We are happy."
输出:"We%20are%20happy."
 */
/**
 * @param {string} s
 * @return {string}
 */
var replaceSpace = function(s) {
  let result = ''
  for (let i = 0; i < s.length; i++) {
      if (s[i] === ' ') {
          result += '%20'
      } else {
          result += s[i]
      }
  }

  return result
}

/**
 * 7. 从尾到头打印链表
 * 输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
 */
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @return {number[]}
 * 利用栈来存储
 */
var reversePrint = function(head) {
  if (!head) return []

  let stack = []
  let node = head
  while(node) {
      stack.push(node.val)
      node = node.next
  }

  let res = []
  while (stack.length) {
    res.push(stack.pop())
  }

  return res
};

/**
 * 8. 反转链表
 * 定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
  if (!head) return null
  let prev = null, curr = head, next = null
  while (curr) {
    next = curr.next
    curr.next = prev
    prev = curr
    curr = next
  }
  return prev
};

/**
 * 9. 二叉搜索树的第k大节点
 * 给定一棵二叉搜索树,请找出其中第k大的节点。
 * 示例 1:

输入: root = [3,1,4,null,2], k = 1
   3
  / \
 1   4
  \
   2
输出: 4
示例 2:

输入: root = [5,3,6,2,4,null,null,1], k = 3
       5
      / \
     3   6
    / \
   2   4
  /
 1
输出: 4
 */
/**
 * @param {TreeNode} root
 * @param {number} k
 * @return {number}
 */
var kthLargest = function(root, k) {
  let result = []
  function inorder(node) {
      if (!node) return
      inorder(node.left)
      result.push(node.val)
      inorder(node.right)
  }

  inorder(root)

  if (k > result.length) return null
  return result[result.length - k]
};

/**
 * 10. 合并两个排序的链表
 * 输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
 * 示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var mergeTwoLists = function(l1, l2) {
  if (!l1) return l2
  if (!l2) return l1

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

/**
 * 11. 二进制中1的个数
 * 请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。
 * 例如,把 9 表示成二进制是 1001,有 2 位是 1。
 * 因此,如果输入 9,则该函数输出 2。
 * 示例 1:

输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
示例 2:

输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。
示例 3:

输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。
 */
/**
 * @param {number} n - a positive integer
 * @return {number}
 */
var hammingWeight = function(n) {
  let count = 0
  while (n > 0) {
      if (n % 2 ) {
          count++
      }
      n >>= 1
  }
  
  return count
};

/**
 * 12. 用两个栈实现队列
 * 用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,
 * 分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
 * 示例 1:

输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:

输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]
 */
var CQueue = function() {
  this.inStack = []
  this.outStack = []
};

/** 
 * @param {number} value
 * @return {void}
 */
CQueue.prototype.appendTail = function(value) {
  this.inStack.push(value)
};

/**
 * @return {number}
 */
CQueue.prototype.deleteHead = function() {
  if (this.outStack.length) {
    return this.outStack.pop()
  }

  while (this.inStack.length) {
    this.outStack.push(this.inStack.pop())
  }

  if (this.outStack.length) {
    return this.outStack.pop()
  }

  return -1
};

/**
 * 13. 二叉树的最近公共祖先
 * 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,
最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4]
         3
        / \
       5   1
      /\   /\
     6  2 0  8
       /\
      7  4
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。

示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
 */

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
var lowestCommonAncestor = function(root, p, q) {
  if (root == null || root == p || root == q) return root
  left = lowestCommonAncestor(root.left, p, q)
  right = lowestCommonAncestor(root.right,p ,q)
  if (left == null) return right
  if (right == null) return left
  
  return root
}
 

/**
 * 14. 和为s的连续正数序列
 * 输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
示例 1:

输入:target = 9
输出:[[2,3,4],[4,5]]
示例 2:

输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]
 */
/**
 * @param {number} target
 * @return {number[][]}
 */
var findContinuousSequence = function(target) {
  let i = 1
  let j = 1
  let res = []
  let sum = 0
  let subSeq = []
  while(i < target/2) {
     if (sum < target) {
         subSeq.push(j)
         sum += j++
     } else if (sum > target) {
         subSeq.shift(i)
         sum -= i++
     } else {
         if (subSeq.length > 1) res.push([...subSeq])
         subSeq.shift(i)
         sum -= i++
     }
  }

  return res
};

/**
 * 15.从上到下打印二叉树 II
 *  从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
 * 例如:
给定二叉树: [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
返回其层次遍历结果:

[
  [3],
  [9,20],
  [15,7]
]
 */
var levelOrder = function(root) {
  if (!root) return []
  let queue = [root]
  let result = []

  while (queue.length) {
      let temp = []
      for (let i = queue.length; i > 0; i--) {
          let node = queue.shift()
          temp.push(node.val)
          if (node.left) queue.push(node.left)
          if (node.right) queue.push(node.right)
      }
      result.push(temp)
  }

  return result
};


/**
 * 16. 二叉搜索树的最近公共祖先
 * 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
 * 输入: root = [3,1,4,null,2], p = 2, q = 4
 * 输出:3
   3
  / \
 1   4
  \
   2
输出: 4
 */
var lowestCommonAncestor = function(root, p, q) {
  while (root != null) {
    if (root.val > p.val && root.val > q.val) {
      root = root.left
    } else if (root.val < p.val && root.val < q.val) {
      root = root.right
    } else {
      break
    }
  }

  return root;
}


/**
 * 17. 数组中出现次数超过一半的数字
 * 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
 */
/**
 * @param {number[]} nums
 * @return {number}
 */
var majorityElement = function(nums) {
  let result
  let votes = 0
  for (let i = 0; i < nums.length; i++) {
      if (votes === 0) result = nums[i]
      votes += nums[i] === result ? 1 : -1
  }

  return result
};

/**
 * 18. 数组中重复的数字
 * 找出数组中重复的数字。

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数
组中某些数字是重复的,但不知道有几个数字重复了,
也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:

输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3 
 */
/**
 * @param {number[]} nums
 * @return {number}
 */
var findRepeatNumber = function(nums) {
  if (!nums || nums.length === 0) return null

  let map = new Map()
  for (let i = 0; i < nums.length; i++) {
      if (map.has(nums[i])) return nums[i]
      else map[nums[i]] = i
  }
};

/**
 * 19. 和为s的两个数字
 * 输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]
示例 2:

输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]
 */
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
  let left = 0, right = nums.length -1
  while (left < right) {
      while (nums[left] + nums[right] > target) right--
      while (nums[left] + nums[right] < target) left++
      if (nums[left] + nums[right] === target) return [nums[left], nums[right]]
  }
};

/**
 * 20. 调整数组顺序使奇数位于偶数前面
 * 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
示例:
输入:nums = [1,2,3,4]
输出:[1,3,2,4] 
注:[3,1,2,4] 也是正确的答案之一。
 */
/**
 * @param {number[]} nums
 * @return {number[]}
 */
var exchange = function(nums) {
  let left = 0
  let right = nums.length -1

  while (left < right) {
      while (nums[left] % 2) left++
      while (nums[right] % 2 === 0) right--

      if (left < right) {
          let temp = nums[left]
          nums[left] = nums[right]
          nums[right] = temp
          left++
          right--
      }
  }

  return nums
};

/**
 * 21. 圆圈中最后剩下的数字
 * 0,1,,n-1这n个数字排成一个圆圈,从数字0开始,
 * 每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。

例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,
则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
示例 1:

输入: n = 5, m = 3
输出: 3
示例 2:

输入: n = 10, m = 17
输出: 2
 */
/**
 * @param {number} n
 * @param {number} m
 * @return {number}
 */
var lastRemaining = function(n, m) {
  let fn = 0
  for (let i = 2; i <= n; i++) {
      fn = (fn + m) % i
  }

  return fn
};

/**
 * 22. 两个链表的第一个公共节点
 * 输入两个链表,找出它们的第一个公共节点。
a1 -> a2
          -> c1 -> c2 -> c3
b1->b2->b3
在节点 c1 开始相交。
 */
/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function(headA, headB) {
  let node = headA
  let lengthA = 0
  while (node) {
      lengthA++
      node = node.next
  }
  if (!lengthA) return null

  node = headB
  let lengthB = 0
  while (node) {
      lengthB++
      node = node.next
  }
  if (!lengthB) return null

  let diff = 0
  let slow, fast
  if (lengthA > lengthB) {
      slow = headA
      fast = headB
      diff = lengthA - lengthB
  } else {
      slow = headB
      fast = headA
      diff = lengthB - lengthA
  }

  while(diff--) {
      slow = slow.next
  }

  while (slow !== fast) {
      slow = slow.next
      fast = fast.next
  }

  return slow

};

/**
 * 23. 第一个只出现一次的字符
 * 在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
示例:
s = "abaccdeff"
返回 "b"
s = "" 
返回 " "
 */
/**
 * @param {string} s
 * @return {character}
 */
var firstUniqChar = function(s) {
  if (!s) return ' '
  let map = new Map()
  for(let i = 0; i < s.length; i++) {
      map.set(s[i], !map.has(s[i]))
  }

  for(let i = 0; i < s.length; i++) {
      if (map.get(s[i])) return s[i]
  }

  return ' '
};

/**
 * 24. 连续子数组的最大和
 * 输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
 */
var maxSubArray = function(nums) {
  if (!nums || nums.length === 0) return null
  let dp
  let maxSum = dp = nums[0]
  for (let i = 1; i < nums.length; i++) {
      dp = Math.max(dp + nums[i], nums[i])
      maxSum = Math.max(dp, maxSum)
  }

  return maxSum
};

/**
 * 25.  删除链表的节点
 * 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
示例 1:

输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:

输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
 */
/**
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
var deleteNode = function(head, val) {
  if (!head) return head
  if (head.val === val) return head.next
      
  let node = head
  while (node.next) {
      if( node.next.val === val) {
          node.next = node.next.next
          return head
      }
      node = node.next
  }
  return head
};

/**
 * 26. 平衡二叉树
 * 输入一棵二叉树的根节点,判断该树是不是平衡二叉树。
 * 如果某二叉树中任意节点的左右子树的深度相差不超过1,
 * 那么它就是一棵平衡二叉树。
 * 示例 1:

给定二叉树 [3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7
返回 true 。

示例 2:

给定二叉树 [1,2,2,3,3,null,null,4,4]

       1
      / \
     2   2
    / \
   3   3
  / \
 4   4
 */
/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isBalanced = function(root) {
  if (!root) return true
  return recur(root) !== -1

  function recur(node) {
    if (!node) return 0
    let left = recur(node.left)
    if (left === -1) return -1
    let right = recur(node.right)
    if (right === -1) return -1

    return Math.abs(left - right) <= 1 ? Math.max(left, right) + 1 : -1
  }
};

/**
 * 27. 对称的二叉树
 * 请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

    1
   / \
  2   2
 / \ / \
3  4 4  3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
    1
   / \
  2   2
   \   \
   3    3

示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false
 */
var isSymmetric = function(root) {
  if (!root)  return true
  
  function recur(l, r) {
      if (l == null && r == null) return true
      if (l == null || r == null || l.val != r.val) return false
      return recur(l.left, r.right) && recur(l.right, r.left)
  }

  return recur(root.left, root.right)
}

/**
 * 28. 包含min函数的栈
 * 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,
 * 调用 min、push 及 pop 的时间复杂度都是 O(1)。
 */
/**
 * initialize your data structure here.
 */
var MinStack = function() {
  this.stack = []
  this.minStack = []
};

/** 
 * @param {number} x
 * @return {void}
 */
MinStack.prototype.push = function(x) {
  this.stack.push(x)
  if (this.minStack.length === 0 || x <= this.minStack[this.minStack.length - 1]) {
    this.minStack.push(x)
  }
};

/**
 * @return {void}
 */
MinStack.prototype.pop = function() {
  let elm = this.stack.pop()
  if (this.minStack[this.minStack.length - 1] === elm) {
    this.minStack.pop()
  }
};

/**
 * @return {number}
 */
MinStack.prototype.top = function() {
  return this.stack[this.stack.length - 1]
};

/**
 * @return {number}
 */
MinStack.prototype.min = function() {
  return this.minStack[this.minStack.length - 1]
};

/**
 * 29. 最小的k个数
 * 输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

示例 1:

输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:

输入:arr = [0,1,2,1], k = 1
输出:[0]
 */
var getLeastNumbers = function(arr, k) {
  return arr.sort((a, b) => a - b).slice(0, k)
};

/**
 * 30. 不用加减乘除做加法
 * 写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。
示例:
输入: a = 1, b = 1
输出: 2
 */
var add = function(a, b) {
  while (b != 0) {
    let c = (a & b) << 1 // 加法的进位
    a = a ^ b // 不带进位的加法
    b = c
  }
  return a
};

/**
 * 31. n个骰子的点数
 * 把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。
示例 1:
输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]
示例 2:
输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]
 */
/**
 * @param {number} n
 * @return {number[]}
 */
var twoSum = function(n) {
  if (n === 0) return []

  let dp = []
  dp[1] = []
  // for (let i = 0; i <= 6 *n; i++) {
  //   dp.push(new Array())
  // }
  for (let i = 1; i <= 6; i++) {
    dp[1][i] = 1
  }

  for (let i = 2; i <= n; i++) {// 骰子数
    dp[i] = []
    for (let j = i; j <= 6 * i; j++) { // 点数和
      dp[i][j] = 0
      for (let count = 1; count <= 6; count++) {
        if (j <= count) {
          break
        }
        dp[i][j] += dp[i - 1][j - count]? dp[i - 1][j - count] : 0
      }
    }
  }

  let sum = Math.pow(6, n), res = []
  for (let i = n; i <= 6 * n; i++) {
    res.push(dp[n][i]/sum)
  }

  return res
};

/**
 * 32. 在排序数组中查找数字I
 * 统计一个数字在排序数组中出现的次数。
 * 示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: 0
 */
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
  if (!nums.length) return 0

  let start = -1, end = -1
  let left = 0, right = nums.length - 1
  
  while (left <= right) {
    let mid = (left + right) >> 1
    if (nums[mid] === target) {
      start = mid
      right = mid - 1
    } else if (nums[mid] > target) {
      right = mid - 1
    } else {
      left = mid + 1
    }
  }

  left = 0
  right = nums.length - 1
  
  while (left <= right) {
    let mid = (left + right) >> 1
    if (nums[mid] === target) {
      end = mid
      left = mid + 1
    } else if (nums[mid] > target) {
      right = mid - 1
    } else {
      left = mid + 1
    }
  }

  return end >= start && start > -1 ? end - start + 1 : 0
};

/**
 * 33. 旋转数组的最小数字
 * 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
 * 输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。
 * 例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。  
 * 示例 1
输入:[3,4,5,1,2]
输出:1
示例 2:
输入:[2,2,2,0,1]
输出:0
 */
/**
 * @param {number[]} numbers
 * @return {number}
 */
var minArray = function(numbers) {
  if(!numbers || !numbers.length) return null

  let low = 0, high = numbers.length - 1
  while (low < high) {
    let mid = (low + high) >> 1
    if (numbers[mid] > numbers[high]) {
      low = mid + 1
    } else if (numbers[mid] < numbers[high]) {
      high = mid
    } else {
      high--
    }
  }

  return numbers[low]
};

/**
 * 34. 扑克牌中的顺子
 * 从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,
 * A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
示例 1:
输入: [1,2,3,4,5]
输出: True
示例 2:
输入: [0,0,1,2,5]
输出: True
 */
/**
 * @param {number[]} nums
 * @return {boolean}
 */
var isStraight = function(nums) {
  let map = new Map()
  let min = 14, max = 0
  
  for (num of nums) {
    if (num === 0) continue
    if (map.has(num)) return false
    map.set(num, num)
    min = Math.min(min, num)
    max = Math.max(max, num)
  }

  return max - min < 5
};

/**
 * 35. 顺时针打印矩阵
 * 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
示例 1:
输入:matrix = [
  [1,2,3],
  [4,5,6],
  [7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:
输入:matrix = [
  [1, 2,  3,  4],
  [5, 6,  7,  8],
  [9, 10, 11, 12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
 */
/**
 * @param {number[][]} matrix
 * @return {number[]}
 */
var spiralOrder = function(matrix) {
  if (!matrix || !matrix.length || matrix[0].length <= 1) return matrix

    let rows = matrix.length, cols = matrix[0].length, res = []
    let left = 0, right = cols - 1
    let top = 0, bottom = rows - 1
    while (left <= right && top <= bottom) {
        for (let i = left; i <= right; i++) res.push(matrix[top][i])
        for (let i = top + 1; i <= bottom; i++) res.push(matrix[i][right])
        if (top < bottom) {
            for (let i = right - 1; i >= left; i--) res.push(matrix[bottom][i])
        }
        if (left < right) {
            for (let i = bottom - 1; i > top; i--) res.push(matrix[i][left])
        }

        left++, right--, top++, bottom--
    }

    return res
};

/**
 * 36. 滑动窗口的最大值
 * 给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7] 
解释: 

  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7
 */
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var maxSlidingWindow = function(nums, k) {
  if (!nums || !nums.length || k <= 0) return []
  if (k === 1) return nums

  let res = [], queue = []
  for (let i = 0; i < nums.length; i++) {
    if(i >= k) {
      // 尾部元素出滑动窗口
      let outElem = nums[i - k]
      if (outElem === queue[0]) queue.shift()
    }
    // 当前元素进入滑动窗口
    let inElem = nums[i]
    while (queue.length && queue[queue.length - 1] < inElem) queue.pop()
    queue.push(inElem)

    if (i >= k - 1) res.push(queue[0])
  }

  return res
};

/** 
 * 37.  0~n-1中缺失的数字
 * 一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。
 * 在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例 1:
输入: [0,1,3]
输出: 2
示例 2:
输入: [0,1,2,3,4,5,6,7,9]
输出: 8
 */
/**
 * @param {number[]} nums
 * @return {number}
 */
var missingNumber = function(nums) {
  if (!nums || !nums.length) return null

  let left = 0, right = nums.length - 1, mid
  while (left <= right) {
    mid = Math.floor((left + right)/2)
    if (nums[mid] === mid) {
      left = mid + 1
    } else {
      right = mid - 1
    }
  }

  return left
};

/**
 * 38. 翻转单词顺序
 * 示例 1:
输入: "the sky is blue"
输出: "blue is sky the"
示例 2:
输入: "  hello world!  "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:
输入: "a good   example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
 
说明:
无空格字符构成一个单词。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
 */
/**
 * @param {string} s
 * @return {string}
 */
var reverseWords = function(s) {
  if (!s || s.length === 0) return ''
  s = s.trim()
  let fast = s.length - 1, low = fast
  let result = ''
  while (fast >= 0) {
      while (fast >= 0 && s[fast] !== ' ') fast--
      result += s.substring(fast + 1, low + 1) + (fast <= 0 ? '' : ' ')
      while (fast >= 0 && s[fast] === ' ') fast--
      low = fast
  }

  return result
};

/**
 * 39. 青蛙跳台阶问题
 * 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:2
示例 2:

输入:n = 7
输出:21
示例 3:

输入:n = 0
输出:1
 */
/**
 * @param {number} n
 * @return {number}
 */
var numWays = function(n) {
  if (n <= 0) return 1
  if (n === 1 || n === 2) return n
  let f1 = 1, f2 = 2, fn = 0

  for (let i = 3; i <= n; i++) {
      fn = (f1 + f2) % 1000000007
      f1 = f2
      f2 = fn
  }
  
  return fn
};

/**
 * 40. 二维数组中的查找
 * 在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,
 * 每一列都按照从上到下递增的顺序排序。请完成一个函数,
 * 输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
现有矩阵 matrix 如下:
[
  [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]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。
 */
/**
 * @param {number[][]} matrix
 * @param {number} target
 * @return {boolean}
 */
var findNumberIn2DArray = function(matrix, target) {
  if (!matrix || matrix.length < 1 || !matrix[0] || matrix[0].length < 1) return false
  let rows = matrix.length
  let columns = matrix[0].length
  let row = 0, column = columns - 1
  while (row < rows && column >= 0) {
      if (matrix[row][column] > target) column--
      else if (matrix[row][column] < target) row++
      else return true
  }

  return false
};

/**
 * 41. 斐波那契数列
 * 一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:
F(0) = 0,   F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:
输入:n = 2
输出:1
示例 2:
输入:n = 5
输出:5
 */
/**
 * @param {number} n
 * @return {number}
 */
var fib = function(n) {
  if (n < 2) return n
  let f0 = 0, f1 =1;
  let i = 2, fn =0;
  while (i <= n) {
      fn = (f0 + f1) % 1000000007
      f0 = f1
      f1 = fn
      i++ 
  }

  return fn
};

/**
 * 42. 求1+2+…+n
 * 求 1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
示例 1:
输入: n = 3
输出: 6
示例 2:
输入: n = 9
输出: 45
 */
/**
 * @param {number} n
 * @return {number}
 */
var sumNums = function(n) {
  return n && (n + sumNums(n-1))
};

/**
 * 43. 数组中数字出现的次数 II
 * 在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
示例 1:
输入:nums = [3,4,3,3]
输出:4
示例 2:
输入:nums = [9,1,7,9,7,9,7]
输出:1
 */
/**
 * @param {number[]} nums
 * @return {number}
 */
var singleNumber = function(nums) {
  let res = 0, bit = 0, cnt = 0
    for (let i = 0; i < 32; i++) {
        bit = 1 << i
        cnt = 0
        for (let j = 0; j < nums.length; j++) {
            if (nums[j] & bit) cnt++
        }
        if (cnt % 3) res |= bit
    }

    return res
};

/**
 * 44. 复杂链表的复制
 * 请实现 copyRandomList 函数,复制一个复杂链表。
 * 在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,
 * 还有一个 random 指针指向链表中的任意节点或者 null。
 */
/**
 * // Definition for a Node.
 * function Node(val, next, random) {
 *    this.val = val;
 *    this.next = next;
 *    this.random = random;
 * };
 */

/**
 * @param {Node} head
 * @return {Node}
 */
var copyRandomList = function(head) {
  if (!head) return null

  let node  = head, map = new Map()
  while (node) {
      map.set(node, new Node(node.val))
      node = node.next
  }

  let newHead = null, newNode = null
  node = head
  while (node) {
      newNode = map.get(node)
      if (node.next) newNode.next = map.get(node.next)
      else newNode.next = null
      if (node.random) newNode.random = map.get(node.random)
      else newNode.random = null
      if (node === head) newHead = newNode
      node = node.next
  }

  return newHead
};

/**
 * 45. 数组中数字出现的次数
 * 一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。
 * 请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
 */
/**
 * @param {number[]} nums
 * @return {number[]}
 */
var singleNumbers = function(nums) {
  let h = 0
  for (let num of nums) {
    h ^= num
  }

  let mask = h & -h// lowbit最低位的1所对应的值lowbit(6)=2 110->10
  let a = 0, b = 0
  for (let num of nums) {
    if (num & mask)  a ^= num
    else b ^= num
  }

  return [a, b]
};

/**
 * 46. 重建二叉树
 * 输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
    3
   / \
  9  20
    /  \
   15   7
 */
/**
 * @param {number[]} preorder
 * @param {number[]} inorder
 * @return {TreeNode}
 */
var buildTree = function(preorder, inorder) {
  if (!preorder.length || !inorder.length) return null
    
    let rootVal = preorder[0]
    let root = new TreeNode(rootVal)
    let index = inorder.indexOf(rootVal)
    root.left = buildTree(preorder.slice(1, index + 1), inorder.slice(0, index))
    root.right = buildTree(preorder.slice(index + 1), inorder.slice(index + 1))

    return root
};

/**
 * 47. 礼物的最大价值
 * 在一个 m*n 的棋盘的每一格都放有一个礼物,
 * 每个礼物都有一定的价值(价值大于 0)。
 * 你可以从棋盘的左上角开始拿格子里的礼物,
 * 并每次向右或者向下移动一格、直到到达棋盘的右下角。
 * 给定一个棋盘及其上面的礼物的价值,
 * 请计算你最多能拿到多少价值的礼物?
示例 1:
输入: 
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
 */
/**
 * @param {number[][]} grid
 * @return {number}
 */
var maxValue = function(grid) {
  let m = grid.length
  if (m === 0) return 0

  let n = grid[0].length

  for (let row = 0; row < m; row++) {
    for (let col = 0; col < n; col++) {
      if (row === 0 && col === 0) {
        continue;
      } else if (row === 0) {
        grid[row][col] += grid[row][col - 1]
      } else if (col === 0) {
        grid[row][col] += grid[row - 1][col]
      } else {
        grid[row][col] += Math.max(grid[row - 1][col], grid[row][col - 1])
      }
    }
  }

  return grid[m - 1][n - 1]
};

/**
 * 48. 从上到下打印二叉树
 * 从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
例如:
给定二叉树: [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
返回:
[3,9,20,15,7]
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var levelOrder = function(root) {
  if (root === null) return []
  const queue = [root]
  const res = []

  while (queue.length > 0) {
      let node = queue.shift()
      res.push(node.val)
      if (node.left) queue.push(node.left)
      if (node.right) queue.push(node.right)
  }

  return res
};

/**
 * 49. 丑数
 * 我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:  
1 是丑数。
n 不超过1690。
 */
/**
 * @param {number} n
 * @return {number}
 */
var nthUglyNumber = function(n) {
  let res = new Array(n)
  res[0] = 1
  let p2 = 0, p3 = 0, p5 = 0

  for (let i = 1; i < n; i++) {
    res[i] = Math.min(2 * res[p2], 3 * res[p3], 5 * res[p5])
    if (res[i] === 2 * res[p2]) {
      p2++
    }
    if (res[i] === 3 * res[p3]) {
      p3++
    } 
    if (res[i] === 5 * res[p5]){
      p5++
    }
  }

  return res[n - 1]
};

/**
 * 50. 二叉搜索树与双向链表
 * 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。
 * 要求不能创建任何新的节点,只能调整树中节点指针的指向。
为了让您更好地理解问题,以下面的二叉搜索树为例:
我们希望将这个二叉搜索树转化为双向循环链表。
链表中的每个节点都有一个前驱和后继指针。
对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。
下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。
      4
      /\
     2  5
     /\
    1  3
    1 <-> 2 <-> 3 <-> 4 <-> 5
特别地,我们希望可以就地完成转换操作。
当转化完成以后,树中节点的左指针需要指向前驱,
树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。
 */
/**
 * // Definition for a Node.
 * function Node(val,left,right) {
 *    this.val = val;
 *    this.left = left;
 *    this.right = right;
 * };
 */
/**
 * @param {Node} root
 * @return {Node}
 *    4
      /\
     2  5
     /\
    1  3
    1 <-> 2 <-> 3 <-> 4 <-> 5
 */
var treeToDoublyList = function(root) {
  if (!root) return null
  let head = null, pre = null
  
  function inorder(node) {
      if (!node) return
      inorder(node.left)
      // process node
      if (!pre) {
        head = node
      } else {
        pre.right = node
      }
      node.left = pre
      pre = node

      inorder(node.right)
  }
  inorder(root)
  head.left = pre
  pre.right = head

  return head
};

/**
 * 51. 股票的最大利润
 * 假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
 */

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(prices) {
  let min = prices[0],
  profit = 0,
  i;
  for (i = 1; i < prices.length; i++) {
    min = Math.min(min, prices[i])
    profit = Math.max(profit, prices[i] - min)
  }

  return profit
};

/**
 * 52. 栈的压入、弹出序列
 * 输入两个整数序列,第一个序列表示栈的压入顺序,
 * 请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。
 * 例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,
 * 但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。

示例 1:
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:

输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
 */
/**
 * @param {number[]} pushed
 * @param {number[]} popped
 * @return {boolean}
 */
var validateStackSequences = function(pushed, popped) {
  const stack = []
  let index = 0;

  for (let i = 0; i < pushed.length; i++) {
      stack.push(pushed[i])
      while (stack.length > 0 && stack[stack.length - 1] === popped[index]) {
          index++
          stack.pop()
      }
  }

  return stack.length === 0
};

/**
 * 53.  从上到下打印二叉树 III
 * 请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

例如:
给定二叉树: [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
返回其层次遍历结果:

[
  [3],
  [20,9],
  [15,7]
]
 */
/**
 * @param {TreeNode} root
 * @return {number[][]}
 */
var levelOrder = function(root) {
  if (!root) return []
   const queue = [root],
   res = [];
   let row = 0

   while (queue.length > 0) {
     const temp = []
     for (let i = queue.length - 1; i >= 0; i--) {
       let node = queue.shift()
       if (row % 2 === 0) {
         temp.push(node.val)
       } else {
         temp.unshift(node.val)
       }
       
       if (node.left) queue.push(node.left)
       if (node.right) queue.push(node.right)
     }
     row++
     res.push(temp)
   }

  return res
}

/**
 * 54. 构建乘积数组
 * 给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B 中的元素 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

示例:
输入: [1,2,3,4,5]
输出: [120,60,40,30,24]
提示:
所有元素乘积之和不会溢出 32 位整数
a.length <= 100000
 */
/**
 * @param {number[]} a
 * @return {number[]}
 */
var constructArr = function(a) {
  if (!a || a.length === 0) return []

  let len = a.length
  const b = []

  for (let i = 0, left = 1; i < len; i++) {
    b[i] = left
    left *= a[i]
  }

  for (let i = len - 1, right = 1; i >= 0; i--) {
    b[i] *= right
    right *= a[i]
  }

  return b
};

/**
 * 55. 二叉树中和为某一值的路径
 * 输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。
 * 从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。

示例:
给定如下二叉树,以及目标和 sum = 22,

              5
             / \
            4   8
           /   / \
          11  13  4
         /  \    / \
        7    2  5   1
返回:
[
   [5,4,11,2],
   [5,8,4,5]
]
 */
/**
 * @param {TreeNode} root
 * @param {number} sum
 * @return {number[][]}
 */
var pathSum = function(root, sum) {
  if (!root) return []
  const res = []

  function traverse(node, target, arr) {
    if (!node) return
    let diff = target - node.val
    arr.push(node.val)

    if (diff === 0 && node.left === null && node.right === null) {
      res.push(arr)
      return
    }

    traverse(node.left, diff, [...arr])
    traverse(node.right, diff, [...arr])
  }

  traverse(root, sum, [])

  return res
};

/**
 * 56. 把数组排成最小的数
 * 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
示例 1:
输入: [10,2]
输出: "102"
示例 2:

输入: [3,30,34,5,9]
输出: "3033459"
 */
/**
 * @param {number[]} nums
 * @return {string}
 */
var minNumber = function(nums) {
  nums.sort((a, b) =>  ('' + a + b) - ('' + b + a))
  return nums.join('')
};

/**
 * 57. 剪绳子
 * 给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),
 * 每段绳子的长度记为 k[0],k[1]...k[m-1] 。请问 k[0]*k[1]*...*k[m-1] 
 * 可能的最大乘积是多少?例如,当绳子的长度是8时,
 * 我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
 */
/**
 * @param {number} n
 * @return {number}
 */
var cuttingRope = function(n) {
  if (n <= 1) return 0
  if (n === 2) return 1
  if (n === 3) return 2

  let sum = 0
  if (n % 3 === 0) {
    sum = Math.pow(3, n/3)
  } else if (n % 3 === 1) {
    sum = Math.pow(3, Math.floor(n/3) -1) * 4
  } else {
    sum = Math.pow(3, Math.floor(n/3)) * 2
  }

  return sum
};

/**
 * 58. 字符串的排列
 * 输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
示例:

输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]
 */
/**
 * @param {string} s
 * @return {string[]}
 */
var permutation = function(s) {
  if (s.length === 0) return []

  const visited = []
  const path = []
  const res = new Set()
  dfs(s, visited, path, res)
  return Array.from(res)
};

function dfs(s, visited, path, res){
  if (path.length === s.length) {
      res.add(path.join(''))
      return
  }

  for(let i = 0; i < s.length; i++) {
    if (visited[i]) continue
    visited[i] = true
    path.push(s[i])
    dfs(s, visited, path, res)
    path.pop()
    visited[i] = false
  }

}

var permute = function(nums) {
  let res = []

  function dfs(arr) {
    if (arr.length === nums.length) {
      res.push(arr.slice())
    }
    
    for(let i = 0; i < nums.length; i++) {
      if (arr.includes(nums[i])) continue
      arr.push(nums[i])
      dfs(arr)
      arr.pop()
    }
  }

  dfs([])
  return res
};

var permute = function(str) {
  let res = []

  function dfs(arr) {
    if (arr.length === str.length) {
      res.push(arr.slice().join(''))
    }
    
    for(let i = 0; i < str.length; i++) {
      if (arr.includes(str[i])) continue
      arr.push(str[i])
      dfs(arr)
      arr.pop()
    }
  }

  dfs([])
  return res
};



/**
 * 59. 把数字翻译成字符串
 * 给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,
 * 1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。
 * 一个数字可能有多个翻译。请编程实现一个函数,
 * 用来计算一个数字有多少种不同的翻译方法。
示例 1:

输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
 */
/**
 * @param {number} num
 * @return {number}
 */
var translateNum = function(num) {
  const str = num.toString()
  const len = str.length

  const dp = new Array(len + 1)
  dp[0] = 1, dp[1] = 1

  for (let i = 2; i <= len; i++) {
    let num = Number(str[i - 2] + str[i - 1])
    if (num >= 10 && num <= 25) {
      dp[i] = dp[i - 1] + dp[i - 2]
    } else {
      dp[i] = dp[i - 1]
    }
  }

  return dp[len]
};

/**
 * 60. 二叉搜索树的后序遍历序列
 * 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。
 * 如果是则返回 true,否则返回 false。
 * 假设输入的数组的任意两个数字都互不相同。

参考以下这颗二叉搜索树:

     5
    / \
   2   6
  / \
 1   3
示例 1:

输入: [1,6,3,2,5]
输出: false
示例 2:
输入: [1,3,2,6,5]
输出: true
 */
/**
 * @param {number[]} postorder
 * @return {boolean}
 */
var verifyPostorder = function(postorder) {
  let stack = [];
  let root = Infinity;
  for(let i = postorder.length - 1; i >= 0; i--) {
    if (postorder[i] > root) return false;
    while (stack.length && stack[stack.length - 1] > postorder[i]) {
      root = stack.pop();
    }
    stack.push(postorder[i]);
  }
  return true;
};

/**
 * 61. 机器人的运动范围
 * 地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。
 * 一个机器人从坐标 [0, 0] 的格子开始移动,
 * 它每次可以向左、右、上、下移动一格(不能移动到方格外),
 * 也不能进入行坐标和列坐标的数位之和大于k的格子。
 * 例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。
 * 但它不能进入方格 [35, 38],因为3+5+3+8=19。
 * 请问该机器人能够到达多少个格子?

示例 1:
输入:m = 2, n = 3, k = 1
输出:3
示例 2:
输入:m = 3, n = 1, k = 0
输出:1
*/
/**
 * @param {number} m
 * @param {number} n
 * @param {number} k
 * @return {number}
 */
var movingCount = function(m, n, k) {
  // 位数和
    function getSum(num) {
        let answer = 0;

        while(num) {
            answer += num % 10;
            num = Math.floor(num / 10);
        }

        return answer;
    } 
    // 方向数组
    const directionAry = [
        [-1, 0], // 上
        [0, 1], // 右
        [1, 0], // 下
        [0, -1] // 左
    ];

    // 已经走过的坐标
    let set = new Set(['0,0']);

    // 将遍历的坐标队列,题意要求从[0,0]开始走
    let queue = [[0, 0]];

    // 遍历队列中的坐标
    while(queue.length) {
        // 移除队首坐标
        let [x, y] = queue.shift();

        // 遍历方向
        for(let i = 0; i < 4; i++) {
            let offsetX = x + directionAry[i][0];
            let offsetY = y + directionAry[i][1];


            // 临界值判断
            if(offsetX < 0 || offsetX >= m || offsetY < 0 || offsetY >= n || getSum(offsetX) + getSum(offsetY) > k || set.has(`${offsetX},${offsetY}`)) {
                continue;
            }

            // 走过的格子就不再纳入统计
            set.add(`${offsetX},${offsetY}`);

            // 将该坐标加入队列(因为这个坐标的四周没有走过,需要纳入下次的遍历)
            queue.push([offsetX, offsetY]);
        }
    }

    // 走过坐标的个数就是可以到达的格子数
    return set.size;

};

/**
 * 62. 队列的最大值
 * 请定义一个队列并实现函数 max_value 得到队列里的最大值,
 * 要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1
示例 1:
输入: 
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
示例 2:

输入: 
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]
 */
var MaxQueue = function() {
  this.queue = []
  this.maxQueue = []
};

/**
 * @return {number}
 */
MaxQueue.prototype.max_value = function() {
  if (this.maxQueue.length === 0) {
    return -1
  }
  return this.maxQueue[0]

};

/** 
 * @param {number} value
 * @return {void}
 */
MaxQueue.prototype.push_back = function(value) {
  this.queue.push(value)
  while (this.maxQueue.length && this.maxQueue[this.maxQueue.length - 1] < value) {
    this.maxQueue.pop()
  }
  this.maxQueue.push(value)
};

/**
 * @return {number}
 */
MaxQueue.prototype.pop_front = function() {
  if (this.queue.length === 0) return -1

  let val = this.queue.shift()
  if (this.maxQueue[0] === val) this.maxQueue.shift()

  return val
};

/**
 * Your MaxQueue object will be instantiated and called as such:
 * var obj = new MaxQueue()
 * var param_1 = obj.max_value()
 * obj.push_back(value)
 * var param_3 = obj.pop_front()
 */


 /**
  * 63. 树的子结构
  * 输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)

B是A的子结构, 即A中有出现和B相同的结构和节点值。

例如:
给定的树 A:

     3
    / \
   4   5
  / \
 1   2
给定的树 B:

   4 
  /
 1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。

示例 1:

输入:A = [1,2,3], B = [3,1]
输出:false
示例 2:

输入:A = [3,4,5,1,2], B = [4,1]
输出:true
  */
 /**
 * @param {TreeNode} A
 * @param {TreeNode} B
 * @return {boolean}
 */

var isSubStructure = function(A, B) {
  function recur(A, B) {
    if (B === null) return true
    if (A === null || A.val !== B.val) return false
    return recur(A.left, B.left) && recur(A.right, B.right)
  }

  return A !== null && B !== null && (recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B))
};

/**
 * 64. 最长不含重复字符的子字符串
 * 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
示例 1:
输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
 */
/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
  if (!s) return 0

  let max = 0, map = new Map(), dp = 0
  for (let i = 0; i < s.length; i++) {
    let index = map.get(s[i])
    if (index >= 0) {
      if (dp >= i - index) {
        dp = i - index
      } else {
        dp++
      }
    } else {
      dp++
    }
    map.set(s[i], i)
    if (max < dp) max = dp
  }

  return max
};

/**
 * 65. 矩阵中的路径
 * 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。
 * 路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。
 * 如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。
 * 例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[
  ["a","b","c","e"],
  ["s","f","c","s"],
  ["a","d","e","e"]
]
但矩阵中不包含字符串“abfb”的路径,
因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,
路径不能再次进入这个格子。
示例 1:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
示例 2:
输入:board = [["a","b"],["c","d"]], word = "abcd"
输出:false
提示:
 */
/**
 * @param {character[][]} board
 * @param {string} word
 * @return {boolean}
 */
var exist = function(board, word) {
  if (board === null || !board.length || !word) return false

  let rows = board.length, cols = board[0].length

  function dfs(board, i, j, word,index) {
    if (i < 0 || i >= rows || j < 0 || j >= cols || board[i][j] !== word[index]) return false
    if (index === word.length - 1) return true
    let temp = board[i][j]
    board[i][j] = null
    let res = dfs(board, i-1,j, word,index+1) || dfs(board, i+1,j,word,index+1)
        || dfs(board, i,j-1, word,index+1) || dfs(board, i,j+1,word,index+1)
    board[i][j] = temp

    return res
  }

  for (let i = 0; i < rows; i++) 
    for(let j = 0; j < cols; j++) {
      if (dfs(board, i, j, word, 0)) return true
    }

  return false
};

/**
 * 66.
 */

/**
* 67.
*/

/**
 * 68.剪绳子 II
 * 给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),
 * 每段绳子的长度记为 k[0],k[1]...k[m - 1] 。
 * 请问 k[0]*k[1]*...*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,
 * 我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
 */
/**
 * @param {number} n
 * @return {number}
 */
var cuttingRope = function(n) {
  let arr=[0,0,1,2,4];
  if(n<5) return arr[n];
  const max=1e9+7;
  let res=1;
  while(n>=5){
      res=res%max*3;
      n=n-3;
  }
  return  res*n%max;
};

/**
 * 69. 把字符串转换成整数
 * @param {string} str
 * @return {number}
 */
var strToInt = function(str) {
  str = str.trim()
  if (!str || str.length === 0) return 0

  let res = 0, sign = 1, start = 0
  if (str[0] === '-') sign = -1
  if (str[0] === '+' || str[0] === '-') start = 1
 
  for (let i = start; i < str.length; i++) {
    if (str[i] >= '0' || str[i] <= '9') {
      res = res * 10 + (str[i] - '0')
    }
  }

  res = sign * res
  let max_int = 2 ** 31 - 1
  let min_int = (-2) ** 31

  if (res > max_int) return max_int
  else if (res < min_int) return min_int
  
  return res
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值