算法之杂记

目录

回文

最长回文子串

最大公约数(Greatest common divisor)

最小公倍数

无序数组遍历,找连续子数组长度,要求O(n)--算法思想重要

2Sum/3Sum/3Sum Closest/4Sum

2Sum: 1. 两数之和(哈希表)

3Sum:15. 三数之和(双指针)

3Sum Clostst 16. 最接近的三数之和(双指针)

4Sum:18. 四数之和(双指针)

排列

1,全排列

2,下一个排列:31. 下一个排列

3,第k个排列:

数独:记得用哈希

36. 有效的数独

37. 解数独

三个类似的题

42. 接雨水 --- dp、双指针、单调栈

84. 柱状图中最大的矩形 --- 单调栈

11. 盛最多水的容器 -----贪心+双指针

借助用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:力扣

  • 递归的终止条件

放在递归调用的前面。

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)

128. 最长连续序列

法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(n^{2})。

2Sum: 1. 两数之和(哈希表)

思路:

法1:暴力枚举,两层for循环。O(n^{2})。

法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函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值