42.接雨水
动态规划,左右各扫描一次。每一个柱子的高度方向可以接的雨水的数量 = min(从当前柱子向左看的最高柱子高度, 从当前柱子向右看的最高柱子高度) - 当前柱子高度.
找到数组中从下标 i 到最左端最高的条形块高度left_max。
找到数组中从下标 i 到最右端最高的条形块高度right_max。
扫描数组 height 并更新答案:
累加min(max_left[i],max_right[i])−height[i] 到 ans上
class Solution {
public:
int trap(vector<int>& height)
{
int ans=0;
int n=height.size();
if(n<3) return 0;
int left[n];
int right[n];
memset(left,0,sizeof left);
memset(right,0,sizeof right);
int tmp_max=0;
for(int i=0;i<n;i++)
{
left[i]=max(height[i],tmp_max);
tmp_max=left[i];
}
int tmp_m=0;
for(int i=n-1;i>=0;i--)
{
right[i]=max(height[i],tmp_m);
tmp_m=right[i];
}
for(int i=0;i<n;i++)
{
int tmp=min(left[i],right[i])-height[i];
if(tmp>0) ans+=tmp;
}
return ans;
}
};
84.柱状图中最大的矩形
单调栈。单调递增栈
元素大于栈顶,则元素下标入栈;
当前元素和栈顶元素相同时,将栈顶元素出栈,当前元素入(将同一个高度的矩形的右边界增加);
元素小于栈顶,栈顶元素出栈,一直出栈,直到栈顶元素小,再将当前元素下标入栈。
要计算以某条矩形为高的最大矩形面积,应该让其底部的宽度尽可能往两侧延伸直到遇到左右两侧第一个比这个矩形条的高度更小的矩形条,此时的宽度就是最大宽度
当元素出栈时,说明这个新元素是出栈元素向后找第一个比其小的元素。
当元素出栈后,说明新栈顶元素是出栈元素向前找第一个比其小的元素:当 6 出栈时,5 成为新的栈顶,那么 5 就是 6 左边第一个比 6 小的元素。
利用单调栈来寻找每个矩形条的左右侧的第一个比它小的矩形条(遍历每个高度)
1.寻找右侧第一个比它小的
2.寻找左侧第一个比它小的,显然,在栈中的元素stack[n-1]肯定要小于stack[n],并且我们按照从左到右的扫描顺序,可以保证stack[n-1]肯定是stack[n]左侧第一个更小元素。
class Solution {
public:
int largestRectangleArea(vector<int>& heights)
{
int n=heights.size();
if(n==0) return 0;
if(n==1) return heights[0];
stack<int> s;//栈内每个元素都是数据变小处的序号(即递增范围的左边界-1)
int ans=0;
//寻找右边界,很明显,就是当前heights[i]元素右边界为i,如果heights[i]大于等于栈顶元素就入栈了
//寻找左边界,显然就是栈顶下一个元素,如果栈顶没有下一个元素,说明栈顶元素是整个前i个元素最小的了
heights.push_back(0);//确保最后一个元素能用上
for(int i=0;i<n+1;i++)
{
while(!s.empty() && heights[s.top()]>=heights[i])
{
int h=heights[s.top()];
s.pop();
if(s.empty())
{
ans=max(ans,h*i);
}
else
{
int l=s.top();
ans=max(ans,h*(i-l-1));
}
}
s.push(i);
}
return ans;
}
};
887.鸡蛋掉落
你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N 共有 N 层楼的建筑。每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。
你的目标是确切地知道 F 的值是多少。无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少?
鸡蛋没碎就可以重复使用。
class Solution {
public:
int superEggDrop(int K, int N)
{
int dp[K+1][N+1];//i个鸡蛋,移动j次能确定的最大层
memset(dp,0,sizeof dp);
for(int j=1;j<=N;j++)
{
for(int i=1;i<=K;i++)
{
//1.鸡蛋碎了:上面的楼层测试完毕,接下来在更低的楼层试,dp[k-1][m-1];
//2.鸡蛋没碎:下面的楼层测试完毕,接下来在更高的楼层试,dp[k][m-1];
dp[i][j]=dp[i-1][j-1]+dp[i][j-1]+1;
if(dp[i][j]>=N) return j;
}
}
return N;
}
};
1028.从先序遍历还原二叉树
我们从二叉树的根节点 root 开始进行深度优先搜索。
在遍历中的每个节点处,我们输出 D 条短划线(其中 D 是该节点的深度),然后输出该节点的值。(如果节点的深度为 D,则其直接子节点的深度为 D + 1。根节点的深度为 0)。
如果节点只有一个子节点,那么保证该子节点为左子节点。
给出遍历输出 S,还原树并返回其根节点 root。
栈中存放当前节点的父节点。如果栈的size大于当前深度,说明栈顶不是他的父节点。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int depth(int u,string s)
{
int ans=0;
u--;
while(u>=0 && s[u]=='-')
{
ans++;
u--;
}
return ans;
}
int current_num(int u,string s)
{
int ans=0;
while(u<s.size() && s[u]!='-')
{
ans*=10;
ans+=(s[u]-'0');
u++;
}
return ans;
}
TreeNode* recoverFromPreorder(string S)
{
if(S.size()==0) return NULL;
stack <TreeNode*> st;
int root_val=current_num(0,S);
TreeNode* root=new TreeNode(root_val);
st.push(root);
for(int i=1;i<S.size();i++)
{
if(S[i]=='-') continue;
if(S[i]!='-' && S[i-1]!='-') continue;//说明这一位属于上一个数
int d=depth(i,S);
while(st.size()>d) st.pop();
TreeNode* node=st.top();
TreeNode* son =new TreeNode(current_num(i,S));
if(node->left) node->right=son;
else node->left=son;
st.push(son);
}
return root;
}
};
920.播放列表的数量
你的音乐播放器里有 N 首不同的歌,在旅途中,你的旅伴想要听 L 首歌(允许歌曲重复)。请你为她按如下规则创建一个播放列表:
每首歌至少播放一次。
一首歌只有在其他 K 首歌播放完之后才能再次播放。
返回可以满足要求的播放列表的数量。由于答案可能非常大,请返回它模 10^9 + 7 的结果。
440.字典序的第k小数字
给定整数 n 和 k,找到 1 到 n 中字典序第 k 小的数字。
注意:1 ≤ k ≤ n ≤ 109。
字典树可以看作十叉树,先序遍历。
一、首先我们可以通过简单的数学方式,计算出[l,l+1]之间有多少个数字。
/* 计算[n,n+1]之间存在多少个数字 */
/* 计算每扩大10倍有多少数字,相加即可 */
int GetNodeNums(int n, int max) {
int ans = 1;
long left = (long)n * 10 + 0; // 扩大十倍的左边界
long right = (long)n * 10 + 9; // 扩大十倍的右边界
while(max >= left) {
if(max <= right)
{
ans += (max - left + 1); // max 在 [l,r]之间
}
else
{
ans += (right - left + 1); // max 在 下一层
}
left = left * 10 + 0; // 下一层
right = right * 10 + 9;
}
return ans;
}
二、
初始化 l = 1, r = 9, k = k(还差数字个数)
令f(x)表示[x,x+1]之间数字个数
步骤1:遍历 i -> [l,r]
步骤2:
如果 k > i, k-=f(i).即第k个数不在[i,i+1]中,还需要找k-=f(i)个数。
如果 k <= i, k--,l=i10 ,r=i10+9,返回步骤1. 即第k个数在[i,i+1]中,还需找k-1个数,接下来查找区间为[i10,i10+9]。
int findKthNumber(int n, int k) {
int l = 1;
int r = 9;
while(k) {
for(int i = l; i <= r; ++i)
{
int f = GetNodeNums(i, n);
if(k > f)
{
k -= f;
}
else
{
k--;
if(k == 0) return i;
l = i * 10 + 0;
r = i * 10 + 9;
break;
}
}
}
return 0;
}