数据结构与LeetCode

225 阅读3分钟

20 有效括号
使用栈模拟。

/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function (s) {
    const n = s.length;
    if(n%2===1){
        return false;
    }
    const arr = [];
    for(let ch of s){
        if(ch==='{'||ch==='('||ch==='['){
            arr.push(ch);
        }else{
            if(arr.length===0){
                return false;
            }else{
                const match = getMatch(ch);
                const tmp = arr.pop();
                if(tmp!==match){
                    return false;
                }
            }
        }
    }
    if(arr.length>0){
        return false;
    }
    return true;
};
function getMatch(ch){
    if(ch==='}'){
        return '{'
    }else if(ch===')'){
        return '('
    }else{
        return '['
    }
}

链表

创建链表的方法:

function create(arr) {
    if (arr.length == 0) {
        return null;
    }
    let head = {
        val: arr[0],
        next: null
    };
    let cur = head;
    for (let i = 1; i < arr.length; i++) {
        cur.next = {
            val: arr[i],
            next: null
        };
        cur = cur.next;
    }
    return head;
}

206 反转链表
定义了三个指针,根据 cur 的值,依次向后移动三个指针。

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    if(head===null){
        return null;
    }
    let pre = null;
    let cur = head;
    while(cur!==null){
        let next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
    return pre;
};

2 两数相加
方法一:利用数字相加然后转换为链表返回

/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function(l1, l2) {
    let s1 = JSON.stringify(l1).match(/\d/g).reverse().join(''),
        s2 = JSON.stringify(l2).match(/\d/g).reverse().join('')

    sum = BigInt(s1)+BigInt(s2)

    return [...sum.toString()].reduce((acc,v)=>{return {val: v, next: acc}}, null)
};

方法二:遍历 l1 和 l2, 生成新的链表。

/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function (l1, l2) {
    let sum = 0;
    let index = 0;
    let head = null;
    let cur = {
        val: 0,
        next: null
    };
    while (l1 != null || l2 != null || sum > 0) {
        sum += (l1 != null ? l1.val : 0) + (l2 != null ? l2.val : 0);
        let curVal = 0;
        if (sum >= 10) {
            curVal = sum % 10;
            sum = parseInt(sum / 10);
        } else {
            curVal = sum;
            sum = 0;
        }
        cur.val = curVal;
        cur.next = {
            val: 0,
            next: null
        }
        if (index === 0) {
            head = cur;
        }
        if (l1 != null) {
            l1 = l1.next;
        }
        if (l2 != null) {
            l2 = l2.next;
        }
        if(l1 != null || l2 != null || sum > 0){
            cur = cur.next;
        }
        index++;
    }
    cur.next = null;
    return head;
};

203 移除链表中的元素
虚拟节点法:

/**
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
var removeElements = function(head, val) {
    let pirot = {val: 0, next: head};
    let pre = pirot;
    while(head!=null){
        if(head.val===val){
            pre.next = head.next;
            head = head.next;
        }else{
            pre = head;
            head = head.next;
        }
    }
    return pirot.next;
};

数组

704 二分查找

var search = function(nums, target) {
    let start = 0;
    let end = nums.length-1;
    while(end>=start){
        const pirot = start + parseInt((end - start)/2);
        if(nums[pirot]>target){
            end = pirot-1;
        }else if(nums[pirot]<target){
            start = pirot+1;
        }else{
            return pirot;
        }
    }
    return -1;
};

283 移动零

/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var moveZeroes = function(nums) {
    let key = 0;
    for(let i=0;i<nums.length;i++){
        if(nums[i]!=0){
            if( i!= key ){
                let tmp = nums[key];
                nums[key] = nums[i];
                nums[i] = tmp;
            }
            key++;
        }
    }
};

27 移除元素

/**
 * @param {number[]} nums
 * @param {number} val
 * @return {number}
 */
var removeElement = function(nums, val) {
    let i = 0;
    while(i<nums.length){
        const tmp = nums[i];
        if(tmp==val){
            nums.splice(i, 1);
        }else{
            i++;
        }
    }
    return nums.length;
};

75 颜色分类

//方法一
/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var sortColors = function(nums) {
    let count0 = 0;
    let count1 = 0;
    let count2 = 0;
    for(let i=0;i<nums.length;i++){
        const tmp = nums[i];
        if(tmp==0){
            count0++;
        }else if(tmp==1){
            count1++;
        }else{
            count2++;
        }
    }
    for(let i=0;i<count0;i++){
        nums[i] = 0;
    }
    for(let i=count0;i<count0+count1;i++){
        nums[i] = 1;
    }
    for(let i=count0+count1;i<count0+count1+count2;i++){
        nums[i] = 2;
    }
};

11 盛最多水的容器
可以使用双指针的解法。使用两个指针,一个在数组的最左边,一个在最右边。对于这道题目而言,怎么移动指针呢?只要移动数值最小的那个指针即可。为什么要移动最小的指针呢?因为题目要找的是盛最多水的容器。移动较大的指针就会发现之后的容器只会变小不会变大,所以要移动值较小的那个指针哦。

/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function(height) {
    let l = 0; 
    let r = height.length-1;
    let res = 0;
    while(l<r){
        let tmpl = height[l];
        let tmpr = height[r];
        let area = (r-l)*Math.min(tmpl, tmpr);
        if(tmpr<tmpl){
            r--;
        }else{
            l++;
        }
        res = Math.max(res, area);
    }
    return res;
};

88 215 167
3 无重复字符的最长子串
这里使用了滑动窗口的思想。因为题目要找的是连续子串,所以可以使用滑动窗口的方法。窗口左边一个变量右边一个变量。set 用来记录已经有的字符。首先,我们不断移动右边的指针,直到遇到set中已经存在的字符串或者到达最右边。这个时候要移动左边的指针;每次移动左边或者右边的时候,更新变量 res,保证它能及时更新到最大值。
滑动窗口的关键在于什么时候移动窗口的左边指针和右边指针。

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    let l = 0; 
    let r = -1;
    let res = 0;
    let set = new Set();
    while(l<s.length){
        if(r+1<s.length && !set.has(s.charAt(r+1))){
            r++;
            set.add(s.charAt(r));
        }else{
            set.delete(s.charAt(l));
            l++;
        }
        res = Math.max(res, r-l+1);
    }
    return res;
}

动态规划

将原问题拆解成若干子问题,同时保存子问题的答案,使得每个子问题只求解一次,最终获得原问题的答案。

题目

70 爬楼梯

//搜索记忆法,自顶向下解决问题
/**
 * @param {number} n
 * @return {number}
 */
let arr;
var climbStairs = function(n) {
    arr = new Array(n+1);
    return calc(n);
};
function calc(n){
    if(n==1){
        arr[1] = 1;
        return 1;
    }
    if(n==2){
        arr[2] = 2;
        return 2;
    }
    if(arr[n]==undefined){
        arr[n] = calc(n-1) + calc(n-2);
    }
    return arr[n];
}
//动态规划自底向上
/**
 * @param {number} n
 * @return {number}
 */
var climbStairs = function(n) {
    let arr = new Array(n+1);
    arr[1] = 1;
    arr[2] = 2;
    for(let i=3;i<=n;i++){
        arr[i] = arr[i-1] + arr[i-2];
    }
    return arr[n];
};

343 整数拆分
将任一整数 n 拆分成 i 和 n-i; 保持 i 不变,动态去找 n-i 的最大值。

/**
 * @param {number} n
 * @return {number}
 */
let arr;
function calc(n){
    if(n==1){
        return 1;
    }
    if(arr[n]!=undefined){
        return arr[n];
    }
    let res = -1;
    for(let i=1; i<n;i++){
        res = Math.max(res, i*(n-i), calc(i) * calc(n-i));
    }
    arr[n] = res;
    return res;
}
var integerBreak = function(n) {
    arr = new Array(n+1);
    return calc(n);
};
/**
 * @param {number} n
 * @return {number}
 */
var integerBreak = function(n) {
    let arr = new Array(n+1);
    arr[1] = 1;
    arr[2] = 1;
    for(let i=2; i<=n;i++){
        if(arr[i]==undefined){
                arr[i] = -1;
            }
        for(let j=1; j<i; j++){
            //i = j+ (i-j)
            arr[i] = Math.max(arr[i], j*(i-j), j* arr[i-j])
        }
    }
    return arr[n];
};

279 91 62 63
剑指 Offer 42. 连续子数组的最大和

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

二叉树

104 二叉树的最大深度

/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root) {
    if(root==null) return 0;
    return Math.max(maxDepth(root.left), maxDepth(root.right))+1;
};

111 二叉树的最低深度

/**
 * @param {TreeNode} root
 * @return {number}
 */
var minDepth = function(root) {
    if(root===null) return 0;
    if(root.left===null&&root.right===null) return 1;
    let min_depth = Number.MAX_VALUE;
    if(root.left){
        min_depth = Math.min(min_depth, minDepth(root.left))
    }
    if(root.right){
        min_depth = Math.min(min_depth, minDepth(root.right))
    }
    return min_depth+1;
};

226 翻转二叉树

/**
 * @param {TreeNode} root
 * @return {TreeNode}
 */
var invertTree = function(root) {
    if(root==null){
        return null;
    }
    let leftTree = invertTree(root.left);
    let rightTree = invertTree(root.right);
    root.left = rightTree;
    root.right = leftTree;
    return root;
};

100 相同的树

/**
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {boolean}
 */
var isSameTree = function(p, q) {
    if(p==null&&q==null){
        return true;
    }
    if(p!=null&&q!=null){
        if(p.val==q.val){
            return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
        }else{
            return false;
        }
    }
    return false;   
};

101 对称二叉树

/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isSymmetric = function(root) {
    return check(root, root);
};

function check(p, q){
    if(p==null&&q==null){
        return true;
    }
    if(p==null||q==null){
        return false;
    }
    return p.val==q.val && check(p.left, q.right)&&check(p.right, q.left);
}

222 完全二叉树的节点个数

/**
 * @param {TreeNode} root
 * @return {number}
 */
var countNodes = function(root) {
    if(root==null){
        return 0;
    }else{
        return 1 + countNodes(root.left)+ countNodes(root.right);
    }
};

110 平衡二叉树

/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isBalanced = function(root) {
    if(root==null){
        return true;
    }
    return Math.abs(getHeight(root.left) - getHeight(root.right))<2 &&
    isBalanced(root.left) && isBalanced(root.right);
};
function getHeight(root){
    if(root==null){
        return 0;
    }
    return Math.max(getHeight(root.left), getHeight(root.right)) +1;
}

112 路径总和

/**
 * @param {TreeNode} root
 * @param {number} sum
 * @return {boolean}
 */
var hasPathSum = function(root, sum) {
    if(root==null){
        return false;
    }
    if(root.left==null&&root.right==null){
        return sum-root.val ==0;
    }
    return hasPathSum(root.left, sum-root.val)||hasPathSum(root.right, sum-root.val);
};

404 左叶子之和

var sumOfLeftLeaves = function(root) {
    if(root==null){
        return 0;
    }
    let count = 0;
    if(root.left){
        if(root.left.left==null&&root.left.right==null){
            count += root.left.val;
        }else{
            count += sumOfLeftLeaves(root.left);
        }
    }
    if(root.right){
        count += sumOfLeftLeaves(root.right);
    }
    return count;
};

递归算法实现,需要确定递归的终止条件。
257 113 129

排序

冒泡排序

  function bubbleSort(arr){
      const l = arr.length;
      for(let i=0; i<l; i++){
          for(let j=0; j<l-i-1; j++){
              //相邻元素两两比较
              if(arr[j]>arr[j+1]){
                  let tmp = arr[j];
                  arr[j] = arr[j+1];
                  arr[j+1] = tmp;
              }
          }
      }
  }

www.cnblogs.com/onepixel/ar…

快速排序

  function quickSort(arr){
      if(arr.length===0) return [];
      const tmp = arr.pop();
      const left = [];
      const right = [];
      arr.forEach(item=>{
          if(item>tmp){
              right.push(item);
          }else{
              left.push(item);
          }
      })
      return quickSort(left).concat(tmp, quickSort(right));
  }

斐波那契数列

什么是斐波那契数列,1,1,2,3,5,8,13...这样一个数列就是斐波那契数列,求第n项的值。变种题目:爬楼梯。

递归

我们很容易写出下面的代码:

  function Fibonacci(n){
      if(n===1||n===2){
          return 1;
      }
      return Fibonacci(n-1) + Fibonacci(n-2);
  }

上面的代码看似完美,在变量 n 比较小的时候,计算还不成问题。但是我的电脑实验的 n >=50 的时候计算时间就很长了(大概是 98秒)。

为什么呢?我们要分析一下上述算法的时间复杂度了。时间复杂度是什么呢?可以简单理解为程序执行的次数。那么,上述算法的时间复杂度(执行次数)怎么计算呢?我们以计算 F(6) 为例分析:

第n层节点个数:2n{2^n}

前n层节点个数:1+2+4+……+2n{2^n} = 2ⁿ⁺¹-1

F(6)有4层,推理:F(n) 有 n-2层

则 F(n) 一共有节点数: 2n2+1\frac{2^n}{2}+1

计算时间复杂度规则:不看常数,不看系数,只看最高次数项

因此:O(F(n)) = O(2n{2 ^n}) 可以推算出这种算法的时间复杂度是 O(2n{2 ^n}), 空间复杂度是 O(n) (某一时刻,开辟的空间个数最多为 n )
www.pianshen.com/article/635…
blog.csdn.net/qq_43722079…

优化递归

仅仅使用暴力递归实现的问题是,比如计算 F(4) 要先计算 F(3) 和 F(2) ,而计算 F(3) 又要计算 F(2) 和 F(1),这就出现了重复计算的问题, F(2) 被计算了 2 次。所以效率比较低。那么怎么解决呢?我们可以使用一个数组存储每次计算的结果 F(1)、 F(2)、 F(3)... 这样可以大大减少计算的时间成本。

  let arr;
  function Fibonacci(n){
      arr = new Array(n+1);
      return getF(n);
  }
  function getF(n){
      if(n===1||n===2){
          arr[n] = 1;
      }
      if(arr[n]){
          return arr[n];
      }else{
          arr[n] = getF(n-1) + getF(n-2);
      }
      return arr[n];
  }

上述算法的时间复杂度降为 O(n), 同时空间复杂度是 O(n)。到这里继续反问下还可以继续优化吗? 答案是肯定的,请继续向下看。

循环实现

空间复杂度是 O(n) 还是可以继续优化的。那就是不要使用数组来存储所有的中间计算值,我们最后只需要知道 F(n-1) + F(n-2) 的值就可以了。那么代码可以优化如下:

  function Fibonacci(n){
      if(n===1||n===2){
          return 1;
      }
      let a = 1;
      let b = 1;
      for(let i=3;i<=n;i++){
          let tmp = a+b;
          a = b;
          b = tmp;
      }
      return b;
  }

上述算法的时间复杂度为 O(n), 同时空间复杂度将为 O(1)。

Better late than never。