左神基础算法 js实现

116 阅读6分钟

结点

class Node {
  constructor(value) {
    this.value = value;
    this.next = null;
  }
}

  class Stack {
  constructor() {
    this.items = [];
  }
  push(el) {
    this.items.push(el);
  }
  pop() {
    return this.items.pop();
  }
  peek() {
    return this.items[this.items.length - 1];
  }
  isEmpty() {
    return this.items.length === 0;
  }
}

队列

class Queue {
  constructor() {
    this.items = [];
  }
  enqueue(el) {
    this.items.push(el);
  }
  dequeue() {
    return this.items.shift();
  }
  isEmpty() {
    return this.items.length == 0;
  }
}

class TreeNode {
  value;
  left;
  right;
  constructor(value) {
    this.value = value;
  }
}

1.二叉树的遍历(递归)

function pre(head) {
  if (head == null) {
    return;
  }
  console.log(head.value + "");
  pre(head.left);
  pre(head.right);
}
function inOrder(head) {
  if (head == null) {
    return;
  }
  inOrder(head.left);
  console.log(head.value + "");
  inOrder(head.right);
}
function posOrder(head) {
  if (head == null) {
    return;
  }
  posOrder(head.left);
  posOrder(head.right);
  console.log(head.value + "");
}

(非递归)

function preOrder(head) {
  if (head != null) {
    let stack = new Stack();
    stack.push(head);
    while (!stack.isEmpty()) {
      head = stack.pop();
      console.log(head.value + " ");
      if (head.right != null) {
        stack.push(head.right);
      }
      if (head.left != null) {
        stack.push(head.left);
      }
    }
  }
}
function inOrder2(head) {
  console.log("非递归中序");

  if (head != null) {
    let stack = new Stack();
    while (!stack.isEmpty() || head != null) {
      if (head != null) {
        stack.push(head);
        head = head.left;
      } else {
        head = stack.pop();
        console.log(head.value);
        head = head.right;
      }
    }
  }
}
后序 使用两个栈
function posOrder2(head) {
  console.log("非递归后序");
  if (head != null) {
    let s1 = new Stack();
    let s2 = new Stack();
    s1.push(head);
    while (!s1.isEmpty()) {
      head = s1.pop();
      s2.push(head);
      if (head.left != null) {
        s1.push(head.left);
      }
      if (head.right != null) {
        s1.push(head.right);
      }
    }
    while (!s2.isEmpty()) {
      console.log(s2.pop().value + " ");
    }
  }
}
后序 使用一个栈
function posOrder3(h) {
  console.log("非递归后序 一个栈"); //只要h不为c的左右,说明c以下还没遍历,c为左不为右就push右
  if (h != null) {
    let stack = new Stack();
    stack.push(h);
    let c = null;
    while (!stack.isEmpty()) {
      c = stack.peek();
      if (c.left != null && h != c.left && h != c.right) {
        stack.push(c.left);
      } else if ((c.right != null) & (h != c.right)) {
        stack.push(c.right);
      } else {
        console.log(stack.pop().value + " ");
        h = c;
      }
    }
  }
}
    • 实现二叉树的按层遍历
    • 其实就是宽度优先遍历,用队列
    • 可以通过设置flag变量的方式,来发现某一层的结束
function level(head) {
  if (head == null) {
    return;
  }
  let queue = new Queue();
  queue.enqueue(head);
  while (!queue.isEmpty()) {
    let cur = queue.dequeue();
    console.log(cur.value);
    if (cur.left != null) {
      queue.enqueue(cur.left);
    }
    if (cur.right != null) {
      queue.enqueue(cur.right);
    }
  }
}

3.计算树的最大宽度

function  maxWidthUseMap(head) {
  if (head == null) {
    return 0;
  }
  let queue = new Queue();
  queue.enqueue(head);
  let levelMap = new Map();
  levelMap.set(head, 1);
  let curLevel = 1; //当前统计哪一层的宽度
  let curLevelNodes = 0; //当前层宽度目前是多少
  let max = 0;
  while (!queue.isEmpty()) {
    let cur = queue.dequeue();
    let curNodeLevel = levelMap.get(cur);
    if (cur.left != null) {
      levelMap.set(cur.left, curNodeLevel + 1);
      queue.enqueue(cur.left);
    }
    if (cur.right != null) {
      levelMap.set(cur.right, curNodeLevel + 1);
      queue.enqueue(cur.right);
    }
    if (curNodeLevel == curLevel) {
      curLevelNodes++;
    } else {
      max = Math.max(max, curLevelNodes);
      curLevel++;
      curLevelNodes = 1;
    }
  }
  max = Math.max(max, curLevelNodes);

  return max;
}
function maxWidthNoMap() {
  if (head == null) {
    return 0;
  }
  let queue = new Queue();
  queue.enqueue(head);
  let curEnd = head; //当前层,最右节点是谁
  let nextEnd = null; //下一层,最右节点是谁
  let max = 0;
  let curLevelNodes = 0; //当前层的节点数
  while (!queue.isEmpty()) {
    let cur = queue.dequeue();
    if (cur.left != null) {
      queue.enqueue(cur.left);
      nextEnd = cur.left;
    }
    if (cur.right != null) {
      queue.enqueue(cur.right);
      nextEnd = cur.right;
    }
    curLevelNodes++;
    if (cur == curEnd) {
      max = Math.max(max, curLevelNodes);
      curLevelNodes = 0;
      curEnd = nextEnd;
    }
  }
  return max;
}

4.序列化和反序列化

//序列化
//先序遍历序列化
function preSerial(head) {
  let ans = new Queue();
  pres(head, ans);
  return ans;
}
function pres(head, ans) {
  if (head == null) {
    ans.enqueue(null);
  } else {
    ans.enqueue(String(head.value));
    pres(head.left, ans);
    pres(head.right, ans);
  }
}
console.log(preSerial(head));
//后序遍历序列化
function posSerial(head) {
  let ans = new Queue();
  poss(head, ans);
  return ans;
}
function poss(head, ans) {
  if (head == null) {
    ans.enqueue(null);
  } else {
    poss(head.left, ans);
    poss(head.right, ans);
    ans.enqueue(String(head.value));
  }
}
//层序遍历序列化
function levelSerial(head) {
  let ans = new Queue();
  if (head == null) {
    ans.enqueue(null);
  } else {
    ans.enqueue(String(head.value));
    let queue = new Queue();
    queue.enqueue(head);
    while (!queue.isEmpty()) {
      let cur = queue.dequeue();
      if (cur.left != null) {
        ans.enqueue(String(cur.left.value));
        queue.enqueue(cur.left);
      } else {
        ans.enqueue(null);
      }
      if (cur.right != null) {
        ans.enqueue(String(cur.right.value));
        queue.enqueue(cur.right);
      } else {
        ans.enqueue(null);
      }
    }
  }
  return ans;
}
//前序反序列化
function buildByPreQueue(prelist) {
  if (prelist == null || prelist.isEmpty()) {
    return null;
  }
  return preb(prelist);
}
function preb(prelist) {
  let value = prelist.dequeue();

  if (value == null) {
    return null;
  }
  let head = new TreeNode(Number(value));
  head.left = preb(prelist);
  head.right = preb(prelist);

  return head;
}
//后序反序列化
function buildByPosQueue(poslist) {
  if (poslist == null || poslist.isEmpty()) {
    return null;
  }
  //左右中-》stack(中右左)
  let stack = new Stack();
  while (!poslist.isEmpty()) {
    stack.push(poslist.dequeue());
  }
  return posb(stack);
}
function posb(posstack) {
  let value = posstack.pop();
  if (value == null) {
    return null;
  }
  let head = new TreeNode(Number(value));
  head.right = posb(posstack);
  head.left = posb(posstack);
  return head;
}
//层序遍历反序列化
function buildByLevelQueue(levelList) {
  if (levelList == null || levelList.isEmpty()) {
    return null;
  }
  let head = generateNode(levelList.dequeue());
  let queue = new Queue();
  if (head != null) {
    queue.enqueue(head);
  }
  let node = null;
  while (!queue.isEmpty()) {
    node = queue.dequeue();
    node.left = generateNode(levelList.dequeue());
    node.right = generateNode(levelList.dequeue());
    if (node.left != null) {
      queue.enqueue(node.left);
    }
    if (node.right != null) {
      queue.enqueue(node.right);
    }
  }
   return head;
}
function generateNode(val) {
  if (val == null) {
    return null;
  }
  return new TreeNode(Number(val));
}
  1. 题目:二叉树结构如下定义: Class Node{ V value; Node left; Node right; Node parent; } 给你二叉树中的某个节点,返回该节点的后继节点(后继节点指的是在中序遍历中一个节点的下一个节点)
function getNextNode(node) {
  if (node == null) {
    return node;
  }
  if (node.right != null) {
    //如果node有右子树,那么node的后继节点就是右子树中的最左节点
    return getLeftMost(node.right);
  } else {
    let parent = node.parent;
    while (parent != null && parent.right == node) {//如果node没有右孩子,那就看node是不是parent的左孩子,如果是,那么parent就是node的后继;如果node是parent的右孩子,就往上找,直到parent是parent的parent的左孩子,那么parent的parent就是后继
      node = parent;
      parent = node.parent;
    }
    return parent;
  }
}
function getLeftMost(node) {
  //寻找最左节点
  if (node == null) {
    return node;
  }
  while (node.left != null) {
    node = node.left;
  }
  return node;
}

链表相关

1.快慢指针题

//奇数长度返回中点,偶数长度返回中上点
function midOrUpMidNode(head) {
  if (head == null || head.next == null || head.next.next == null) {
    return head;
  }
  //链表有3个点或以上
  let slow = head.next;
  let fast = head.next.next;
  while (fast.next != null && fast.next.next != null) {
    slow = slow.next;
    fast = fast.next.next;
  }
  return slow;
}
//奇数中偶数中下点
function midOrDownMidNode(head) {
  if (head == null || head.next == null) {
    return head;
  }
  let slow = head.next;
  let fast = head.next;
  while (fast.next != null && fast.next.next != null) {
    slow = slow.next;
    fast = fast.next.next;
  }
  return slow;
}
//奇数中前一个偶数上中点前一个
function midOrUpMidPreNode(head) {
  if (head == null || head.next == null || head.next.next == null) {
    return null;
  }
  let slow = head;
  let fast = head.next.next;
  while (fast.next != null && fast.next.next != null) {
    slow = slow.next;
    fast = fast.next.next;
  }
  return slow;
}
//奇数中前一个偶数下中点前一个
function midOrDownMidPreNode(head) {
  if (head == null || head.next == null) {
    return null;
  }
  if (head.next.next == null) {
    return head;
  }
  let slow = head;
  let fast = head.next;
  while (fast.next != null && fast.next.next != null) {
    slow = slow.next;
    fast = fast.next.next;
  }
  return slow;
}

2.判断一个链表是不是回文结构

 // need n extra space
function isPalindrome1(head) {
  let stack = new Stack();
  let cur = head;
  while (cur != null) {
    stack.push(cur);
    cur = cur.next;
  }
  while (head != null) {
    if (head.value != stack.pop().value) {
      return false;
    }
    head = head.next;
  }
  return true;
}
  // need n/2 extra space
function isPalindrome2(head) {
  if (head == null || head.next == null) {
    return true;
  }
  let right = head.next;
  let cur = head;
  while (cur.next != null && cur.next.next != null) {//个人理解:其实就是快慢指针取奇数的中下和偶数的中下第一个,进栈和前半段进行对比
    right = right.next;
    cur = cur.next.next;
  }
  let stack = new Stack();
  while (right != null) {
    stack.push(right);
    right = right.next;
  }
// need O(1) extra space
function isPalindrome3(head) {
  if (head == null || head.next == null) {
    return true;
  }
  let n1 = head;
  let n2 = head;
  while (n2.next != null && n2.next.next != null) {//如果奇数,找到中点 偶数找到中上点 
    n1 = n1.next;
    n2 = n2.next.next;
  }
  //n1中点
  n2 = n1.next;  //n1的下一个点就是下半段的第一个点
  n1.next = null;
  let n3 = null;
  //反转链表 保存好n2的下一个,然后断开,反过来,然后右移
  while (n2 != null) {
    n3 = n2.next;
    n2.next = n1;
    n1 = n2;
    n2 = n3;
  }
  n3 = n1;//保存最后一个节点
  n2 = head;//令n2为左链表的第一个节点
  let res = true;
  while (n1 != null && n2 != null) {
    if (n1.value != n2.value) {
      res = false;
      break;
    }
    n1 = n1.next;
    n2 = n2.next;
  }

  n1 = n3.next;
  n3.next = null;
  while (n1 != null) {
    n2 = n1.next;
    n1.next = n3;
    n3 = n1;
    n1 = n2;
  }
  return res;
}

3.给定一个单向链表头结点head,再给定一个整数pivot,实现调整链表为左中右三部分。对调整后的结点顺序没有要求 把链表放入数组,在数组上做partition 笔试用 分成小。中。大三部分,再把各个部分串起来 面试用


//1.把链表放入数组,在数组上做partition 笔试用
function listPartition1(head, pivot) {
  if (head == null) {
    return head;
  }
  let cur = head;
  let i = 0;
  while (cur != null) {
    //计算链的长度
    i++;
    cur = cur.next;
  }
  let nodeArr = [];
  nodeArr.length = i;
  i = 0;
  cur = head;
  for (i = 0; i != nodeArr.length; i++) {
    nodeArr[i] = cur;
    cur = cur.next;
  }

  arrPartition(nodeArr, pivot);
  //再串起来
  for (i = 1; i != nodeArr.length; i++) {
    nodeArr[i - 1].next = nodeArr[i];
  }
  nodeArr[i - 1].next = null;

  return nodeArr[0];
}
function arrPartition(nodeArr, pivot) {
  let small = -1;
  let big = nodeArr.length;
  let index = 0;
  while (index != big) {
    if (nodeArr[index].value < pivot) {
      swap(nodeArr, ++small, index++);
    } else if (nodeArr[index].value == pivot) {
      index++;
    } else {
      swap(nodeArr, --big, index);
    }
  }
}
function swap(nodeArr, a, b) {
  let tmp = nodeArr[a];
  nodeArr[a] = nodeArr[b];
  nodeArr[b] = tmp;
}
//分成三部分链表再连接起来
function listPartition2(head, pivot) {
  let sH = null;
  let sT = null;
  let eH = null;
  let eT = null;
  let mH = null;
  let mT = null;
  let next = null;
  while (head != null) {
    next = head.next;
    head.next = null;
    if (head.value < pivot) {
      if (sH == null) {
        sH = head;
        sT = head;
      } else {
        sT.next = head;
        sT = head;
      }
    } else if (head.value == pivot) {
      if (eH == null) {
        eH = head;
        eT = head;
      } else {
        eT.next = head;
        eT = head;
      }
    } else {
      if (mH == null) {
        mH = head;
        mT = head;
      } else {
        mT.next = head;
        mT = head;
      }
    }
    head = next;
  }
  // 小于区域的尾巴,连等于区域的头,等于区域的尾巴连大于区域的头
  if (sT != null) {
    //如果有小于区域
    sT.next = eH;
    eT = eT == null ? sT : eT; // 下一步,谁去连大于区域的头,谁就变成eT
  }
  // 下一步,一定是需要用eT 去接 大于区域的头
  // 有等于区域,eT -> 等于区域的尾结点
  // 无等于区域,eT -> 小于区域的尾结点
  // eT 尽量不为空的尾巴节点
  if (eT != null) {
    eT.next = mH;
  }
  return sH != null ? sH : eH != null ? eH : mH;
}
  1. 一种特殊的单链表节点描述: class node{ int value; Node next; Node rand; Node(int val){value=val} } rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null 给定一个由Node节点类型组成的无环单链表的头结点head,请实现一个函数完成这个链表的赋值,并返回复制的新链表的头结点
class rnode {
  val;
  next;
  random;
  constructor(val) {
    this.val = val;
    this.next = null;
    this.random = null;
  }
}
function copyRandomList1(head) {
  //key 老节点
  //value新节点
  map = new Map();
  let cur = head;
  while (cur != null) {
    map.set(cur, new rnode(cur.val));
    cur = cur.next;
  }
  cur = head;
  while (cur != null) {
    //cur老
    //map.get(cur)新
    //新.next->cur.next 克隆节点找到
    map.get(cur).next = map.get(cur.next);
    map.get(cur).random = map.get(cur.random);
    cur = cur.next;
  }
  return map.get(head);
}
function copyRandomList2(head) {
  if (head == null) {
    return null;
  }
  let cur = head;
  let next = null;
  // 1 -> 2 -> 3 -> null
  // 1 -> 1' -> 2 -> 2' -> 3 -> 3'
  while (cur != null) {
    next = cur.next;
    cur.next = new rnode(cur.val);
    cur.next.next = next;
    cur = next;
  }
  cur = head;
  let copy = null;
  // 1 1' 2 2' 3 3'
  // 依次设置 1' 2' 3' random指针
  while (cur != null) {
    next = cur.next.next;
    copy = cur.next;
    copy.random = cur.random != null ? cur.random.next : null;
    cur = next;
  }
  let res = head.next;
  cur = head;
  // 老 新 混在一起,next方向上,random正确
  // next方向上,把新老链表分离
  while (cur != null) {
    next = cur.next.next;
    copy = cur.next;
    cur.next = next;
    copy.next = next != null ? next.next : null;
    cur = next;
  }
  return res;
}