算法助手
用户:
这个题目有什么好的思路吗?“Given an integer array nums, return all the different possible non-decreasing subsequences of the given array with at least two elements. You may return the answer in any order.”
我的代码是这样的:
/*
* @lc app=leetcode.cn id=491 lang=cpp
*
* [491] Non-decreasing Subsequences
*/
#include "General.h"
// @lc code=start
class Solution
{
private:
vector<vector<int>> result;
void search(vector<int> &nums, vector<int> &tempResult, int left)
{
if (tempResult.size() > 1)
{
if (tempResult[tempResult.size() - 1] >= tempResult[tempResult.size() - 2])
{
result.push_back(tempResult);
}
else
{
return;
}
}
for (int i = left; i < nums.size(); i++)
{
if (i > left && nums[i] == nums[i - 1])
{
continue;
}
tempResult.push_back(nums[i]);
search(nums, tempResult, i + 1);
tempResult.pop_back();
}
}
public:
vector<vector<int>> findSubsequences(vector<int> &nums)
{
vector<int> v;
search(nums, v, 0);
return result;
}
};
// @lc code=end
#include <cassert>
#include <vector>
int main(int argc, char const *argv[])
{
Solution sol;
vector<int> nums = {1, 2, 1, 1};
vector<vector<int>> result = sol.findSubsequences(nums);
cout << '[' << endl;
for (vector<int> ve : result)
{
cout << " [";
for (int i : ve)
{
cout << i << ", ";
}
cout << "]," << endl;
}
cout << ']' << endl;
return 0;
}
ChatGPT:
您的代码已经很接近正确答案了,但是存在一些问题需要解决。首先,您需要避免生成重复的子序列。在您的代码中,您尝试通过检查 if (i > left && nums[i] == nums[i - 1])
来避免重复,但这只能在同一层递归中避免重复,如果重复的数字在不同层递归中出现,这种方法就无效了。
解决这个问题的一个方法是在每一层递归中使用一个哈希集合来记录已经使用过的数字,从而避免在同一层递归中使用重复的数字。此外,您在添加到结果集 result
之前检查 tempResult
是否是非递减的,这是不必要的,因为在添加新元素之前,您可以先检查它是否不小于 tempResult
的最后一个元素。
以下是修改后的代码:
#include <vector>
#include <set>
using namespace std;
class Solution {
private:
vector<vector<int>> result;
set<vector<int>> uniqueResults; // 用于记录唯一的子序列
void search(vector<int> &nums, vector<int> &tempResult, int start) {
if (tempResult.size() > 1) {
result.push_back(tempResult);
}
set<int> used; // 用于当前递归层去重
for (int i = start; i < nums.size(); i++) {
// 如果当前数字已经在这一层使用过,跳过
if (used.find(nums[i]) != used.end()) continue;
// 如果当前数字小于tempResult中最后一个数字,跳过
if (!tempResult.empty() && nums[i] < tempResult.back()) continue;
used.insert(nums[i]);
tempResult.push_back(nums[i]);
search(nums, tempResult, i + 1);
tempResult.pop_back();
}
}
public:
vector<vector<int>> findSubsequences(vector<int>& nums) {
vector<int> tempResult;
search(nums, tempResult, 0);
return result;
}
};
在这个版本中,我们使用了一个 set<int>
叫 used
来记录在当前递归层已经使用过的数字,从而避免在同一层中重复使用相同的数字。同时,我们在添加元素到 tempResult
之前,检查它是否大于等于 tempResult
的最后一个元素,以确保 tempResult
是非递减的。
另外,我移除了 uniqueResults
集合,因为在这个版本的代码中,我们已经确保了所有生成的子序列都是唯一的,所以不再需要它。
这样修改后的代码应该能够正确地解决问题,且不会生成重复的子序列。
用户:
别的我先不说,你这个set能做到vector
ChatGPT:
抱歉,我之前的回答中确实有误。std::set
或 std::unordered_set
不能直接用来存储 vector<int>
类型的元素进行去重,因为默认情况下它们不支持对容器类型的比较。要对 vector<int>
类型的元素进行去重,可以使用 std::set
,但必须提供一个比较函数来比较两个 vector<int>
。不过,对于这个问题,我们不需要对结果集合中的 vector<int>
进行去重,因为我们可以在搜索过程中避免生成重复的子序列。
在每一层递归中,我们可以使用一个 std::set
来记录这一层已经使用过的元素值,从而避免在同一层递归中使用相同的元素。这是因为我们只关心在同一层递归中是否有重复的元素,而不是跨层递归的元素。
下面是修正后的代码:
#include <vector>
#include <iostream>
using namespace std;
class Solution {
private:
vector<vector<int>> result;
void search(vector<int> &nums, vector<int> &tempResult, int start) {
if (tempResult.size() > 1) {
result.push_back(tempResult);
}
set<int> used; // 用于当前递归层去重
for (int i = start; i < nums.size(); i++) {
// 如果当前数字已经在这一层使用过,跳过
if (used.find(nums[i]) != used.end()) continue;
// 如果tempResult为空,或者当前数字不小于tempResult中最后一个数字,可以添加
if (tempResult.empty() || nums[i] >= tempResult.back()) {
used.insert(nums[i]);
tempResult.push_back(nums[i]);
search(nums, tempResult, i + 1);
tempResult.pop_back();
}
}
}
public:
vector<vector<int>> findSubsequences(vector<int>& nums) {
vector<int> tempResult;
search(nums, tempResult, 0);
return result;
}
};
int main() {
Solution sol;
vector<int> nums = {4, 6, 7, 7};
vector<vector<int>> result = sol.findSubsequences(nums);
for (const auto &subseq : result) {
for (int num : subseq) {
cout << num << " ";
}
cout << endl;
}
return 0;
}
这段代码中,used
集合在每次递归调用时都是一个新的集合,用来记录当前递归层已经使用过的数字,确保在同一层中不会有重复的元素被加入到 tempResult
中。这样,我们在生成结果的过程中就不会产生重复的子序列了。
用户:
终于对了……
ChatGPT:
很高兴听到这个问题终于得到了解决!如果您有其他关于算法、数据结构或编程的问题,或者需要进一步的帮助,请随时告诉我。祝您在算法竞赛中取得优异的成绩!
用户:
当然有。在最初的代码中,我用了if (i > left && nums[i] == nums[i - 1])
,改进版本中,我们改用了used数组/集合来去重。这么做的区别在哪里?是因为一个要用在有序数组,一个要用在无序数组吗?当然,想法都是一样的:同一层内,如果用了一样的数字(非第一个),那么前面的回溯中必然已经包括了这一结果
ChatGPT:
您的理解是正确的。在递归的回溯问题中,去除重复解的方法通常取决于输入数据是否预先排序。
-
当输入数据是有序的,您可以使用类似
if (i > start && nums[i] == nums[i - 1])
的条件来跳过同一层中已经使用过的元素。因为数组是有序的,所以重复的元素会相邻出现,您只需要检查当前元素是否与前一个元素相同即可。 -
当输入数据是无序的,上述方法就不再适用,因为相同的元素可能不是相邻的。这时候,您可以使用一个额外的数据结构(如
used
数组或集合)来记录在当前递归层已经访问过哪些元素。这样做可以保证即使在无序的数组中,同一层递归也不会产生重复的子序列。
在您的问题中,使用 used
数组是为了确保在当前递归层内,相同的数字只被使用一次,即便这个数组没有被排序。这个方法比较通用,因为它不依赖于数组的排序。而且,used
数组的方法在时间复杂度上通常优于排序加上 if (i > start && nums[i] == nums[i - 1])
的方法,因为排序本身就需要额外的时间(通常是 O(n log n))。
在处理这类问题时,选择哪种去重方法取决于输入数据的特点以及问题的具体要求。如果问题保证了输入数据的有序性,或者排序后不会影响问题的解答,那么第一种方法会更简洁。如果输入数据是无序的,或者排序会改变问题的解答,那么使用 used
数组或集合会更合适。