目录
一、集合的子集合
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
oj:https://leetcode-cn.com/problems/subsets/
1.1 回溯法思路
回溯法及思路
- 回溯法:回溯法又称试探法,有点类似于枚举法。
- 采用深度优先遍历,从根节点出发,递归地搜索解空间树,直到找到解或者最后穷尽解空间树后返回。
- 函数停止的条件是达到空间数的叶子节点,未达到叶子节点则依次遍历左子树和右子树。
本题的回溯法算法设计如图所示:
1.2 回溯法代码及解析
下面代码及思路解析:
- 通过current_set变量来存储当前是否加入的矩阵
- 深度优先遍历,即先current_set不加num[loc],送入递归,再加上num[loc]送入递归
- 递归中止之前,需要将当前的vector进行insert,因为当前insert是上一个递归中加或者不加num[loc]送进来的变量,因此有必要先insert,再进行判断中止。
- 用set<vector<int>>可以节省重复,减少时间,简化代码量,但是会提升相应的运算消耗与存储消耗,因此可以用更见的的,直接用vector<vector<int>>来实现的方法
- 递归中,对当前current_set进行push_back操作之后,一定要pop_back,即使在程序的结尾。因为本次递归会被上层函数调用,因此程序运行完成的时候,返回到上层函数中的current_set没有恢复原状,会导致程序出错。比如,下一步中在尾部加入3了,return,导致上一层调用中必然含有3
- 后面会给出更精简的代码
#include<iostream>
#include<vector>
#include<algorithm>
#include<set>
using namespace std;
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>>sub_sets;
int length = nums.size();
if (length < 1){
vector<int> empty;
sub_sets.push_back(empty);
return sub_sets;
}
vector<int>current_set;
set<vector<int>>all_sub_sets;
find_all_subsets(nums, 0, current_set, all_sub_sets);
for (auto item : all_sub_sets){
sub_sets.push_back(item);
}
return sub_sets;
}
void find_all_subsets(vector<int>& nums, int loc, vector<int> ¤t_set, set<vector<int>> & all_sub_sets){
all_sub_sets.insert(current_set);
int length = nums.size();
if (loc >= length){
return;
}
find_all_subsets(nums, loc + 1, current_set, all_sub_sets);
current_set.push_back(nums[loc]);
find_all_subsets(nums, loc + 1, current_set, all_sub_sets);
current_set.pop_back();
return;
}
};
int main(){
vector<int>set = {1,2,3};
Solution s1;
vector<vector<int>>subsets_it = s1.subsets(set);
for (auto sub_set : subsets_it){
if (sub_set.empty())cout << endl;
else{
for (auto item : sub_set){
cout << item << " ";
}
cout << endl;
}
}
int end; cin >> end;
return 0;
}
相对更精简的代码:
- 此代码直接不用set,而是用vector<vector<int>>来实现
- 只在最后一步进行push_back,避免了重复的问题
- 输出结果如下,即上面图中,遍历后的顺序
3
2
2 3
1
1 3
1 2
1 2 3
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>>sub_sets;
int length = nums.size();
if (length < 1){
vector<int> empty;
sub_sets.push_back(empty);
return sub_sets;
}
vector<int>current_set;
find_all_subsets(nums, 0, current_set, sub_sets);
return sub_sets;
}
void find_all_subsets(vector<int>& nums, int loc, vector<int> ¤t_set, vector<vector<int>> & all_sub_sets){
int length = nums.size();
if (loc == length){
all_sub_sets.push_back(current_set);
return;
}
find_all_subsets(nums, loc + 1, current_set, all_sub_sets);
current_set.push_back(nums[loc]);
find_all_subsets(nums, loc + 1, current_set, all_sub_sets);
current_set.pop_back();
return;
}
};
1.3 其他人思路及代码供参考
作者:yi-shi-yi-mu-zi
链接:https://leetcode-cn.com/problems/subsets/solution/fen-zhi-fa-ji-qi-die-dai-xun-huan-shi-xian-he-di-g/
来源:力扣(LeetCode)
- 以下代码在执行过程中,只进行了在加入元素之后的push_back
- 所以需要在初始位置进行push_back空集合
void compute(vector<vector<int>>&subset_set,
vector<int>& nums,vector<int>&now_set,int i){
if(i>=nums.size()){
return;
}
now_set.push_back(nums[i]);
subset_set.push_back(now_set);
compute(subset_set,nums,now_set,i+1);
now_set.pop_back();
compute(subset_set,nums,now_set,i+1);
return;
}
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>>subset_set;//保存最终结果
vector<int>now_set={};
subset_set.push_back(now_set);
compute(subset_set,nums,now_set,0);
return subset_set;
}
};
1.4 分治法(动态规划)
分治思想,具体如下图:
- 假设没有元素,则只有空集合的情况
- 假设多了元素num[0],所有子集两种情况:在空集合的基础上一分为二,一部分是空集合不含num[0],维持原状,另一部分是空集合含num[0]
- 假设多了num[1],则在num[0]已有的所有子集上一分为二,一部分是已有num[0]的所有子集,不包含num[1],维持原状,另一部分在原来num[0]的所有子集基础上包含num[1]
- 依次类推,num[n+1]的所有自己在num[n]的所有子集基础上一分为二。
也可以从动态规划的角度来理解此代码
- 初始元素0个元素,所以空集合 set[0]={}
- 有一个元素num[0],集合为set[1]=set[0]+set[0].append(num[0])={}+{num[1]}
- 已有n元素子集合的方案数目为set[n]
- 加入新元素,则新set[n+1]=set[n]+set[n].append(num[0])
根据如上思路写出下面代码:
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>>result = { {} };
//result.push_back({});
int length = nums.size();
if (length < 1)return result;
for (int idx = 0; idx < length; idx++){
int current = nums[idx];
int copy_len = result.size();
for (int idx_copy = 0; idx_copy < copy_len; idx_copy++){
vector<int> copy = result[idx_copy];
copy.push_back(current);
result.push_back(copy);
}
}
return result;
}
};
注意事项:
- 初始化空的二维数组,可以用:result = { {} }或者/result.push_back({})表示第一个元素为空集合
1.5 位运算法实现穷举
用二进制中数位来实现当前元素选中与否
- 当前位与第几个元素对应
- 当前位为1,则当前元素选中
- 当前位为0,则当前元素不选中
代码实现:
- long long int共64位,因此集合内元素上限为64
- 可以用为了防止位运算& 与移位运算<<的优先级的问题,小心点加上括号
- long long int也可以作为地址取地址
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>>result ;
int length = nums.size();
long long int mask = 0x0000000000000001;
long long int top =( mask << length);
for (long long int select = 0; select < top; select++){
vector<int> sub;
for (long long int num_item = 0; num_item < length; num_item++){
if ((mask << num_item)&select){
sub.push_back(nums[num_item]);
}
}
result.push_back(sub);
}
return result;
}
};
二、连续子数组的最大和
剑指offer P237
2.1 类似股票最大值
参考这个:c++策略类编程问题汇总 之中求股票最大值的算法
https://blog.csdn.net/weixin_36474809/article/details/100170310
- 序列对于之前的所有的值在n位置累计为 sum[n] 则子序列的值相当于 m到n的子序列的值相当于 sum[n]- sum[m-1]
- 这就相当于求最大化sum[n]- sum[m-1]的问题,就是股票的最大利润
#include<iostream>
#include<vector>
#include<deque>
using namespace std;
class Solution {
public:
int FindGreatestSumOfSubArray(vector<int> array) {
int length = array.size();
if (length < 1)return 0;
for (int idx = 1; idx < length; idx++){
array[idx] += array[idx - 1];
}
int min = 0;
int max = array[0];
for (int idx = 1; idx < length; idx++){
int sum = array[idx] - min;
if (sum>max)max = sum;
if (array[idx] < min)min = array[idx];
}
return max;
}
};
int main(){
vector<int> array = { 6, -3, -2, 7, -15, 1, 2, 2 }; //out 8
Solution s1;
cout << s1.FindGreatestSumOfSubArray(array) << endl;
int end; cin >> end;
return 0;
}
2.2 推算方法
一个值用于存最大数值,另一个用于存当前sum,从左往右,如果前面sum小于0,则对于右边来说,最大的右边的子序列必然不包含其左边的项。
这种方法显然比上种方法简单很多:
class Solution {
public:
int FindGreatestSumOfSubArray(vector<int> array) {
int length = array.size();
if (length < 1)return 0;
int max=0x80000000;
int sum = 0;
for (int idx = 0; idx < length; idx++){
if (sum < 0)sum = array[idx];
else sum += array[idx];
if (max<sum)max = sum;
}
return max;
}
};
2.3 动态规划方法
可以从动态规划的角度来理解上面的问题
- f(i)表示包含data[i]的最大和子序列的和
- 转化就是,如果 data[i] >= 0, 则 f(i) = f(i-1) + data[i]
- 如果 data[i] < 0, 则 f(i) = data[i]
程序写法与上面一样。
三、最大子矩阵和
博客:
https://blog.csdn.net/qq_41929449/article/details/79892086
OJ链接:
ZOJ Problem Set - 1074
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1074
https://zoj.pintia.cn/problem-sets/91827364500/problems/91827364573
来自 <https://www.cnblogs.com/GodA/p/5237061.html>
解析:
https://www.cnblogs.com/aabbcc/p/6504605.html
3.1 题干
一个M*N的矩阵,找到此矩阵的一个子矩阵,并且这个子矩阵的元素的和是最大的,输出这个最大的值。
例如:3*3的矩阵:
3
-1 3 -1
2 -1 3
-3 1 2
和最大的子矩阵是:
3 -1
-1 3
1 2
最大和是7
As an example, the maximal sub-rectangle of the array:
4
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
is in the lower left corner:
9 2
-4 1
-1 8
and has a sum of 15.
3.2 错误的积分图的方法
仅仅例程可以通过,想法不错,但是仔细推导,发现错了
采用了积分图的方法,例程通过,但是OJ不过。吐槽下ZOJ,无法看到中间结果。
- 积分图中的值,相当于当前像素点中所有左,上,左上元素的和
- 想当然的以为,积分图中右上积分值减去左下积分值,等于子矩阵的和(错误)
- 例如红框减去蓝框,并非子矩阵的值,二是两个长条状的子矩阵的值。
#include<iostream>
#include<vector>
#include<deque>
#include<algorithm>
using namespace std;
int main(){
int row, col; cin >> row; col = row;
vector<int> each_col(col + 1);
vector<vector<int>> matrix(row + 1, each_col);
vector<vector<int>> min_matrix = matrix;
//算积分图,为了方便运算,第0行和第0列设为0,从1-row分别表示矩阵中的值
//每个像素点的值是其左边和上边所有元素的和
int max_value = 0;
//转换为求其右上元素减去左下元素的最大值
for (int idx_r = 1; idx_r <= row; idx_r++){
for (int idx_c = 1; idx_c <= col; idx_c++){
int current; cin >> current;
matrix[idx_r][idx_c] = matrix[idx_r - 1][idx_c] + matrix[idx_r][idx_c - 1] - matrix[idx_r - 1][idx_c - 1] + current;
min_matrix[idx_r][idx_c] = min(matrix[idx_r][idx_c], min(min_matrix[idx_r - 1][idx_c], min_matrix[idx_r][idx_c - 1]));
int sub_max = matrix[idx_r][idx_c] - min_matrix[idx_r][idx_c];
if (sub_max>max_value)max_value = sub_max;
}
}
cout << max_value << endl;
int end; cin >> end;
return 0;
}
3.3 在连续子数组基础上更改
如果对每个子矩阵进行求和比较,需要O((M*N)^2)的算法复杂度。显然不能满足要求
- 对于一维问题,连续子数组的最大和的问题,已经解决过了。可以用O(n)实现求解
- 但是连续子矩阵最大和的问题,可以分解为连续子数组的最大和的问题。
- 这样在一个维度上可以算法复杂度渐少为O(N),另一个维度上就依然是O(M^2),最终算法复杂度O(M*M*N)
- 问题转换:连续子矩阵的最大和=矩阵中连续的行的每一列求和为一个一维数组,然后在一维数组之上求解一维数组的连续子序列的最大和
以下代码完全通过
#include<iostream>
#include<vector>
#include<deque>
#include<algorithm>
using namespace std;
int main(){
int row, col; cin >> row; col = row;
vector<int> each_row(col);
vector<vector<int>> matrix(row, each_row);
for (int idx_r = 0; idx_r < row; idx_r++){
for (int idx_c = 0; idx_c < col; idx_c++){
cin >> matrix[idx_r][idx_c];
}
}
int max_value = matrix[0][0];
//先将当前up_row到down_row的和存为一维数组,然后在一维数组上寻找
for (int up_row = 0; up_row < row; up_row++){
for (int down_row = up_row; down_row < row; down_row++){
vector<int> row_sum(col, 0);
for (int idx_r = up_row; idx_r <= down_row; idx_r++){
for (int idx_c = 0; idx_c < col; idx_c++){
row_sum[idx_c] += matrix[idx_r][idx_c];
}
}
int sum = row_sum[0];
for (int idx_c = 1; idx_c < col; idx_c++){
if (sum < 0)sum = row_sum[idx_c];
else{
sum += row_sum[idx_c];
}
if (sum>max_value)max_value = sum;
}
}
}
cout << max_value << endl;
int end; cin >> end;
return 0;
}
四、直方图中面积最大的矩形
Leetcode 84。OJ:
https://leetcode-cn.com/problems/largest-rectangle-in-histogram/
4.1 题干
以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。问脂肪图的最大矩形的面积。
输入
6
2 1 5 6 2 3
输出10
4.2 思路
如何根据一遍 遍历O(N)找到当前柱子左边第一个比它矮的柱子?即如何找出每个位置对应的left_min_loc的值。
- 需要开辟一个数组left_min_loc,一个数组存储左边第一个比它矮的柱子。
- 从左往右遍历,如果右边位置idx+1上的柱子比左边idx位置的柱子长,则右边柱子的向左第一个比它矮的柱子的位置就是idx,例如上面location为2,3,5的柱子就是这么得到的
- 如果右边位置idx+1上的柱子比左边idx位置的柱子短,则需要与左边柱子的left_min_loc进行比较,如果idx位置的柱子比left_min_loc位置的柱子长,则它的left_min_loc为此值。如果当前idx的柱子依然比left_min_loc位置的柱子短,则继续向left_min_loc位置的前面的left_min_loc的柱子进行比较。
4.3 解答
解答,注意等号的判断。
- while (right_min_loc != length && current_height <= heights[right_min_loc] )语句中是<=,因为等于的情况下,依然可以向左继续推进。
- 同时,需要加入判断不等于length,不等于-1,不然就会内存溢出
#include<iostream>
#include<vector>
#include<deque>
#include<algorithm>
using namespace std;
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int length = heights.size();
if (length < 1)return 0;
vector<int>most_left_min(length); most_left_min[0] = -1;
vector<int>most_right_min(length); most_right_min[length - 1] = length;
for (int idx = 1; idx < length; idx++){
int current_height = heights[idx];
if (current_height>heights[idx - 1]){
most_left_min[idx] = idx - 1;
}
else{
int left_min_loc = most_left_min[idx - 1];
while (left_min_loc != -1 && current_height <= heights[left_min_loc]){
left_min_loc = most_left_min[left_min_loc];
}
most_left_min[idx] = left_min_loc;
}
}
//找到最右
for (int idx = length - 2; idx >= 0; idx--){
int current_height = heights[idx];
if (current_height>heights[idx + 1]){
most_right_min[idx] = idx + 1;
}
else{
int right_min_loc = most_right_min[idx + 1];
while (right_min_loc != length && current_height <= heights[right_min_loc]){
right_min_loc = most_right_min[right_min_loc];
}
most_right_min[idx] = right_min_loc;
}
}
int max_square = 0;
for (int idx = 0; idx < length; idx++){
int square = (most_right_min[idx] - most_left_min[idx] - 1)*heights[idx];
if (square>max_square)max_square = square;
}
return max_square;
}
};
int main(){
/*
6
2 1 5 6 2 3
//out 10
12
0 1 0 2 1 0 1 3 2 1 2 1
out 6
*/
int length; cin >> length;
vector<int> height(length);
for (int idx = 0; idx < length; idx++){
cin >> height[idx];
}
Solution s1;
cout << s1.largestRectangleArea(height) << endl;
//cout << max_value << endl;
int end; cin >> end;
return 0;
}
五、最大全1子矩阵
https://blog.csdn.net/huanghanqian/article/details/78771558
Leetcode 85 困难题
OJ:
https://leetcode-cn.com/problems/maximal-rectangle/
给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。例如,输入:
4 5
10100
10111
11111
10010
输出: 6
如果只确定左上右下的矩阵边,则需要对左上和右下进行遍历,算法复杂度O(MN*MN)
5.1 投影法
- 按行投影,遍历行,行内的投影为一行。全1则投影为1,有0则投影为0
- 然后在行内找出1最宽的列,当前行列最大面积 行宽*列宽
- 遍历所有行并且进行投影算法复杂度为O(M*M),求行内最宽列为O(N),一共O(M*M*N),依然算法复杂度过高。
#include<iostream>
#include<vector>
#include<deque>
#include<algorithm>
using namespace std;
class Solution {
public:
int maximalRectangle(vector<vector<char>>& matrix) {
int row = matrix.size();
if (row < 1)return 0;
int col = matrix[0].size();
int max_num1 = 0;
for (int upper_row = 0; upper_row < row; upper_row++){
for (int lower_row = upper_row; lower_row < row; lower_row++){
vector<char>compresed_row(col, '1');
int row_width = lower_row - upper_row + 1;
// 把矩阵投影到每一行,均为1才是1
for (int idx = upper_row; idx <= lower_row; idx++){
for (int idxc = 0; idxc < col; idxc++){
if (matrix[idx][idxc] == '0')compresed_row[idxc] = '0';
}
}
// 针对每一行的投影,算出行宽度
int col_width = 0;
for (int idx = 0; idx < col; idx++){
//矩阵存在,则将最大面积存入max_num1之中
if (compresed_row[idx] == '1'){
col_width++;
if (col_width*row_width>max_num1)max_num1 = col_width*row_width;
}
else{
col_width = 0;
}
}
}
}
return max_num1;
}
};
int main(){
/*
4 5
10100
10111
11111
10010
out 6
5 5
11111
11111
11110
11111
11111
//out 20
*/
int row, col; cin >> row >> col;
vector<char>each_row(col);
vector<vector<char>>matrix(row, each_row);
for (int idx = 0; idx < row; idx++){
for (int idxc = 0; idxc < col; idxc++){
cin >> matrix[idx][idxc];
}
}
Solution s1;
cout << s1.maximalRectangle(matrix) << endl;
//cout << max_value << endl;
int end; cin >> end;
return 0;
}
5.2 分解为面积最大的直方图子问题
- 将矩阵中的子矩阵,转换为求柱状图的最长长方形。
- 一次遍历找到最长长方形的算法复杂度为O(N)
- 在每一行的基础上往上找到最大的柱状图的长方形面积。
- 算法复杂度O(MN)非常简单
#include<iostream>
#include<vector>
#include<deque>
#include<algorithm>
using namespace std;
class Solution {
public:
int maximalRectangle(vector<vector<char>>& matrix) {
int row = matrix.size();
if (row < 1)return 0;
int col = matrix[0].size();
if (col < 1)return 0;
int max_area = 0;
for (int idx_r = 0; idx_r < row; idx_r++){
vector<int> col_tall(col, 0);
//cout << "row_hist_gram:" << idx_r << endl;
//cout << "col_tall" << endl;
for (int idx_c = 0; idx_c < col; idx_c++){
//统计出来当前行往上的直方图
for (int tall_r = idx_r; tall_r < row; tall_r++){
if (matrix[tall_r][idx_c] == '1')col_tall[idx_c]++;
else{
//cout << col_tall[idx_c] << " ";
break;
}
}
}
//cout << endl;
vector<int>left_min_loc(col); left_min_loc[0] = -1;
vector<int>right_min_loc(col); right_min_loc[col - 1] = col;
//从左向右遍历
//cout << "left_min_loc:";
for (int idx = 1; idx < col; idx++){
int current_height = col_tall[idx];
//比左边大,则左边位置即为left_min_loc
if (current_height>col_tall[idx - 1]){
left_min_loc[idx] = idx - 1;
}
else{// <= 左边,则可以左边再向左延申
int current_left_loc = idx - 1;
while (current_left_loc != -1 && current_height <= col_tall[current_left_loc]){
current_left_loc = left_min_loc[current_left_loc];
}
left_min_loc[idx] = current_left_loc;
}
//cout << left_min_loc[idx] << ' ';
}
//cout << endl;
//从右向左遍历
//cout << "right_min_loc:";
for (int idx = col - 2; idx >= 0; idx--){
int current_height = col_tall[idx];
//比右边大,则右边位置即为right_min_loc
if (current_height>col_tall[idx + 1]){
right_min_loc[idx] = idx + 1;
}
else{// <= 右边,则可以右边再向右延申
int current_right_loc = idx + 1;
while (current_right_loc != col && current_height <= col_tall[current_right_loc]){
current_right_loc = right_min_loc[current_right_loc];
}
right_min_loc[idx] = current_right_loc;
}
//cout << right_min_loc[idx] << ' ';
}
//cout << endl;
for (int idx = 0; idx < col; idx++){
int area = (right_min_loc[idx] - left_min_loc[idx] - 1)*col_tall[idx];
//cout << area << ' ';
if (area>max_area)max_area = area;
}
}
//cout << endl;
return max_area;
}
};
int main(){
/*
4 5
10100
10111
11111
10010
out 6
5 5
11111
11111
11110
11111
11111
//out 20
*/
int row, col; cin >> row >> col;
vector<char>each_row(col);
vector<vector<char>>matrix(row, each_row);
for (int idx = 0; idx < row; idx++){
for (int idxc = 0; idxc < col; idxc++){
cin >> matrix[idx][idxc];
}
}
Solution s1;
cout << s1.maximalRectangle(matrix) << endl;
int end; cin >> end;
return 0;
}