js算法题解(第30天) ---- leetcode 22. 括号生成

247 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情

前言

每天一道算法题,死磕算法
从今天开始,准备把 🔥 LeetCode 热题 HOT 100中没有做的题目都做一下,因为这100道题特别适合短时间内提升,熟练掌握这 100 道题,你就已经具备了在代码世界通行的基本能力。

题目

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

分析

第一步从题目中提取关键字

  • 1.有效的,这三个字怎么听起来那么熟悉,我们在讲栈的时候,是不是做过一道有效的括号的题目,(不知道的同学请看js算法题解(第十五天)---20. 有效的括号)所以正好拿来用

  • 2.一看到组合两个字我们立马联想到回溯,用回溯算法我们要知道3个概念

    1.路径(track): 就是当前已经选择的路径
    2.选择列表(nums): 就是当前可以选择的路径列表
    3.结束条件: 就是当前路径的结束条件

    模板如下

    result = [];
    
    let backTrace = function(track, nums){
        if(结束条件){
            result.push(track);
            return;
        }
    
        for(let i=0;i<nums.length;i++){
            加入路径
            backTrace(track,nums)
            从路径中去除
        }
    }
    

    所以算法题被我们完成了填空游戏,是不是感觉很好玩

/**
 * @param {number} n
 * @return {string[]}
 */
 // 我之所以用栈这种数据结构是为了复习一下栈的使用,如果做一个知识点,能让我们把其他知识点关联起来,这样不是更好么
 const isValid = function (result) {
  // 定义哈希值
  let obj = {
    ")": "("
  };
  // 定义栈
  let arr = [];
  for (let i = 0; i < result.length; i++){
     // 如果是)的话
    if (obj[result[i]]) {
       // 如果栈为空
      if (!arr.length) {
        return false;
      }
      // 取出栈顶的元素
      let top = arr[arr.length - 1];
      // 如果栈定的元素等于值,那么出栈
      if (top === obj[result[i]) {
        arr.pop();
      } else {
        return false;
      }
    } else {
      arr.push(result[i]);
    }
  }
  // 如果循环完,栈里面还有元素,那么为false
  if (arr.length) {
    return false;
  }
  return true;
}


var generateParenthesis = function (n) {
  let result = [];
  if (n===0) {
    return result; 
  }
  // 第一个选择是(括号,否则就是失效的
  backTrack('',['('],2*n,result,0)
  return result;
};

/**
 * 
 * @param {string} track 路径
 * @param {array} nums 选择列表
 * @param {number} n 结束条件
 * @param {array} result 结果
 * @returns 
 */
const backTrack = function (track, nums, n, result) {
  if (track.length === n) {
    if (isValid(track)) {
      result.push(track);
    }
    return;
  }
  for (let i = 0; i < nums.length; i++){
    track += nums[i];
    backTrack(track, ['(', ')'], n, result)
    track = track.substring(0, track.length - 1);
  }
}

写时一时爽,看看结果只超过了20%的人

image.png

咱们现在的答案之所以时间复杂度高,就是因为把所有的组合答案都放到了结果里面,然后再判断是否有效,但是有些答案从一开始就不对,所以我们没必要把他加到结果里面,这种策略叫做剪枝

总之,剪枝策略,属于算法优化范畴;通常应用在DFS 和 BFS 搜索算法中;剪枝策略就是寻找过滤条件,提前减少不必要的搜索路径。

因为这个题目本身就是一个二叉树,所以我们可以用一个递归二叉树的方法去做,然后再加一个二叉树的结束条件

所以我们的第二套方案是

/**
 * 
 * @param {string} track 路径
 * @param {number} left 左括号个数 
 * @param {number} right 右括号个数 
 * @param {array} result 结果 
 * @param {number} n 括号对数
 * @returns 
 */
const func = function(track,left,right,result,n){
  // 只要right>left,那么肯定就是不对的
  if(left>n||right>n||right>left){
    return;
  }
  if(track.length === 2*n){
    result.push(track);
    return;
  }
  //左子树递归
  func(track+"(",left+1,right,result,n);
  //右子树递归
  func(track+')',left,right+1,result,n);
}

var generateParenthesis = function(n) {
  let result = [];
  func('',0,0,result,n);
  return result;
};

这种方法提交一下,平均超过76%的用户,好吧,知足了,小伙伴们,快来试一试吧

参考