目錄
一、集合的子集合
給定一組不含重複元素的整數數組 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;
}