递归算法练习JS

反转一个单链表
示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

/*var head = {
  val: 1,
  next: {
    val: 2,
    next: {
      val: 3,
      next: null
    }
  }
}*/
  • 方法一:将val存入数组,反转数组
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */

var newList = null;
var reverseList = function (head) {
  var temp = head, result = [];
  while (temp) {
    result.push(temp.val);
    temp = temp.next;
  }

  temp = head;
  var i = 0;
  result.reverse();
  while (temp) {
    temp.val = result[i++];
    temp = temp.next;
  }
  return head;
};

  • 方法二:局部反转最终实现整体反转
var reverseList = function (head) {
  var pre = null;
  while (head) {
    var next = head.next;
    head.next = pre;
    pre = head;
    head = next;
  }
  return pre;
};

斐波那契数列

斐波那契数,通常用 F(n) 表示,形成的序列称为斐波那契数列。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,   F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.

给定 N,计算 F(N)。

示例:

输入:2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1.
输入:3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2.
  • 解答方法一:递归
//用时68ms 内存消耗34.2m
var fib = function(N) {
  var obj = {};
  return fibCal(N, obj)
};

function fibCal(n, obj) {
  if (0 <= n && n <= 30) {
    var result;
    if (obj.hasOwnProperty(n)) {   //记忆化(memoization)解决重复计算问题
      return obj[n];
    }

    if (n < 2) {
      result = n
    } else {
      result = fib(n - 1) + fib(n - 2)
    }

    obj[n] = result;
    return result;
  }
}
  • 解答方法二:循环
//推荐:击败 100%,用时 48ms
var fib = function(N) {
    const a=[0,1]
    for(let i=2;i<=N;i++){
        a[i]=a[i-1]+a[i-2]
    }
    return a[N]
};

const fib = function(N) {
    if(N<=1) return N;
    let [a, b] = [0, 1];
    while(N-- > 1) {
        let sum = a + b;
        a = b;
        b = sum;
    }
    return b;
};


爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例:

输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1.  1 阶 + 1 阶
2.  2 阶
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1.  1 阶 + 1 阶 + 1 阶
2.  1 阶 + 2 阶
3.  2 阶 + 1 阶
  • 方法一:带索引的递归(斐波那契)
var map = {
  0: 1,
  1: 1
}
var climbStairs = function(n) {
  if(map[n] !== undefined) return map[n]
  map[n] = climbStairs(n-1) + climbStairs(n-2)
  return map[n]
};


二叉树的最大深度

给定一个二叉树,找出其最大深度。即二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。

示例:

给定二叉树 [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;
 * }
 */
  • 方法一:递归
//执行用时 :76 ms, 在所有 javascript 提交中击败了62.58%的用户
//内存消耗 :37.1 MB, 在所有 javascript 提交中击败了51.55%的用户
var maxDepth = function(root) {
    if(root === null)
        return 0;
    var left = maxDepth(root.left) + 1;
    var right = maxDepth(root.right) + 1;
    return left > right ? left : right;
};

//简化写法
//执行用时 :80 ms, 在所有 javascript 提交中击败了44.31%的用户
//内存消耗 :37.2 MB, 在所有 javascript 提交中击败了38.35%的用户
var maxDepth = function(root) {
    return root === null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1
}
  • 方法二:迭代(栈)
//执行用时 :68 ms, 在所有 javascript 提交中击败了90.24%的用户
//内存消耗 :37.7 MB, 在所有 javascript 提交中击败了5.15%的用户

var maxDepth = function(root) {
    if(root === null) return 0;
    let depth = 1;
    let stack = [root];
    let currentNode = root;
    
    while(stack.length) {
        while(getNotArrivedChild(currentNode)) {
            stack.push(getNotArrivedChild(currentNode));
            currentNode = getNotArrivedChild(currentNode);
            //这里给访问过的节点标记一下
            currentNode.ok = true;
            //如果加入这个节点后,stack长度大于当前depth,就更新depth为stack长度。
            if(stack.length > depth) depth = stack.length;
        }
        stack.pop();
        currentNode = stack[stack.length-1];
    }
    
    return depth;
};

//这个函数用来返回当前节点还未被访问过的子节点
function getNotArrivedChild(root) {
    if(root.left && !root.left.ok) return root.left;
    if(root.right && !root.right.ok) return root.right;
    return null;
}

Pow(x,n)

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

示例:

输入: 2.00000, 10
输出: 1024.00000

输入: 2.10000, 3
输出: 9.26100

输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25

/**
 * @param {number} x
 * @param {number} n
 * @return {number}
 */

说明:
-100.0 < x < 100.0
n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1]

  • 方法一:递归快速幂算法

如果 n 是偶数,我们可以使用公式 (x ^ n) ^ 2 = x ^ {2 * n}(x n) 2=x 2∗n得到 x ^ n = A * Ax n=A∗A。 如果 n 是奇数,那么 A * A = x ^ {n - 1}A∗A=x n−1。直观地说,我们需要将另一个 xx 和结果相乘,所以 x ^ n = A * A * xx n=A∗A∗x

//执行用时 :72 ms, 在所有 javascript 提交中击败了54.10%的用户
//内存消耗 :33.8 MB, 在所有 javascript 提交中击败了26.02%的用户
var myPow = function (x, n) {
  const fastPow = (x, n) => {
    if (n == 1) { return x };
    let val = n % 2 === 0 ? fastPow(x, n / 2) : fastPow(x, (n - 1) / 2);
    return n % 2 === 0 ? val * val : val * val * x;
  }

  if (x === 1 || n === 0) {
    return 1;
  }

  return n < 0 ? 1 / fastPow(x, Math.abs(n)) : fastPow(x, n);
}
  • 方法二:分治、递归
//执行用时 :56 ms, 在所有 javascript 提交中击败了98.48%的用户
//内存消耗 :34.2 MB, 在所有 javascript 提交中击败了5.69%的用户

var myPow = function (x, n) {
  //临界条件
  if (n === 0) return 1;
  if (x === 0) return 0;
  if (n < 0) return 1 / myPow(x, -n);
  //n为奇数
  if (n % 2) return x * myPow(x, n - 1);
  //n为偶数
  return myPow(x * x, n / 2);
};

合并两个有序链表

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

示例:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
  • 方法一:递归

取两个列表头部中较小的那个,然后再加上合并其余元素所得到的结果

//执行用时 :80 ms, 在所有 javascript 提交中击败了53.05%的用户
//内存消耗 :35.7 MB, 在所有 javascript 提交中击败了22.92%的用户

var mergeTwoLists = function (l1, l2) {
  if (l1 === null) {
    return l2;
  } else if (l2 === null) {
    return l1;
  } else if (l1.val < l2.val) {
    l1.next = mergeTwoLists(l1.next, l2);
    return l1;
  } else {
    l2.next = mergeTwoLists(l1, l2.next);
    return l2;
  }
};
  • 方法二:迭代

假设 l1 完全小于 l2,并逐个处理元素,在 l1 的必要位置插入 l2 元素。

//执行用时 :76 ms, 在所有 javascript 提交中击败了71.00%的用户
//内存消耗 :35.5 MB, 在所有 javascript 提交中击败了39.88%的用户
var mergeTwoLists = function (l1, l2) {
  var prehead = new ListNode();
  var prev = prehead;

  while (l1 !== null && l2 !== null) {
    if (l1.val < l2.val) {
      prev.next = l1;
      l1 = l1.next;
    } else {
      prev.next = l2;
      l2 = l2.next;
    }
    prev = prev.next;
  }

  prev.next = l1 === null ? l2 : l1;
  return prehead.next;
};

第K个语法符号

在第一行我们写上一个 0。接下来的每一行,将前一行中的0替换为01,1替换为10。
给定行数 N 和序数 K,返回第 N 行中第 K个字符。(K从1开始)

示例:

输入: N = 1, K = 1
输出: 0

输入: N = 2, K = 2
输出: 1

输入: N = 4, K = 5
输出: 1

解释:
第一行: 0
第二行: 01
第三行: 0110
第四行: 01101001

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/k-th-symbol-in-grammar
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

注意:

N 的范围 [1, 30].
K 的范围 [1, 2^(N-1)].

  • 递归

第 K 位的父位应该是第 (K+1) / 2 位。如果父位是 0,那么这一位就是 1 - (K%2)。如果父位是 1,那么这一位就是 K%2

//执行用时 :72 ms, 在所有 javascript 提交中击败了29.88%的用户
//内存消耗 :33.6 MB, 在所有 javascript 提交中击败了40.00%的用户
var kthGrammar = function (N, K) {
  if (N == 1) { return 0; }
  return (~K & 1) ^ kthGrammar(N - 1, (K + 1) / 2);
};
//执行用时 :76 ms, 在所有 javascript 提交中击败了22.99%的用户
//内存消耗 :34.1 MB, 在所有 javascript 提交中击败了20.00%的用户
var kthGrammar = function (N, K) {
  if (K === 1) {
    return 0;
  }
  if (K === 2) {
    return 1;
  }

  var parent = K % 2 == 0 ? kthGrammar(N - 1, K / 2) : kthGrammar(N - 1, (K + 1) / 2);

  if (parent === 0) {
    return K % 2 === 0 ? 1 : 0;
  }
  return K % 2 === 0 ? 0 : 1;
}

不同的二叉搜索树

给定一个整数 n,生成所有由 1 … n 为节点所组成的二叉搜索树

示例:

输入: 3
输出:
[
  [1,null,3,2],
  [3,2,null,1],
  [3,1,null,null,2],
  [2,1,3],
  [1,null,2,null,3]
]
解释:
以上的输出对应以下 5 种不同结构的二叉搜索树:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3
   
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {number} n
 * @return {TreeNode[]}
 */
  • 递归

从序列 1 …n 取出数字 i 并以它作为当前树的根节点。 那么就有 i - 1 个元素可以用来构造左子树,而另外的 n - i 个元素可以用于构造右子树。

//执行用时 :88 ms, 在所有 javascript 提交中击败了76.71%的用户
//内存消耗 :38.4 MB, 在所有 javascript 提交中击败了12.50%的用户
var generateTrees = function (n) {
  if (n === 0) { return []; }
  return getTree(1, n);

  function getTree(start, end) {
    var res = [];
    if (start > end) {
      res.push(null);
      return res;
    }

    for (var i = start; i <= end; i++) {
      var left = getTree(start, i - 1);
      var right = getTree(i + 1, end);

      for (var l = 0; l < left.length; l++) {
        for (var r = 0; r < right.length; r++) {
          var newNode = new TreeNode(i);
          newNode.left = left[l];
          newNode.right = right[r];
          res.push(newNode);
        }
      }
    }
    return res;
  }
}

题目来源于leetcode https://leetcode-cn.com/explore

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值