当前位置: 首页 > news >正文

LeetCode算法日记 - Day 57: 括号生成、组合

目录

1. 括号生成

1.1 题目解析

1.2 解法

1.3 代码实现

2. 组合

2.1 题目解析

2.2 解法

2.3 代码实现


1. 括号生成

https://leetcode.cn/problems/generate-parentheses/description/

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

示例 1:

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

示例 2:

输入:n = 1
输出:["()"]

提示:

  • 1 <= n <= 8

1.1 题目解析

题目本质
生成所有长度为 2n 的“合法括号串”。“合法”可等价为:任意前缀内 ) 的数量不超过 (,且总计 ( 与 ) 各 n 个。

常规解法
暴力枚举所有由 ( 与 ) 组成的 2n 长串(共2^{2n} 个),再逐个校验是否合法。

问题分析:
暴力会产生大量明显不合法的前缀(例如一上来就放 )),校验再滤掉,时间在生成阶段就浪费了,复杂度近似O(2^{2n})量级,不可取。

思路转折:
要高效 → 必须在生成阶段就“剪枝”。
核心约束只有两条:
i)左括号总数 ≤ n;
ii)任意时刻右括号数 ≤ 左括号数。
只在满足约束时继续向下搜索,就能只生成合法分支,避免无效枚举。这正是回溯 + 约束剪枝的用武之地,最终解的规模是第 n 个卡特兰数 Cn,复杂度与结果数量同阶,比暴力好很多。剪枝目的不一定是优化性能,也能防止产出错误数据。

1.2 解法

算法思想
• 回溯构造路径 path;
• 若 left < n 放 ( 继续;
• 若 right < left 放 ) 继续;
• 当 path.length() == 2 * n 收集答案。

i)维护成员变量:ret 结果集、path(StringBuffer)、left/right 当前已放数量、n 目标对数。

ii)终终止条件:path.length() == 2 * n 时加入结果返回。

iii)分支一:若 left < n,append('('),left++,递归,回溯时对称撤销(left-- 与删末字符)。

iv)分支二:若 right < left,append(')'),right++,递归,回溯时对称撤销(right-- 与删末字符)。

v)返回 ret。

易错点

  • 终止条件不能用 right == n,应以长度到 2n 为准。

  • 放左括号条件必须 left < n(而非 <=)。

  • 回溯要“计数器+字符”成对撤销,避免状态泄漏。

  • right < left 是前缀合法性的关键约束。

1.3 代码实现

import java.util.*;class Solution {List<String> ret;StringBuffer path;int right;int left;int n;public List<String> generateParenthesis(int length) {ret = new ArrayList<>();path = new StringBuffer();right = 0;left = 0;n = length;dfs();return ret;}// 回溯 + 约束剪枝public void dfs() {// 长度到达 2n,说明一定 left == right == nif (path.length() == 2 * n) {ret.add(path.toString());return;}// 还能放左括号if (left < n) {path.append('(');left++;dfs();left--; // 回溯:计数器恢复path.deleteCharAt(path.length() - 1); // 回溯:删除最后一个字符}// 右括号必须比左括号少才能放if (right < left) {path.append(')');right++;dfs();right--; // 回溯:计数器恢复path.deleteCharAt(path.length() - 1); // 回溯:删除最后一个字符}}
}

复杂度分析

  • 时间复杂度:O(C_n)(第 n 个卡特兰数,等于结果数量同阶);

  • 空间复杂度: O(n)(递归栈与构造路径,不含结果集)。

2. 组合

https://leetcode.cn/problems/combinations/

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例 1:

输入:n = 4, k = 2
输出:
[[2,4],[3,4],[2,3],[1,2],[1,3],[1,4],
]

示例 2:

输入:n = 1, k = 1
输出:[[1]]

提示:

  • 1 <= n <= 20
  • 1 <= k <= n

2.1 题目解析

题目本质
在区间 [1, n] 内选择恰好 k 个数的所有组合(无序、无重复)。本质是“从 n 个里选 k 个”的枚举问题。

常规解法
多层 for 循环硬写枚举。适合固定很小的 kkk,但代码不可扩展。

问题分析
固定层数不适配通用 n,k。通用枚举需“逐层选择+回退”的回溯框架。若无剪枝,会在“已经选满 kkk 个后仍继续递归”或“剩余元素不足以凑满”两类分支上做无用功。

思路转折
要高效 → 用回溯 + 两类剪枝
i)长度剪枝:当 path.size()==k 立刻收集并 return,阻断后续无意义扩展;
ii)容量剪枝:当前起点 key 到 n 剩余数量必须 ≥ k - path.size(),否则当前分支不可能凑满,直接停本层循环。

2.2 解法

算法思想
• 回溯构造递增路径 path,保证不重不漏;
• 当 path.size()==k:加入答案并返回;;
• 容量剪枝:循环上界设为 n - (k - path.size()) + 1

i)设全局 ret、path、n、k,从 dfs(1) 启动。

ii)命中基:path.size()==k → ret.add(copy) → return。

iii)设本层最大起点 maxStart = n - (k - path.size()) + 1。

iv)循环 i 从 key 到 maxStart:选 i,递归 dfs(i+1),回溯移除 i。

v)结束返回 ret。

易错点

  • 命中基只 add 不 return 会导致超配递归(path 继续被塞到 k+1、k+2… 虽不入库但浪费栈帧)。

  • 忽略容量剪枝,导致在“剩余可选元素数 < 需要补齐数”时仍然空转。

  • 结果收集必须拷贝新列表,避免被后续回溯污染。

2.3 代码实现

import java.util.*;class Solution {List<List<Integer>> ret;List<Integer> path;int n;int k;public List<List<Integer>> combine(int _n, int _k) {ret = new ArrayList<>();path = new ArrayList<>();k = _k;n = _n;dfs(1);return ret;}public void dfs(int key) {// 长度剪枝:选满即收集并返回,阻断无意义扩展if (path.size() == k) {ret.add(new ArrayList<>(path));return;}// 容量剪枝:当前层起点至多到 n - (还需数量) + 1int maxStart = n - (k - path.size()) + 1;for (int i = key; i <= maxStart; i++) {path.add(i);dfs(i + 1);path.remove(path.size() - 1);}}
}

复杂度分析

  • 时间复杂度:Θ(C(n, k)),与答案规模同阶(剪枝降低常数因子)。

  • 空间复杂度:O(k),递归深度与路径长度(不含结果集)。

http://www.dtcms.com/a/427113.html

相关文章:

  • FinalShell 服务器远程连接工具
  • 分享:一键自动化巡检服务器
  • 广州建站快车加盟网网站建设策划书
  • 12306网站架构站长之家seo综合
  • 学习:uniapp全栈微信小程序vue3后台-额外/精彩报错篇
  • 【云服务器相关】云服务器与P2P
  • vscode终端输出中文乱码一种解决方法
  • 脑机接口(BCI):从信号到交互的工程实践
  • 更改mysql密码
  • 同步与互斥
  • Java Web搭建商城首页
  • STP生成树(h3c)
  • 深圳汇网网站建设移动互联网时代的到来为很多企业提供了新的商业机会
  • 安卓接入Bigo广告源
  • 安卓Handler+Messenger实现跨应用通讯
  • 公司网站建设完成通知重庆市工程建设交易中心网站
  • 北京网站设计公司hlh成都柚米科技15企业营销型网站系统
  • 德州网站建设招聘帝国网站怎么仿站
  • 15. C++ 类的转换
  • 基于STM32与influxDB的电力监控系统-7
  • python 之 argparse的简单使用
  • 开源 java android app 开发(十七)封库--混淆源码
  • windows显示驱动开发-IddCx 对象
  • 图书馆网站建设的作用广州新建站
  • (27)APS.NET Core8.0 堆栈原理通俗理解
  • SVN 一些命令疑问
  • 精读 C++20 设计模式:行为型设计模式 — 状态机模式
  • 多周期路径约束
  • Webpack配置之path.join、path.resolve和__dirname详解
  • vue打包优化方案都有哪些?