【力扣刷题实战】全排列II
大家好,我是小卡皮巴拉
文章目录
目录
力扣题目:全排列II
题目描述
解题思路
问题理解
算法选择
具体思路
解题要点
完整代码(C++)
兄弟们共勉 !!!
每篇前言
博客主页:小卡皮巴拉
咱的口号:🌹小比特,大梦想🌹
作者请求:由于博主水平有限,难免会有错误和不准之处,我也非常渴望知道这些错误,恳请大佬们批评斧正。
力扣题目:全排列II
原题链接:47. 全排列 II - 力扣(LeetCode)
题目描述
给定一个可包含重复数字的序列 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]]
解题思路
问题理解
本题给定一个可包含重复数字的整数数组 nums
,要求找出该数组所有不重复的全排列,并以二维向量的形式返回这些全排列,每个全排列是一个一维向量,且返回的全排列顺序可以任意。
算法选择
采用深度优先搜索(DFS)算法结合回溯思想以及剪枝优化。通过深度优先搜索遍历所有可能的排列组合,在遍历过程中利用回溯来撤销已经做出的选择,从而尝试其他可能的排列,同时使用剪枝操作避免生成重复的全排列。
具体思路
-
初始化:定义一个一维向量
path
用于存储当前正在构建的一个全排列,一个二维向量ret
用于存储所有不重复的全排列结果,一个布尔数组check
用于标记数组nums
中每个元素是否已经被使用过,初始化为false
。 -
排序:对数组
nums
进行排序,这一步是为了后续的剪枝操作做准备,方便发现重复元素。 -
深度优先搜索:调用
dfs
函数开始深度优先搜索,传入数组nums
和起始索引0
。-
递归终止条件:当
pos
等于数组nums
的长度时,说明已经得到了一个完整的全排列,将path
添加到ret
中,并返回,结束当前递归分支。 -
遍历选择:遍历数组
nums
,对于每个元素nums[i]
,检查其是否已经被使用过(即check[i]
是否为false
)以及是否满足剪枝条件(即i != 0 && nums[i] == nums[i - 1] && check[i - 1] == false
)。如果不满足条件,则跳过该元素;如果满足条件:-
将
nums[i]
添加到path
中,表示选择了该元素用于当前的排列。 -
将
check[i]
设置为true
,标记该元素已被使用。 -
递归调用
dfs
函数,传入数组nums
和下一个索引pos + 1
,继续构建下一个位置的排列。
-
-
回溯操作:递归调用返回后,说明当前分支的排列已经构建完毕或者尝试失败,需要回溯。将
path
中最后一个元素移除(即path.pop_back()
),恢复到添加该元素之前的状态;将check[i]
设置为false
,取消该元素的使用标记,以便在其他排列中可以再次使用。
-
-
返回结果:当所有可能的排列都被尝试完毕后,
ret
中存储了数组nums
的所有不重复全排列,返回ret
。
解题要点
-
深度优先搜索的实现:正确实现深度优先搜索算法,通过递归调用逐步构建排列,确保能够遍历到所有可能的排列组合。
-
回溯的运用:理解回溯的过程,在递归调用返回后,准确地撤销已经做出的选择(如移除
path
中的元素和取消check
中的标记),以便尝试其他可能的排列。 -
剪枝操作:合理使用剪枝条件,在同一节点的所有分支中,避免重复选择相同的元素,从而避免生成重复的全排列。排序后的数组使得相同元素相邻,便于进行剪枝判断。
完整代码(C++)
class Solution {
// 用于存储当前正在构建的一个全排列的一维向量
vector<int> path;
// 用于存储所有不重复全排列结果的二维向量
vector<vector<int>> ret;
// 布尔数组,用于标记数组 nums 中每个元素是否已经被使用过,初始化为 false
bool check[9];
public:
vector<vector<int>> permuteUnique(vector<int>& nums)
{
// 对数组 nums 进行排序,以便后续进行剪枝操作,处理重复元素
sort(nums.begin(), nums.end());
// 调用深度优先搜索函数 dfs 开始生成全排列,从索引 0 开始
dfs(nums, 0);
// 返回存储所有不重复全排列结果的向量 ret
return ret;
}
void dfs(vector<int>& nums, int pos)
{
// 递归出口:当当前构建的排列的长度等于原数组 nums 的长度时
// 说明已经得到了一个完整的全排列,将其加入到结果向量 ret 中并返回
if (pos == nums.size())
{
ret.push_back(path);
return;
}
// 遍历原数组 nums
for (int i = 0; i < nums.size(); i++)
{
// 剪枝操作:
// 1. 在同一节点的所有分支中,相同的元素只能选择一次。
// 即如果当前元素 nums[i] 与前一个元素 nums[i - 1] 相同,且前一个元素还未被使用(check[i - 1] == false),
// 则跳过当前分支,避免生成重复的全排列。
// 2. 同一个数只能使用一次,通过 check 数组来标记。
if (check[i] == true || (i != 0 && nums[i] == nums[i - 1] && check[i - 1] == false))
continue;
// 将当前元素加入到当前正在构建的排列 path 中
path.push_back(nums[i]);
// 标记当前元素已经被使用过
check[i] = true;
// 递归调用 dfs 继续构建下一个位置的排列
dfs(nums, pos + 1);
// 回溯:将当前元素从排列 path 中移除,恢复到添加该元素之前的状态
path.pop_back();
// 取消当前元素的使用标记,以便在其他排列中可以再次使用
check[i] = false;
}
}
};
兄弟们共勉 !!!
码字不易,求个三连
抱拳了兄弟们!