目录
最大公约数(Greatest common divisor)
无序数组遍历,找连续子数组长度,要求O(n)--算法思想重要
3Sum Clostst 16. 最接近的三数之和(双指针)
借助用std::getline函数封装字符串分割函数(仅按单字符分割,不是字符串分割)
-
回文
字符串回文
1,reverse
2,双指针
3,栈
整数回文
思路:不能反转数字,因为可能会溢出。
1,反转一半的数
思路:把后一半的数通过取余并累加(反转后一半的数), 前一半的数通过每次除10。最后判断前一半的数,和反转后后一半的数进行比较。
// 16ms
bool isPalindrome(int x) {
// 处理特殊情况
// (1) 负数不是回文
// (2) 除了 0 以外,所有个位是 0 的数字不可能是回文,因为最高位不等于 0
if (x < 0 || (x % 10 == 0 && x != 0)) {
return false;
}
int rev = 0;
while (x > rev) {
rev = rev * 10 + x % 10;
x /= 10;
}
return x == rev || x == rev / 10;
}
2,像字符串回文一样,不断地取第一位和最后一位(10进制)进行比较,相等则取第二位和倒数第二位,直到完成比较或者中途找到了不一致的位。
记:x每次去掉首位的数:(1)循环外得到初始d (2)循环内:x = x % d / 10; d /= 100;
// 8ms
bool isPalindrome(int x) {
if (x < 0) {
return false;
}
int d = 1; // divisor
while (x / d >= 10) {
d *= 10;
}
while (x > 0) {
int q = x / d; // quotient
int r = x % 10; // remainder
if (q != r) {
return false;
}
x = x % d / 10;
d /= 100;
}
return true;
}
3,转为字符串处理
// 20ms
bool isPalindrome(int x) {
if (x < 0) {
return false;
}
string s = to_string(x);
int start = 0;
int end = s.size() - 1;
while (start < end) {
if (s[start] == s[end]) {
start++;
end--;
} else {
break;
}
}
return start >= end;
}
-
最长回文子串
1,中心扩展法
leetcode5:力扣
- 递归的终止条件
放在递归调用的前面。
-
最大公约数(Greatest common divisor)
- 更相减损术
int gcd(int x,int y)
{
while(x!=y)
{
if(x>y) x=x-y;
else
y=y-x;
}
return x;
}
-
最小公倍数
x / gcd(x, y) * y
-
无序数组遍历,找连续子数组长度,要求O(n)--算法思想重要
分析:
1,排除排序,因为排序是O(nlogn)。
2,要借用哈希表,先初始化哈希表,并在遍历数组的同时 (1)判断当前元素在哈希表满足条件时contiue. (2)当前元素不满足条件进行一个小循环处理找与该元素连续的元素。这样才能保证是O(n)的,因为哈希表的查找是O(1)
法1:用一个哈希表 unordered_map<int, bool> used 记录每个元素是否使用,对每个元素,以该
元素为中心,往左右扩张,直到不连续为止,记录下最长的长度。
int longestConsecutive1(const vector<int>& nums) const
{
if (nums.size() == 0) {
return 0;
}
int result = 0;
unordered_map<int, bool> used;
for (auto ele : nums) {
used[ele] = false;
}
for (auto ele : nums) {
if (used[ele] == true) {
continue;
}
int consecutiveLen = 1;
// 中心往右扩展
for (int j = ele + 1; used.find(j) != used.end(); j++) {
consecutiveLen++;
used[j] = true;
}
// 中心往左扩展
for (int j = ele - 1; used.find(j) != used.end(); j--) {
consecutiveLen++;
used[j] = true;
}
result = max(result, consecutiveLen);
}
return result;
}
法2:用hash表unordered_set,在遍历原始数组时,对连续数字序列的起始元素进行小循环计数,对连续数字序列的非起始元素continue。
int longestConsecutive(const vector<int>& nums) const
{
if (nums.size() == 0) {
return 0;
}
int result = 0;
unordered_set<int> hashSet;
for (auto ele : nums) {
hashSet.insert(ele);
}
for (auto ele : nums) {
if (hashSet.find(ele - 1) != hashSet.end()) {
continue;
}
int consecutiveLen = 1;
while (hashSet.find(ele + 1) != hashSet.end()) {
consecutiveLen++;
ele++;
}
result = max(result, consecutiveLen);
}
return result;
}
2Sum/3Sum/3Sum Closest/4Sum
思路:因为是具体指定的nSum,规定了n,因此用暴力枚举回溯法求组合总数不合适(回溯-子集_组合_排序_练习_u011764940的博客-CSDN博客, leetcode40:组合总和 II)。因为(1)回溯是只要能有元素和加起来等于target就都符合条件,没有规定一定是n个元素; (2)回溯的时间复杂度是O()。
2Sum: 1. 两数之和(哈希表)
思路:
法1:暴力枚举,两层for循环。O()。
法2:哈希表(find()方法,O(1)) + 一次遍历(O(n))。O(n)。
注意:因为输出是要求idx1必须小于idx2,所以判断条件是:if (pos != eleMap.end() && pos->second > i)
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> eleMap;
for (size_t i = 0; i < nums.size(); i++) {
eleMap[nums[i]] = i;
}
vector<int> result;
for (size_t i = 0; i < nums.size(); i++) {
int gap = target - nums[i];
auto pos = eleMap.find(gap);
if (pos != eleMap.end() && pos->second > i) {
result.push_back(i);
result.push_back(pos->second);
break;
}
}
return result;
}
3Sum:15. 三数之和(双指针)
思路:
普通逻辑:三层循环+去重。但是可以对第2和第3层循环合并,用一轮循环双指针来代替。
1,因为是有多个解,解不能重复
(1)因此需要对原序列排序。保证我们枚举的三元组 (a, b, c)满足a<=b<=c,保证了只有 (a, b, c)这个顺序会被枚举到,而 (b, a, c)、(c, b, a) 等等这些不会,这样就避免结果重复。
(2)每一层循环,相邻两次枚举的元素不能相同(因为若有解一定是与之前重复解)。来减少枚举次数,并避免结果重复。注意:内部双指针循环找到解内部的两个小循环是用来去重(在4Sum处也有另一种处理但不好)。
2,外层循环固定三数中的第一个,内层循环双指针左右夹逼
3,本题特殊剪枝:三数之和结果为0,那么三数中的第一个数都大于target,则三数和肯定大于target。剪枝减少枚举次数。
// 迭代器版本
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
if (nums.size() == 0) {
return result;
}
sort(nums.begin(), nums.end());
const int target = 0;
int cur3Sum = 0;
for (auto i = nums.begin(); i != nums.end() - 1; i++) {
if (*i > 0) { // 剪枝减少枚举次数: 3数中的第1个数就大于0, 则3数和一定大于0
break;
}
if (i > nums.begin() && *i == *(i - 1)) { // 剪枝去重: 结果不能重复, 若起始点值相同, 则若有解一定是与之前重复解
continue;
}
auto j = i + 1;
auto k = nums.end() - 1;
while (j < k) {
cur3Sum = *i + *j + *k;
if (cur3Sum == target) {
result.push_back({*i, *j, *k});
while (j < k && *j == *(j + 1)) { // 去重
j++;
}
while (j < k && *k == *(k - 1)) { // 去重
k--;
}
j++;
k--;
} else if (cur3Sum < target) {
j++;
} else if (cur3Sum > target) {
k--;
}
}
}
return result;
}
// 思路:双指针,左右夹逼。
//下标版本耗时96ms, 击败60.7%
vector<vector<int>> threeSum2(vector<int>& nums) {
vector<vector<int>> res;
if(nums.size() < 3) {
return res;
}
int len = nums.size();
const int target = 0;
int cur3Sum = 0;
sort(nums.begin(), nums.end());
for(int i = 0; i < len; i++) {
if(nums[i] > 0) { // 剪枝减少枚举次数: 3数中的第1个数就大于0, 则3数和一定大于0
break;
}
if(i > 0 && nums[i] == nums[i - 1]) { // 剪枝去重: 结果不能重复, 若起始点值相同, 则若有解一定是与之前重复解
continue;
}
int left = i + 1;
int right = len - 1;
while(left < right) {
cur3Sum = nums[i] + nums[left] + nums[right];
if(cur3Sum == target) {
res.push_back({nums[i], nums[left], nums[right]});
while(left < right && nums[left] == nums[left + 1]) {
left++;
}
while(left < right && nums[right] == nums[right-1]) {
right--;
}
left++;
right--;
}
else if(cur3Sum > target) {
right--;
}
else {
left++;
}
}
}
return res;
}
3Sum Clostst 16. 最接近的三数之和(双指针)
思路:
1,同3Sum
2,1个剪枝减少枚举次数(不是去重,因为本题只有一个解):遍历外层循环时,若起始点值相同, 则若有解一定是与之前重复解
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
int minGap = INT_MAX;
int result = nums[0] + nums[1] + nums[2];
for (size_t i = 0; i < nums.size() - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
size_t j = i + 1;
size_t k = nums.size() - 1;
while (j < k) {
int sum = nums[i] + nums[j] + nums[k];
int gap = abs(sum - target);
if (gap <= minGap) {
result = sum;
minGap = gap;
}
if (sum < target) {
j++;
} else if (sum > target) {
k--;
} else if (sum == target) {
return result;
}
}
}
return result;
}
};
4Sum:18. 四数之和(双指针)
思路:
法1:思路同3Sum。去重、剪枝等都与3Sum相同。
第二中解法中:内部双指针循环找到解内部没有用两个小循环是用来去重,而是把重复的(a,b,c,d)加入结果集,最后用unique + erase去重,但是效率低。
法2:用哈希表先保存两个数的和及其对应的索引。因为a、b有序,c、d有序,若要保证push_back到result中的(a、b、c、d)有序,需要满足b<c,即在c<=b时continue去重。
class Solution {
public:
// 思路同3sum. 双指针,下标法左右夹逼。 不要用hashmap法--更慢。
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
if (nums.size() < 4) {
return result;
}
sort(nums.begin(), nums.end());
size_t len = nums.size();
for (size_t a = 0; a < len - 3; a++) {
if (a > 0 && nums[a] == nums[a - 1]) {
continue;
}
for (size_t b = a + 1; b < len - 2; b++) {
if (b > a + 1 && nums[b] == nums[b - 1]) {
continue;
}
size_t c = b + 1;
size_t d = len - 1;
while (c < d) {
if (nums[a] + nums[b] + nums[c] + nums[d] == target) {
result.push_back({nums[a], nums[b], nums[c], nums[d]});
while (c < d && nums[c] == nums[c + 1]) {
c++;
}
c++;
while (c < d && nums[d] == nums[d - 1]) {
d--;
}
d--;
} else if (nums[a] + nums[b] + nums[c] + nums[d] < target) {
c++;
} else {
d--;
}
}
}
}
return result;
}
vector<vector<int>> fourSum1(vector<int>& nums, int target) {
vector<vector<int>> result;
if (nums.size() < 4) {
return result;
}
sort(nums.begin(), nums.end());
auto last = nums.end();
for (auto a = nums.begin(); a < prev(last, 3); a++) {
for (auto b = next(a); b < prev(last, 2); b++) {
auto c = next(b);
auto d = prev(last);
while (c < d) {
if (*a + *b + *c + *d == target) {
result.push_back({*a, *b, *c, *d});
//这里不管是否答案有重复,后面统一去重
// while (c < d && *c == *(c + 1)) {
// c++;
// }
c++;
// while (c < d && *d == *(c - 1)) {
// d--;
// }
d--;
} else if (*a + *b + *c + *d < target) {
c++;
} else {
d--;
}
}
}
}
sort(result.begin(), result.end());
result.erase(unique(result.begin(), result.end()), result.end());
return result;
}
vector<vector<int>> fourSum2(vector<int>& nums, int target) {
vector<vector<int>> result;
if (nums.size() < 4) {
return result;
}
sort(nums.begin(), nums.end());
unordered_map<int, vector<pair<int, int>>> cache;
for (size_t a = 0; a < nums.size(); a++) {
for (size_t b = a + 1; b < nums.size(); b++) {
cache[nums[a] + nums[b]].push_back(make_pair(a, b));
}
}
for (size_t c = 0; c < nums.size(); c++) {
for (size_t d = c + 1; d < nums.size(); d++) {
const int key = target - nums[c] - nums[d];
if (cache.find(key) == cache.end()) {
continue;
}
const auto& vec = cache[key];
for (size_t k = 0; k < vec.size(); k++) {
if (c <= vec[k].second) {
continue;
}
result.push_back({nums[vec[k].first], nums[vec[k].second], nums[c], nums[d]});
}
}
}
sort(result.begin(), result.end());
result.erase(unique(result.begin(), result.end()), result.end());
return result;
}
};
排列
1,全排列
回溯:回溯算法解子集、组合、排序_u011764940的博客-CSDN博客
2,下一个排列:31. 下一个排列
思路:
1,从right到left找第一个降序(越过升序和平序)的数A: xxxxxAxxxxxxxxx
2,从right到left找第一个大于(越过降序和平序)A的数B: xxxxxAxxxxBxxxx
3,交换A和B:xxxxBxxxxAxxxx
4,交换过后,把B后面的数从小到大排序
// 法1 下标
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int i = nums.size() - 2;
while (i >= 0 && nums[i] >= nums[i + 1]) { // 从right向left找第一个降序的点i。越过升序或平序点。
i--;
}
if (i >= 0) { // 有i点满足降序
int j = nums.size() - 1;
while (j >= 0 && nums[j] <= nums[i]) { // 从right向left找第一个大于i点的点j。越过小于等于的。
j--;
}
swap(nums[i], nums[j]);
}
reverse(nums.begin() + i + 1, nums.end()); // 满足对若原始序全是升序时i=-1的处理
}
}
// 法2 迭代器
class Solution {
public:
void nextPermutation(vector<int>& nums) {
NextPermutationHelper(nums.begin(), nums.end());
}
template <typename BidiIt>
void NextPermutationHelper(BidiIt first, BidiIt last)
{
auto rfirst = reverse_iterator<BidiIt>(last);
auto rlast = reverse_iterator<BidiIt>(first);
auto pivot = next(rfirst);
// 从right到left找第一个降序的数. 等于的话也继续往后找,一定要找到降序点。
while (pivot != rlast && *pivot >= *prev(pivot)) {
++pivot;
}
// 若从right到left都是升序,则当前数组就是最大的数,此时直接reverse为最小的数
if (pivot == rlast) {
reverse(rfirst, rlast);
return;
}
int target = *pivot;
// 从right到left找第一个大于A的数, find_if 如果没找到,返回尾迭代器pivot
// auto changePos = find_if(rfirst, pivot, [target](int ele) { return ele > target; }); // ok
// auto changePos = find_if(rfirst, pivot, [pivot](int ele) { return ele > *pivot; }); // ok
auto changePos = find_if(rfirst, pivot, bind(CheckSize, std::placeholders::_1, target)); // ok. #include <functional> and using namespace std::placeholders;
// 交换
swap(*changePos, *pivot);
// 排序
reverse(rfirst, pivot);
}
static bool CheckSize(int ele, int target)
{
return ele > target;
}
};
3,第k个排列:
思路:
法1:康托展开
法2:暴力枚举,调用k-1次STL next_permutation
法1:康托展开
康托展开介绍:
康托展开和逆康托展开_wbin233的博客-CSDN博客_逆康托展开
康托展开:给定一个排列,计算这个排列处于所有排列里的第几个。
公式:
X=smaller[0] * (n-1)! + smaller[1] * (n-2)! + ... + smaller[i] * (n-i-1)! + ... + smaller[n-1] * 0!
其中, smaller[i]表示在(i, end)范围比input[i]小的个数. 这就是康托展开。
如:在(1,2,3,4,5)5个数的排列组合中,计算 34152的康托展开值。
首位是3,则小于3的数有两个,为1和2,a[5]=2,则首位小于3的所有排列组合为 smaller[5]*(5-1)!
第二位是4,则小于4的数有两个,为1和2,注意这里3并不能算,因为3已经在第一位,所以其实计算的是在第二位之后小于4的个数。因此smaller[4]=2
第三位是1,则在其之后小于1的数有0个,所以smaller[3]=0
第四位是5,则在其之后小于5的数有1个,为2,所以smaller[2]=1
最后一位就不用计算啦,因为在它之后已经没有数了,所以smaller[1]固定为0
根据公式:
X = 2 * 4! + 2 * 3! + 0 * 2! + 1 * 1! + 0 * 0! = 2 * 24 + 2 * 6 + 1 = 61
所以比 34152 小的组合有61个,即34152是排第62。
const int FAC[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880}; // 阶乘
int cantor(int *input, int n)
{
int x = 0;
for (int i = 0; i < n; ++i) {
int smaller = 0; // 在当前位之后小于其的个数
for (int j = i + 1; j < n; ++j) {
if (input[j] < input[i])
smaller++;
}
x += FAC[n - i - 1] * smaller; // 康托展开累加
}
return x; // 康托展开值
}
康托展开逆运算: 输出n的所有排列中的第k个数
一开始已经提过了,康托展开是一个全排列到一个自然数的双射,因此是可逆的。即对于上述例子,在(1,2,3,4,5)给出61可以算出起排列组合为 34152。
由上述的计算过程可以容易的逆推回来,具体过程如下:
用 61 / 4! = 2余13,说明smaller[0]=2,说明比首位小的数有2个,所以首位为3。
用 13 / 3! = 2余1,说明smaller[1]=2,说明在第二位之后小于第二位的数有2个,所以第二位为4。
用 1 / 2! = 0余1,说明smaller[2]=0,说明在第三位之后没有小于第三位的数,所以第三位为1。
用 1 / 1! = 1余0,说明smaller[3]=1,说明在第四位之后小于第四位的数有1个,所以第四位为5。
最后一位自然就是剩下的数2啦。通过以上分析,所求排列组合为 34152。
具体代码实现如下:(假设排列数小于10个)
const int FAC[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880}; // 阶乘
void decantor(int n, int k) // n个数的排列,输出第k个数(这里计数是从1开始记,所以如果从0开始记,那么调用该函数时需要把k减一再传入)
{
vector<int> rest; // 存放当前可选数,保证有序
for(int i = 1; i <= n; i++) // 1, 2, 3, 4 ..... n
rest.push_back(i);
vector<int> ans; // 所求排列组合
for(int i = n; i >= 1; i--)
{
int rem = k % FAC[i - 1]; // 余数
int quo = k / FAC[i - 1]; // 商数
k = rem;
ans.push_back(rest[quo]); // 剩余数里第quo + 1个数为当前位
rest.erase(rest.begin() + quo); // 移除选做当前位的数
}
}
本题的解:
// 0 ms 5.9 MB
class Solution {
public:
string getPermutation(int n, int k) {
generateFac(n);
return decantor(n, --k); // k先减1, 因为是从ans的第0号开始算
}
// 计算n个阶乘
void generateFac(int n)
{
fac = vector<int>(n + 1, 0);
fac[0] = 1;
for (int i = 1; i <= n; i++) {
fac[i] = i * fac[i - 1];
}
}
//康托展开逆运算
string decantor(int n, int k) // n个数的排列,输出第k个数
{
vector<int> rest; // 存放当前可选数,保证有序
for(int i = 1; i <= n; i++) { // 1, 2, 3, 4 ..... n
rest.push_back(i);
}
vector<int> ans; // 所求排列组合
for(int i = n; i >= 1; i--) {
int rem = k % fac[i - 1]; // 余数
int quo = k / fac[i - 1]; // 商数
k = rem;
ans.push_back(rest[quo]); // 剩余数里第quo + 1个数为当前位
rest.erase(rest.begin() + quo); // 移除选做当前位的数
}
string res;
for (auto number : ans) {
res.push_back(number + '0');
}
return res;
}
private:
vector<int> fac; // 阶乘结果. 如10个数的阶乘结果{1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
};
法2:暴力枚举,调用k-1次STL next_permutation
// 1092 ms 5.9 MB
class Solution {
public:
string getPermutation(int n, int k) {
string permStr;
for (int i = 1; i <= n; i++) {
permStr += '0' + i;
}
for (size_t i = 0; i < k - 1; i++) {
next_permutation(permStr.begin(), permStr.end());
}
return permStr;
}
};
数独:记得用哈希
数独问题一定记得要用hash。来减少一层循环。(类似的,26字母表,0-9数字这种问题一般可以考虑用hash数组)
36. 有效的数独
class Solution {
public:
bool isValidSudoku(vector<vector<char>>& board)
{
vector<bool> used(board.size(), false);
for (int i = 0; i < 9; i++) {
fill(used.begin(), used.end(), false);
for (int j = 0; j < 9; j++) {
if (check(board[i][j], used) == false) {
return false;
}
}
fill(used.begin(), used.end(), false);
for (int j = 0; j < 9; j++) {
if (check(board[j][i], used) == false) {
return false;
}
}
}
for (int i = 0; i < 3; i++) { // A: 9个box
for (int j = 0; j < 3; j++) { // A: 9个box
fill(used.begin(), used.end(), false);
for (int k = i * 3; k < i * 3 + 3; k++) { // B: 每个box的9个pix
for (int m = j * 3; m < j * 3 + 3; m++) {// B: 每个box的9个pix
if (check(board[k][m], used) == false) {
return false;
}
}
}
}
}
return true;
}
bool check(char ch, vector<bool>& used)
{
if (ch == '.') {
return true;
}
if (used[ch - '1'] == true) { // 哈希
return false;
}
used[ch - '1'] = true; // 哈希
return true;
}
};
37. 解数独
回溯算法解子集、组合、排序_u011764940的博客-CSDN博客
三个类似的题
42. 接雨水 --- dp、双指针、单调栈
思路:
法1:dp:对于每个柱子,找到其左右两边最高的柱子,该柱子能容纳的面积就是min(max_left, max_right) - height。所以:
(1)从左往右扫描一遍,对于每个柱子,求取左边最大值;
(2)从右往左扫描一遍,对于每个柱子,求最大右值;
(3)再扫描一遍,把每个柱子的面积并累加。
注意:与柱状图最大面积思路(对于每个柱子,求出其左右两侧第一个比它小的元素位置)的区别。
法2:双指针
法3:单调栈
从左到右扫描,维护一个单调递增栈,单调栈存储的是下标,满足从栈顶到栈底的下标对应的数组height 中的元素递增。
当前元素若满足单调性的元素,直接入栈
当前元素若会打破栈的单调性,则循环出栈并处理出栈的元素。
从左到右遍历数组,遍历到下标i时,如果栈内至少有两个元素,记栈顶元素为top, top 的下面一个元素是left,则一定有height[left] ≥ height[top]。如果 height[i] > height[top],则得到一个可以接雨水的区域,该区域的宽度是 i- left −1,高度是 min(height[left], height[i]) − height[top],根据宽度和高度即可计算得到该区域能接的雨水量。
class Solution {
public:
// dp--ok
int trap(vector<int>& height) {
int n = height.size();
if (n == 0) {
return 0;
}
vector<int> leftMax(n);
leftMax[0] = height[0];
for (int i = 1; i < n; ++i) {
leftMax[i] = max(leftMax[i - 1], height[i]);
}
vector<int> rightMax(n);
rightMax[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; --i) {
rightMax[i] = max(rightMax[i + 1], height[i]);
}
int ans = 0;
for (int i = 0; i < n; ++i) {
ans += min(leftMax[i], rightMax[i]) - height[i];
}
return ans;
}
// 双指针 --- 与dp思路一样, 是对dp的空间优化
int trap1(vector<int>& height) {
int ans = 0;
int left = 0, right = height.size() - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = max(leftMax, height[left]);
rightMax = max(rightMax, height[right]);
if (height[left] < height[right]) { // 必有leftMax<rightMax. 下标left处能接
// 的雨水量等于leftMax−height[left]
ans += leftMax - height[left];
++left;
} else { // 则必有leftMax≥rightMax. 下标right处能接的雨水量等于
// rightMax−height[right]
ans += rightMax - height[right];
--right;
}
}
return ans;
}
// 单调栈--单调递增栈(从栈顶到栈底下标对应的数组值单调递增)--ok
int trap2(vector<int>& height) {
int ans = 0;
stack<int> stk;
int n = height.size();
for (int i = 0; i < n; ++i) {
while (!stk.empty() && height[i] > height[stk.top()]) {
int top = stk.top();
stk.pop();
if (stk.empty()) {
break;
}
int left = stk.top();
int currWidth = i - left - 1;
int currHeight = min(height[left], height[i]) - height[top];
ans += currWidth * currHeight;
}
stk.push(i);
}
return ans;
}
};
84. 柱状图中最大的矩形 --- 单调栈
思路:
对每一个元素,求出其左右两侧第一个比它小的元素位置。则以当前元素为中心可以得到的最大面积就是(right_first_small - left_first_small) * height[i]。
注意:与接雨水思路(对于每个柱子,找到其左右两边最高的柱子)的区别。
方法一:一遍从前往后框架,一遍从右往左框架:
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int n = heights.size();
vector<int> left(n), right(n);
stack<int> mono_stack;
for (int i = 0; i < n; ++i) {
while (!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) {
mono_stack.pop();
}
left[i] = (mono_stack.empty() ? -1 : mono_stack.top());
mono_stack.push(i);
}
mono_stack = stack<int>();
for (int i = n - 1; i >= 0; --i) {
while (!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) {
mono_stack.pop();
}
right[i] = (mono_stack.empty() ? n : mono_stack.top());
mono_stack.push(i);
}
int ans = 0;
for (int i = 0; i < n; ++i) {
ans = max(ans, (right[i] - left[i] - 1) * heights[i]);
}
return ans;
}
};
方法二:一遍遍历,两个框架思想结合
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
int n = heights.size();
vector<int> left(n), right(n, n);
stack<int> mono_stack;
for (int i = 0; i < n; ++i) {
while (!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) {
right[mono_stack.top()] = i;
mono_stack.pop();
}
left[i] = (mono_stack.empty() ? -1 : mono_stack.top());
mono_stack.push(i);
}
int ans = 0;
for (int i = 0; i < n; ++i) {
ans = max(ans, (right[i] - left[i] - 1) * heights[i]);
}
return ans;
}
};
11. 盛最多水的容器 -----贪心+双指针
思路:
// 贪心+双指针
不管left++还是right--, 都是len = len - 1; 因此,只能是固定大的y,让小y的一端缩小靠近,才有可能尝试得到下面的大值。否则若固定小y,让大y一端靠近,那一定是得到一个小值
class Solution {
public:
// 贪心+双指针
// 不管left++还是right--, 都是len = len - 1;
// 因此,只能是固定大的y,让小y的一端缩小靠近,
// 才有可能尝试得到下面的大值。否则若固定小y,
// 让大y一端靠近,那一定是得到一个小值
int maxArea(vector<int>& height) {
int start = 0;
int end = height.size() - 1;
int area = 0;
while (start < end) {
area = max(area, min(height[start], height[end]) * (end - start));
if (height[start] < height[end]) {
start++;
} else {
end--;
}
}
return area;
}
};
借助用std::getline函数封装字符串分割函数(仅按单字符分割,不是字符串分割)
C++标准库没有提供现成的拆分函数,推荐用这个实现:
std::getline - cppreference.com
std::getline第三参数char delim默认是换行符'\n',可以改为任何分割字符来进行分割字符串。
void SplitString(const string& input, char sperChar, vector<string>& outArray)
{
stringstream sstr(input);
string token;
while (getline(sstr, token, sperChar)) {
outArray.push_back(token);
}
}
其它语言都有现成的split函数。