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

LeetCode算法日记 - Day 56: 全排列II、话号码的字母组合

目录

1. 全排列II

1.1 题目解析

1.2 解法

1.3 代码实现

2. 电话号码的字母组合

2.1 题目解析

2.2 解法

2.3 代码实现


1. 全排列II

https://leetcode.cn/problems/permutations-ii/

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

示例 1:

输入:nums = [1,1,2]
输出:
[[1,1,2],[1,2,1],[2,1,1]]

示例 2:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

提示:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

1.1 题目解析

题目本质
在长度 ≤ 8 的数组里,从左到右“选位填数”,但元素可能重复;我们需要枚举所有长度为 n 的排列,同时去掉因相同元素换位造成的重复结果

常规解法
朴素回溯(DFS):每层从剩余元素中任选一个放入路径 path,直到放满加入答案。

问题分析
朴素回溯对含重复元素的数组会把“相同数字在同一层互换位置”的分支也枚举出来,产生大量重复结果与无用搜索;虽然可以用集合去重,但代价高(每层/全局去重会引入额外哈希开销,且实现繁琐)。

思路转折
要想不重不漏且高效,必须在搜索时剪枝,避免生成重复分支。关键做法:

        i)先对 nums 排序,使相同数字相邻;

        ii)同一层的枚举里,不同层的相同元素会被标记为 true,而只有同层的才会被标记为 false 。如果当前数字 nums[i] 与前一个数字 nums[i-1] 相等,且前一个相同数字本层还没被使用(!used[i-1]),——这能保证“同层相同值只让排序后出现的第一个出场”,杜绝同构分支。

1.2 解法

算法思想(简短总结)
• 排序:nums 从小到大,使相同元素相邻。
• 回溯:path 记录当前排列;used[i] 标记下标 i 是否已被使用。
• 同层去重剪枝:当 i>0 && nums[i]==nums[i-1] && !used[i-1] 时跳过当前 i,仅允许同层的“第一个相同值”被选。
• 递归结束:当 path.size()==n 时加入答案。

i)对 nums 调用 Arrays.sort(nums)。

ii)准备结构:List<List<Integer>> res、List<Integer> path、boolean[] used。

iii)定义 dfs(depth):

  • 若 depth==n:将 path 拷贝加入 res,返回。

  • 循环 i=0..n-1:

    • 若 used[i] 为真,跳过;

    • 若 i>0 && nums[i]==nums[i-1] && !used[i-1],剪枝跳过;

    • 否则选择:used[i]=true,path.add(nums[i]),递归到 depth+1;回溯撤销选择。

iv)返回 res。

易错点

  • 忘记先排序,导致“同层去重”条件失效。

  • 将剪枝条件误写成 used[i-1](应是 !used[i-1]),或把“同层”与“跨层”混淆。

  • 到达叶子未 return,虽然不致错,但会多跑无用循环。

  • 冗余分支:if(vis[i]) continue; 后不要再写 else continue;。

1.3 代码实现

import java.util.*;class Solution {private List<List<Integer>> res;private List<Integer> path;private boolean[] used;public List<List<Integer>> permuteUnique(int[] nums) {Arrays.sort(nums);                 // 1) 排序,使相同元素相邻int n = nums.length;res = new ArrayList<>();path = new ArrayList<>();used = new boolean[n];dfs(nums, 0);return res;}private void dfs(int[] nums, int depth) {if (depth == nums.length) {        // 2) 叶子:收集答案res.add(new ArrayList<>(path));return;}for (int i = 0; i < nums.length; i++) {if (used[i]) continue;         // 已用过,跳过// 3) 同层去重:相同元素只允许第一个进入本层分支if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue;// 4) 选择used[i] = true;path.add(nums[i]);// 5) 递归下一层dfs(nums, depth + 1);// 6) 回溯path.remove(path.size() - 1);used[i] = false;}}
}

复杂度分析

  • 时间复杂度:最坏情况下为生成所有排列的复杂度 O(n · n!),剪枝能显著减少含重复元素时的无效分支。

  • 空间复杂度:O(n) 递归栈与标记数组,答案集额外空间按输出规模计。

2. 电话号码的字母组合

https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

示例 3:

输入:digits = "2"
输出:["a","b","c"]

提示:

  • 0 <= digits.length <= 4
  • digits[i] 是范围 ['2', '9'] 的一个数字。

2.1 题目解析

题目本质
把一串数字(2–9)按电话键映射成“按位选字母”的所有组合,本质是一个固定长度的笛卡尔积/按位回溯问题。

常规解法
逐位遍历,每一位从映射的字母集中任选一个,深度优先把路径补满后收集为一个字符串。

问题分析:

  • 若直接用多重循环,会随着输入长度变化而改变循环层数,不可扩展;且写死层数容易出错。

  • 用回溯可以自然处理任意位数(这里 ≤4),并且路径构建/撤销的代价低。

  • 复杂度上,假设每位平均有 b 个字母、长度为 n,则解的规模与搜索复杂度为 O(bⁿ),这里 b≈3~4,n≤4,规模很小。

思路转折:
要写得简洁、稳定、易维护

  • 用常量表 KEYS[0..9] 存映射;

  • 递归参数用“当前位置 pos”;

  • 路径用可变字符串(StringBuilder/StringBuffer),到叶子一次性 toString() 收集;

  • 空输入直接返回空列表,避免额外分支。

2.2 解法

算法思想
• 排列模型:第 pos 位从 KEYS[digits[pos]-'0'] 中依次选一个字母。
• 递归推进:选中一个字母 → 递归到 pos+1;当 pos==n 时加入答案。
• 回溯撤销:从路径尾部删去刚加入的字母,返回上一层继续尝试其他字母。

i)digits 为空,返回空列表。

ii)初始化常量映射表 KEYS

iii)准备结果列表 res 与路径 path(可变字符串)。

iv)调用 dfs(digits, 0)。

v)在 dfs 中:

  • 若 pos == digits.length():把 path.toString() 加入结果并返回。

  • 取本位数字 idx = digits.charAt(pos) - '0',得到字母串 letters = KEYS[idx]。

  • 遍历 letters:依次 append 当前字母 → 递归到下一位 → 回溯 delete 最后一个字母。

vi)返回结果列表。

易错点

  • 到达叶子时把同一个可变对象直接加入结果,导致后续修改污染结果;应 toString() 拷贝。

  • 使用 + 拼接字符串构建路径,会产生大量中间对象;应使用 StringBuilder/StringBuffer。

2.3 代码实现

import java.util.*;class Solution {// 电话键映射private static final String[] KEYS = {"",     // 0"",     // 1"abc",  // 2"def",  // 3"ghi",  // 4"jkl",  // 5"mno",  // 6"pqrs", // 7"tuv",  // 8"wxyz"  // 9};private List<String> res = new ArrayList<>();private StringBuilder path = new StringBuilder();public List<String> letterCombinations(String digits) {if (digits == null || digits.length() == 0) return res; // 空输入dfs(digits, 0);return res;}private void dfs(String digits, int pos) {if (pos == digits.length()) {          // 叶子:收集结果res.add(path.toString());return;}int idx = digits.charAt(pos) - '0';    // 当前位对应的按键String letters = KEYS[idx];for (int i = 0; i < letters.length(); i++) {path.append(letters.charAt(i));    // 选择一个字母dfs(digits, pos + 1);              // 递归下一位path.deleteCharAt(path.length() - 1); // 回溯撤销}}
}

复杂度分析(时间+空间)

  • 时间复杂度:令输入长度为 n、每位分支数最大为 b(b≤4),则 O(bⁿ);本题 n≤4,规模很小。

  • 空间复杂度:递归深度 O(n),路径临时空间 O(n),结果集按输出规模计(最多 3⁴=81 或 4⁴=256 级别)。

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

相关文章:

  • 天津住房城乡建设厅官方网站常州网站优化
  • 超易用前端使用Canvas海报图片生成器
  • 网站开发配置管理计划wordpress怎安装
  • --group-start/--group-end 能不能解决 OpenSSL 1.0 vs 1.1 的优先级问题?
  • 中国品牌网官方网站甘肃网络推广公司
  • 使用Trae配置MySQL MCP智能体进行数据库
  • RPA:开启数字化办公的新时代
  • 打工人日报#20250928
  • 怎么用html做移动网站吗wordpress网页设定
  • 门户网站做等保需要备案哪些php 家政网站
  • 扩散模型-上下文学习第一篇【In-Context Learning Unlocked for Diffusion Models】
  • 信息系统项目的成本管理(智能园区)
  • LeetCode:82.杨辉三角
  • 快速交付与弹性扩展,轻量化5GC融合边缘云与专网方案
  • maptalks-根据后端返回的坐标(WKT格式)在地图上绘制图斑
  • BERT 总结
  • java设计模式五、适配器模式
  • 语音识别的评价指标
  • 成都建设企业网站果麦传媒的网站怎么做的
  • python:Django 和 Vue.js 技术栈解析
  • (二十六)、Kuboard 部署网络问题 k8s 使用本地镜像 k8s使用 register本地镜像站 综合应用
  • 腾讯云上TKE集群中通过clb-ingress公网访问到后端服务
  • 信阳做网站公司编程培训机构加盟怎样
  • vps空间如何做网站备份如何提高网站的搜索
  • 广州免费自助建站开发建设工程什么意思
  • Apache Doris 4.0 AI 能力揭秘(二):为企业级应用而生的 AI 函数设计与实践
  • 用deepseek部署全自动的机器人--bytebot
  • 网站开发者模式下怎么保存图片建设网站空间
  • 兰州新区建站07073游戏网
  • 营销型网站建站教程wordpress edit lock