1. LeetCode 31. 下一个排列
大概思路:
- 由于是要找到下一个排列,所以变动范围尽肯能靠后(增加的幅度更小),从数组末尾开始的第一个降序(倒着看)排列的数到数组末尾就是数组的变动范围,记为[l, r]。
- 将区间[l, r]内的数分为区间左端点和剩下的所有数两个部分,第二部分是一个降序排列的数组,第一部分即区间左端点的数小于第二部分第一个数。
- 要想找到区间[l, r]的下一个排列,由于第二部分已经是最大排列,所以只能改变第一部分,在第二部分找出一个数大于第一部分且最小,将其与第一部分交换位置,然后将第二部分从小到大排列,即可得到下一个排列。
- 后缀部分的下一个排列也可以得出整个数组的下一个排列(递归?),若从后面找没有降序排列数,则将整个数组从小到大排列。
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int k = nums.size() - 1;
while(k > 0 && nums[k] <= nums[k - 1])k--;
if(k <= 0)reverse(nums.begin(), nums.end());
else
{
int t = k;
while(t < nums.size() && nums[t] > nums[k - 1])t++;
swap(nums[k - 1], nums[t - 1]);
sort(nums.begin() + k, nums.end());
}
}
};
2. LeetCode 32. 最长有效括号
大概思路:
- 对于任意一个括号数组前缀,若前缀中右括号数量大于左括号数量,则该前缀应该被舍弃。
- 满足第1点,并且左括号数量等于右括号数量的括号数组是有效的。
- 用双指针维护一个区间(左指针指向区间前一个数),维护区间左右括号数量,若左括号数等于右括号数,则更新答案;若左括号数小于右括号数,则该区间被舍弃,左指针指向右指针;若左括号数大于右括号数,则需要找到与该右括号数相匹配左括号左边第一个左括号的位置来更新答案(最麻烦的一种情况)
- 可以利用栈来实现,栈中存放双指针指向区间的所有左括号的位置,若右指针指向元素为左括号,则将该位置入栈;若右指针指向元素为右括号,此时若栈为空(对应第二种情况),若栈中只有一个元素(第一种情况),若栈中有多于1个元素(第三种情况)。
class Solution {
public:
int longestValidParentheses(string s) {
stack<int>a;
int res = 0;
for(int l = -1, r = 0; r < s.size(); r++)
{
if(s[r] == '(')a.push(r);
else
{
if(a.size())
{
a.pop();
if(a.size())res = max(res, r - a.top());
else res = max(res, r - l);
}
else l = r;
}
}
return res;
}
};
3. LeetCode 40. 组合总和 II
大概思路:
- 与组合总和I类似,多了一个数量限制(将整个数组排序,若有n个相同数(记为x)相邻,则x的数量限制为n。
- dfs,具体处理过程如下
class Solution {
public:
vector<vector<int>>ans;
vector<int>path;
vector<vector<int>> combinationSum2(vector<int>& c, int target) {
sort(c.begin(), c.end());
dfs(c, 0, target);
return ans;
}
void dfs(vector<int>c, int u, int target)
{
if(target == 0)
{
ans.push_back(path);
return;
}
if(u == c.size())return;
int k = u + 1;
while(k < c.size() && c[k] == c[u])k++;
int cnt = k - u;
for(int i = 0; i * c[u] <= target && i <= cnt; i++)
{
dfs(c, k, target - i * c[u]);
path.push_back(c[u]);
}
for(int i = 0; i * c[u] <= target && i <= cnt; i++)
path.pop_back();
}
};
4. LeetCode 41. 缺失的第一个正数
大概思路:
- 桶排序,将所有正数放入正数-x(x=1)的下标的位置。
- 从前往后扫描一遍,找到第一个a[nums[i]-1]不等于nums[i[的数
- 由于是正数(从1开始)所以x=1,可以拓展到x=其他的情形。
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int n = nums.size();
int res = 0;
for(int i = 0; i < n; i++)
{
while(nums[i] >= 1 && nums[i] <= n && nums[nums[i] - 1] != nums[i])
swap(nums[i], nums[nums[i] - 1]);
}
for(int i = 0; i < n; i++)
if(nums[i] != i + 1)
{
res = i + 1;
break;
}
if(!res)res = n + 1;
return res;
}
};
5. LeetCode 42. 接雨水
大概思路1:
- 统计每根柱子上面能有多少雨水,求所有柱子之和即可。
- 每根柱子上方最多能接多少雨水取决于其左边最高的柱子和右边最高的柱子,取两者的较小值-该柱子高度。若结果小于等于0说明该柱子上方没有雨水。
class Solution {
public:
int trap(vector<int>& height) {
int n = height.size(), res = 0;
if(n == 0)return 0;
vector<int>left_max(n), right_max(n);
left_max[0] = height[0], right_max[n - 1] = height[n - 1];
for(int i = 1; i < n; i++)
left_max[i] = max(left_max[i - 1], height[i]);
for(int i = n - 2; i >= 0; i--)
right_max[i] = max(right_max[i + 1], height[i]);
for(int i = 1; i < n - 1; i++)
res += min(left_max[i], right_max[i]) - height[i];
return res;
}
};
大概思路2:
- 更通用也稍微麻烦一点,利用一个单调递减栈(高度相同只保留最右边的那个)
- 只要新访问的元素大于等于栈顶,就一边不断弹出栈顶,一边更新答案(次栈顶<=h[i]:答案加上次栈顶与栈顶之差(即h)与i到次栈顶距离差(即w)的积;次栈顶>h[i]:答案加上h[i]与栈顶之差(即h)与i到次栈顶距离差(即w)的积)
- 代码中栈顶用last表示,次栈顶用stk,top()表示,栈中存放下标(计算w要用到)。
class Solution {
public:
int trap(vector<int>& h) {
stack<int>stk;
int n = h.size(), res = 0;
for(int i = 0; i < n; i++)
{
int last = 0;
while(stk.size() && h[stk.top()] <= h[i])
{
res += (h[stk.top()] - last) * (i - stk.top() - 1);
last = h[stk.top()];
stk.pop();
}
if(stk.size())res += (h[i] - last) * (i - stk.top() - 1);
stk.push(i);
}
return res;
}
};