lee-code 算法训练 树、二叉树

107 阅读4分钟

5、哈希表、映射、集合的实现与特性。

哈希表,也加散列表,是根据关键码值而直接进行访问的数据结构。 哈希表,哈希函数 - 查询、插入、删除都是O(1) 时间复杂度。 复杂度 search O(1) 添加 O(1) 删除 O(1) 红黑树都是O(log(n))

1、普通方式
function isAnagram(s, t){
	if(s.length !== t.length) return false
	s = s.split('').sort().join('')
	t = t.split('').sort().join('')
    return s === t
}
2、哈希表形式
从26个字符角度统计
function isAnagram(s, t){
    if(s.length !== t.length){
    	return false;
    }
    const tableArr = new Array(26).fill(0)
    let _flagCodeNum = 'a'.codePointAt(0)
    for(let i=0;i<s.length;i++){
    	tableArr[s[i].codePointAt(0) - _flagCodeNum]++;
    }
    for(let i=0;i<t.length;i++){
    	tableArr[t[i].codePointAt(0) - _flagCodeNum]--;
        if(tableArr[t[i].codePointAt(0) - _flagCodeNum] < 0){
        	return false
        }
    }
    return true
}

function twoNum(arr, target){
    let hash = {}
    for(let i=0;i<arr.length;i++){
        let another = target - arr[i]
        // 可能为0 查询差值
        if(hash[another] !== undefined){
            return [hash[another], i]
        }
        // 存入初始值
        hash[arr[i]] = i
    }
}
// 1
var groupAnagrams = function(strs, h = {}) {
    for (var str of strs) {
        var key = Array.from(str).sort().join()
        h[key] ? h[key].push(str) : h[key] = [str]
    }
    return Object.values(h)
}
// 2 charCodeAt字母的ASCII码
var groupAnagrams = function(strs, h = {}) {
    for (var str of strs) {
        for (var i = str.length, p = new Array(26).fill(0); i--;)
            p[str[i].charCodeAt() - 97]++
        key = p.toString()
        h[key] ? h[key].push(str) : h[key] = [str]
    }
    return Object.values(h)
}
// 3
var groupAnagrams = function(strs) {
    let hash = new Map()
    
    for(let i = 0; i < strs.length; i++) {
        let str = strs[i].split('').sort().join()
        if(hash.has(str)) {
            let temp = hash.get(str)
            temp.push(strs[i])
            hash.set(str, temp)
        } else {
            hash.set(str, [strs[i]])
        }
    }
    
    return [...hash.values()]
};


tips: 养成收藏精选代码的习惯

6、树、二叉树、二叉搜索树

链表是特殊化的树,树(只有两个子节点)是特殊化的图(有环)

  • 前序遍历 根-左-右
  • 中序遍历 左-根-右
  • 后续遍历 左-右-根

二叉搜索树,也称为有序二叉树,排序二叉树,是指一个空树,或者具有下列性质的二叉树,

1、左子树上所有结点的值均小于它的根结点的值。

2、右子树上所有结点的值均大于它的根结点的值。

3、以此类推

查询、插入、删除都是log(n)。极端情况下是O(n)。所以加速了。二分查找,log(2n)

// 遍历理论值
var treeArr = [1, null, 2,3, null,4,5,6,7] 
1、前 根-左-右  1245367
2、中 左-根-右  4251637
3、后 左-右-根  4526731
1、递归
const preorder = (root) => {
    const res = []
    const preOrder = (root) => {
    	if(root === null) return 
        // arr 对应位置,即:前中后
        res.push(root.val)
        preOrder(root.left)
        preOrder(root.right)
    }
    preOrder(root)
    return res
}
const preorder = (root, array = []) => {
	if(root){
    	array.push(root.val)
        preorder(root.left, array)
        preorder(root.right, array)
    }
}

2、栈、迭代
// 0.取根节点为目标节点,开始遍历
// 1.访问目标节点
// 2.左孩子入栈 -> 直至左孩子为空的节点
// 3.节点出栈,以右孩子为目标节点,再依次执行1、2、3
const preorder2 = (root) => {
    const stack = []
    const arr = []
    while(root || stack.length){
    	while(root){
            arr.push(root.val)
            stack.push(root)
            root = root.left
        }
        root = stack.pop()
        root = root.right
    }
    return arr
}

1、递归
var inorderTraver = function(root){
    const arr = []
    const inorder = (root)=>{
    	if(!root) return;
        // arr 对应前中后
        inorder(root.left)
        arr.push(root.val)
        inorder(root.right)
    }
    inorder(root)
    return arr;
}
2、栈、迭代
// 取跟节点为目标节点,开始遍历
// 1.左孩子入栈 -> 直至左孩子为空的节点
// 2.节点出栈 -> 访问该节点
// 3.以右孩子为目标节点,再依次执行1、2、3
var inorderTraver = function(root){
    const arr = []
    const skt = []
    while(root || skt.length){
    	while(root){
            skt.push(root)
            root = root.left
        }
        root = skt.pop()
        arr.push(root.val)
        root = root.right
    }
    return arr
}
# 取跟节点为目标节点,开始遍历
# 1.左孩子入栈 -> 直至左孩子为空的节点
# 2.栈顶节点的右节点为空或右节点被访问过 -> 节点出栈并访问他,将节点标记为已访问
# 3.栈顶节点的右节点不为空且未被访问,以右孩子为目标节点,再依次执行1、2、3
var postorder = function(root){
    const arr = []
    const skt = []
    let prev = null
    
    while(root || skt.length){
        while(root){
            skt.push(root)
            root = root.left
        }
        root = skt.pop()
        if(root.right === null || root.right === prev){
            arr.push(root.val)
            prev = root
            root = null
        } else {
        	skt.push(root)
            root = root.right
        }
    }
    return arr
}

    var postorderTraversal2 = function (root) {
      const result = [];
      const stack = [];
      let last = null; // 标记上一个访问的节点
      let current = root;
      while (current || stack.length > 0) {
        while (current) {
          stack.push(current);
          current = current.left;
        }
        current = stack[stack.length - 1];
        if (!current.right || current.right == last) {
          current = stack.pop();
          result.push(current.val);
          last = current;
          current = null; // 继续弹栈
        } else {
          current = current.right;
        }
      }
      return result;
    }
// 递归算法
var postorder = function(root) {
    if (!root) return []
    function houxu (root, res) {
        if (!root) {
            return []
        }
        if (root.children) {
            root.children.map(child => houxu(child, res))
        }
        res.push(root.val)
        return res
    }
    return houxu(root, [])
};

// 利用栈迭代算法
var postorder = function(root) {
    if (!root) return []
    let stack = [root]
    let result = []
    while (stack.length) {
        let node = stack.pop()
        if (node) {
            result.unshift(node.val)
        }
        if (node.children) {
            stack.push(...node.children)
        }
    }
    return result
};

二叉树节点插入js 实现

function Node(value) {
  this.value = value
  this.left = null
  this.right = null
}

function BinaryTree() {
  this.root = null // 树根
  this.queue = [] // 存储会被使用的父节点
  this.insertNum = 0 // 记录插入操作的次数
}

BinaryTree.prototype.insert = function (value) {
  this.insertNum++ // 插入次数加1
    let node = new Node(value)
  if (!this.root) { // 判断根节点是否存在
    this.root = node // 插入根节点
    this.queue.push(this.root) // 新节点入列
  } else { // 插入非根节点
    let parent = this.queue[0] // 被插入的父节点
    if (!(this.insertNum % 2)) { // 通过插入次数判断左右
      parent.left = node // 插入左边
      this.queue.push(parent.left) // 新节点入列
    } else {
      parent.right = node // 插入右边
      this.queue.push(parent.right) // 新节点入列
      this.queue.shift() // 当前父节点parent 已经不可能再插入子节点,故出列
    }
  }
  return this
}

let binaryTree = new BinaryTree()
binaryTree.insert('A')
  .insert('B')
  .insert('C')
  .insert('D')
  .insert('E')
  .insert('F')

console.log(JSON.stringify(binaryTree.root, null, 4))

www.jianshu.com/p/dc04871ea…

数值插入

function BinaryTree(){
    // 定义节点
    var Node = function(key){
        this.key = key;
        this.left = null;
        this.right = null;
    }
    // 初始化根节点
    var root = null;
    // 插入节点
    this.insert = function(key){
        // 实例化node节点
        var newNode = new Node(key);
        // 根节点为空,便将newNode赋给root节点
        if (root === null) {
            root = newNode;
        } else {
        // 根节点存在,插入新节点
            insertNode(root, newNode);
        };
    }
    // 插入节点(中序遍历)
    var insertNode = function(node, newNode){
        // node 当前节点
        // newNode 新节点
        // 若newNode小于node,则插入到node的左侧
        if(newNode.key < node.key){
            // 若node的左孩子为空,则插入为node的左孩子
            if(node.left === null){
                node.left = newNode;
            } else {
            // 若node的左孩子不为空,则继续去和node的左孩子比较进行插入
                insertNode(node.left, newNode);
            }
        }else {
            if(node.right === null){
                node.right = newNode;
            }else{
                insertNode(node.right, newNode);
            }
        }
    }
}
var nodes = [2, 5, 4, 1, 3, 6]; 
var binaryTree = new BinaryTree(); // 将数组中的每个元素插入到二叉树中 
nodes.forEach(function(key){ 
    binaryTree.insert(key); 
})
function Node(data,left,right) {
    this.data = data;
    this.left = left;
    this.right = right;
    this.show = show;
}

function show() {
    return this.data;
}

function BST() {
    this.root = null;
    this.insert = insert;
    this.inOrder = inOrder;
    this.getMin = getMin;
    this.getMax = getMax;
    this.find = find;
    this.remove = remove;
}

function insert(data) {
    var n = new Node(data,null,null);
    if(this.root == null) {
        this.root = n;
    }else {
        var current = this.root;
        var parent;
        while(current) {
            parent = current;
            if(data <  current.data) {
                current = current.left;
                if(current == null) {
                    parent.left = n;
                    break;
                }
            }else {
                current = current.right;
                if(current == null) {
                    parent.right = n;
                    break;
                }
            }
        }
    }
}
// 中序遍历
function inOrder(node) {
    if(!(node == null)) {
        inOrder(node.left);
        console.log(node.show());
        inOrder(node.right);
    }
}

// 先序遍历 
function preOrder(node) {
    if(!(node == null)) {
        console.log(node.show());
        preOrder(node.left);
        preOrder(node.right);
    }
}

// 后序遍历
function postOrder(node) {
    if(!(node == null)) {
        postOrder(node.left);
        postOrder(node.right);
        console.log("后序遍历"+node.show());
    }
}

// 二叉树查找最小值
function getMin(){
    var current = this.root;
    while(!(current.left == null)) {
        current = current.left;
    }
    return current.data;
}

// 二叉树上查找最大值
function getMax() {
    var current = this.root;
    while(!(current.right == null)) {
        current = current.right;
    }
    return current.data;
}

// 查找给定值
function find(data) {
    var current = this.root;
    while(current != null) {
        if(current.data == data) {
            return current;
        }else if(data < current.data) {
            current = current.left;
        }else {
            current = current.right;
        }
    }
    return null;
}

function remove(data) {
    root = removeNode(this.root,data);
}
function getSmallest(node) {
   if (node.left == null) {
      return node;
   }
   else {
      return getSmallest(node.left);
   }
}
function removeNode(node,data) {
    if(node == null) {
        return null;
    }
    if(data == node.data) {
        // 没有子节点的节点
        if(node.left == null && node.right == null) {
            return null;
        } 
        // 没有左子节点的节点
        if(node.left == null) {
            return node.right;
        }
        // 没有右子节点的节点
        if(node.right == null) {
            return node.left;
        }
        // 有2个子节点的节点
        var tempNode = getSmallest(node.right);
        node.data = tempNode.data;
        node.right = removeNode(node.right,tempNode.data);
        return node;
    }else if(data < node.data) {
        node.left = removeNode(node.left,data);
        return node;
    }else {
        node.right = removeNode(node.right,data);
        return node;
    }
}
代码初始化如下:
var nums = new BST();
nums.insert(23);
nums.insert(45);
nums.insert(16);
nums.insert(37);
nums.insert(3);
nums.insert(99);
nums.insert(22);
var min = nums.getMin();
console.log(min);
var max = nums.getMax();
console.log(max);
var value = nums.find("45");
console.log(value);
nums.remove(23);

分治(尝试分步去解决一个问题)、回溯的的思路

分治模板

Javascript
const divide_conquer = (problem, params) => {
  // recursion terminator
  if (problem == null) {
    process_result
    return
  } 
  // process current problem
  subproblems = split_problem(problem, data)
  subresult1 = divide_conquer(subproblem[0], p1)
  subresult2 = divide_conquer(subproblem[1], p1)
  subresult3 = divide_conquer(subproblem[2], p1)
  ...
  // merge
  result = process_result(subresult1, subresult2, subresult3)
  // revert the current level status

}

数据结构 - 堆

堆的底层实际上是一棵完全二叉树,可以用数组实现

  • 每个的节点元素值不小于其子节点 - 最大堆
  • 每个的节点元素值不大于其子节点 - 最小堆 堆在处理某些特殊场景时可以大大降低代码的时间复杂度,例如在庞大的数据中找到最大的几个数或者最小的几个数,可以借助堆来完成这个过程。
    function Heap(type = 'min') {
      this.type = type;
      this.value = [];
    }

    Heap.prototype.create = function () {
      const length = this.value.length;
      for (let i = Math.floor((length / 2) - 1); i >= 0; i--) {
        this.ajust(i, length);
      }
    }

    Heap.prototype.ajust = function (index, length) {
      const array = this.value;
      for (let i = 2 * index + 1; i < length; i = 2 * i + 1) {
        if (i + 1 < length) {
          if ((this.type === 'max' && array[i + 1] > array[i]) ||
            (this.type === 'min' && array[i + 1] < array[i])) {
            i++;
          }
        }
        if ((this.type === 'max' && array[index] < [array[i]]) ||
          (this.type === 'min' && array[index] > [array[i]])) {
          [array[index], array[i]] = [array[i], array[index]];
          index = i;
        } else {
          break;
        }
      }
    }

    Heap.prototype.add = function (element) {
      const array = this.value;
      array.push(element);
      if (array.length > 1) {
        let index = array.length - 1;
        let target = Math.floor((index - 1) / 2);
        while (target >= 0) {
          if ((this.type === 'min' && array[index] < array[target]) ||
            (this.type === 'max' && array[index] > array[target])) {
            [array[index], array[target]] = [array[target], array[index]]
            index = target;
            target = Math.floor((index - 1) / 2);
          } else {
            break;
          }
        }
      }
    }

    Heap.prototype.pop = function () {
      const array = this.value;
      let result = null;
      if (array.length > 1) {
        result = array[0];
        array[0] = array.pop();
        this.ajust(0, array.length);
      } else if (array.length === 1) {
        return array.pop();
      }
      return result;
    }

    var heap = new Heap('max');
    heap.add(6)
    heap.add(10)
    console.log(heap.value);
    console.log(heap.pop());
    console.log(heap.value);