八、回溯算法学习3(代码随想录学习)

13.子集Ⅱ

leetcode链接

思路: 本题和上题类似,但多了去重操作。使用used数组记录当前子集中使用的元素,若当前元素与其前一元素相同,并且前一元素未在当前子集中,则跳过当前元素。

class Solution {
private:
	vector<vector<int>> res;
	vector<int> path;

	// used数组记录nums中哪些元素已在当前子集中
	void backtracing(vector<int> &nums, int index, vector<bool> &used) {
		res.push_back(path);

		for(int i = index; i < nums.size(); i++){
			// 去除重复元素
			if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false)
				continue;
			path.push_back(nums[i]);
			used[i] = true;
			backtracing(nums, i + 1, used);
			used[i] = false;
			path.pop_back();
		}
		return;
	}

public:
	vector<vector<int>> subsetsWithDup(vector<int>& nums) {
		res.clear();
		path.clear();
		sort(nums.begin(), nums.end()); // 将数组排序
		vector<bool> used(nums.size(), false);
		backtracing(nums, 0, used);
		return res;
	}
};

时间复杂度: O(n*2^n)
空间复杂度: O(n)

14.非递减序列

leetcode链接

思路: 利用递归回溯的算法,本次要理清楚去重的目标。同一父节点下的同层上使用过的元素就不能再使用了。

在这里插入图片描述
因此,使用一个set记录本层已经使用过的元素,遇到重复元素略过即可。

class Solution {
private:
	vector<vector<int>> res;
	vector<int> path;

	void backtracing(const vector<int> &nums, int index) {
		if (path.size() > 1)
			res.push_back(path);

		unordered_set<int> used;  // 记录本层使用过的元素,使用过本层就不再使用
		for (int i = index; i < nums.size(); i++) {
			if ((path.size() > 0 && path[path.size() - 1] > nums[i]) 
				|| used.find(nums[i]) != used.end())
				continue;

			// 记录nums[i]在本层已经使用过了,每一层会有新的used,因此不需要回溯
			used.insert(nums[i]); 
			path.push_back(nums[i]);
			backtracing(nums, i + 1);  
			path.pop_back();
		}
		return;
	}

public:
	vector<vector<int>> findSubsequences(vector<int>& nums) {
		res.clear();
		path.clear();
		backtracing(nums, 0);
		return res;
	}
};

时间复杂度: O(n*2^n)
空间复杂度: O(n)

15. 全排列

leetcode链接

思路: 全排列也是使用回溯算法,但与组合问题不同的是,全排列的for循环的起点是0,而不是上一层传来的下标,使用used数组记录哪些数据已在本次排列中,continue。

class Solution {
private:
	vector<vector<int>> res;
	vector<int> path;

	void backtracing(const vector<int>& nums, vector<bool> &used) {
		if (path.size() == nums.size()) {
			res.push_back(path);
			return;
		}

		for (int i = 0; i < nums.size(); i++) {
			if (used[i]) continue;

			path.push_back(nums[i]);
			used[i] = true;
			backtracing(nums, used);
			used[i] = false;
			path.pop_back();
		}
		return;
	}

public:
	vector<vector<int>> permute(vector<int>& nums) {
		res.clear();
		path.clear();
		vector<bool> used(nums.size(), false);
		backtracing(nums, used);
		return res;
	}
};

时间复杂度: O(n!)
空间复杂度: O(n)

16. 全排列Ⅱ

leetcode链接

思路: 总体思路与上题类似,但需要进行去重操作,去重的对象是同一父节点的同一层,每个元素只会出现一次,需去除重复元素。

// 对重复元素去重,对于同一父节点下的同一层,每个元素只出现一次
if (i > 0 && nums[i] == nums[i - 1] && !used[i-1])
	continue; 

整体代码:

class Solution {
private:
	vector<vector<int>> res;
	vector<int> path;

	void backtracing(const vector<int> &nums, vector<bool> used) {
		if (path.size() == nums.size()) {
			res.push_back(path);
			return;
		}

		for (int i = 0; i < nums.size(); i++) {
			// 对重复元素去重,对于同一父节点下的同一层,每个元素只出现一次
			if (i > 0 && nums[i] == nums[i - 1] && !used[i-1])
				continue; 

			if (used[i])  // 若当前元素已统计,则跳过
				continue;

			path.push_back(nums[i]);
			used[i] = true;
			backtracing(nums, used);
			used[i] = false;
			path.pop_back();
		}
	}

public:
	vector<vector<int>> permuteUnique(vector<int>& nums) {
		res.clear();
		path.clear();
		vector<bool> used(nums.size(), false);
		sort(nums.begin(), nums.end());
		backtracing(nums, used);
		return res;
	}
};

时间复杂度: O(n*n!)
空间复杂度: O(n)

拓展:
如果改成 used[i - 1] == true, 也是正确的!,去重代码如下:

if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
    continue;
}

这么写的实质是对树枝进行去重,而原本的写法是对树层进行去重。
对于排列问题,树层上去重和树枝上去重,都是可以的,但是树层上去重效率更高!

并且,一定要加上 used[i - 1] == false或者used[i - 1] == true,因为 used[i - 1] 要一直是 true 或者一直是false 才可以,而不是 一会是true 一会又是false。 所以这个条件要写上。

17. 回溯周末总结

代码随想录:回溯周末总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值