December 13rd : 807. 保持城市天际线
December 14th : 630. 课程表 III
December 15th : 851. 喧闹和富有
December 16th : 1610. 可见点的最大数目
December 17th : 1518. 换酒问题
December 18th : 419. 甲板上的战舰
December 19th : 997. 找到小镇的法官
December 13rd : 807. 保持城市天际线
昨天把Porsche拼完啦~到时整个亚克力展示柜就更快乐啦!!!
![](https://i-blog.csdnimg.cn/blog_migrate/bd8392aa4bcad5774512411e0d8fdd01.jpeg)
今天的题,统计出各行各列的最大值,然后遍历整个城市格子,找出行列最大值中的较小值减去格子的值也即是该格子最大高度增量,累加返回结果即可。
class Solution {
public:
int maxIncreaseKeepingSkyline(vector<vector<int>>& grid) {
int n = grid.size();
vector<int> rows = vector<int>(n, 0), cols = vector<int>(n, 0);
for(int i = 0; i < n; ++i) {
for(int j = 0; j < n; ++j) {
rows[i] = max(grid[i][j], rows[i]);
cols[i] = max(grid[j][i], cols[i]);
}
}
int ret = 0;
for(int i = 0; i < n; ++i)
for(int j = 0; j < n; ++j)
ret += min(rows[i], cols[j]) - grid[i][j];
return ret;
}
};
December 14th : 630. 课程表 III
经典贪心算法题目,每次选取截止时间当前最小的课程(排序后遍历):如果选取该课程学习后的时间没超出截止时间,那么就学习这门课程;如果超出截止时间,则选出目前所要学习的课程当中的最长学习时间的课程(优先队列/大根堆存储获取),将其替换为当前所遍历到的课程,也即是学习同样门数的课程,所花费的总时间较短。
具体的推导过程见:方法一:优先队列 + 贪心。
class Solution {
public:
int scheduleCourse(vector<vector<int>>& courses) {
sort(courses.begin(), courses.end(),
[](vector<int>& a, vector<int>& b){return a[1] < b[1];});
priority_queue<int, vector<int>, less<int>> q;
int total = 0;
for(const auto& course : courses) {
int ti = course[0], di = course[1];
if(total + ti <= di) {
total += ti;
q.push(ti);
} else if(!q.empty() && q.top() > ti) {
total -= q.top() - ti;
q.pop();
q.push(ti);
}
}
return q.size();
}
};
December 15th : 851. 喧闹和富有
穷在闹市无人问,富在深山有远亲 。
不信但看宴中酒,杯杯先敬富贵人 。
门前拴着高头马,不是亲来也是亲。
门前放着讨饭棍,亲朋好友不上门 。
世上结交需黄金,黄金不多交不深 。
有钱有酒多兄弟,急难何曾见一人 。
三穷三富过到老,十年兴败多少人 。
谁人背后无人说,谁人背后不说人!
还是不忘初心,和光同尘才是!
然后这道题,将谁比谁富/穷有当作图的边,每个人作为图中的点,则是图的相关问题,图问题整理见:图的遍历(DFS,BFS,Topology Sort),恰巧这道题也用到了图的遍历和拓扑排序,知识链接如上。
首先因为逻辑自洽,所以可知是个DAG图(有向无环图,可以认为是多叉树),那么有两种做法,首先都是先建图:
- ① 对于建图时,较穷的指向较富的情况,则对以较穷的这个人为根节点的图分支进行遍历,找到比他富有的人当中的最安静的那个(图的遍历,DFS和BFS);
- ② 对于建图时,较富的指向较穷的情况,从最富的开始,对于最富的(入度为0)有 a n s [ 最 富 的 ] = 最 富 的 ans[最富的]=最富的 ans[最富的]=最富的,再将比他较穷的,和他有关系的(相邻节点)的 a n s ans ans 进行更新,之后抛开最富的后若最富的相邻点的入度是0,则再将他抛开,更新与他相邻的点的 a n s ans ans,一直到所有的人入度均为0,也即是DAG有拓扑排序,拓扑排序判断是否是有向无环图的逆应用。这个可以理解为反向BFS。
//dfs&bfs
class Solution {
public:
vector<int> loudAndRich(vector<vector<int>>& richer, vector<int>& quiet) {
int n = quiet.size();
vector<vector<int>> graph(n);
for(vector<int>& vec : richer) graph[vec[1]].push_back(vec[0]);
vector<int> ans(n, -1);
for(int x = 0; x < n; ++x) dfs(graph, quiet, ans, x);
//for(int x = 0; x < n; ++x) ans[x] = bfs(graph, quiet, x);
return ans;
}
private:
void dfs(const vector<vector<int>>& graph, const vector<int>& quiet,
vector<int>& ans, int x) {
if(ans[x] != -1) return;
ans[x] = x;
for(int y : graph[x]) {
dfs(graph, quiet, ans, y);
if(quiet[ans[x]] > quiet[ans[y]]) ans[x] = ans[y];
}
}
//超时
int bfs(const vector<vector<int>>& graph, const vector<int>& quiet, int x) {
queue<int> q; q.push(x);
while(!q.empty()) {
int y = q.front(); q.pop();
if(quiet[y] < quiet[x]) x = y;
for(int z : graph[y]) q.push(z);
}
return x;
}
};
/****************************************************************************************************/
//topology sort
class Solution {
public:
vector<int> loudAndRich(vector<vector<int>>& richer, vector<int>& quiet) {
int n = quiet.size();
vector<vector<int>> graph(n); //记录图的连边情况,是有向边图,DAG
vector<int> inDegree(n); //统计各个person的入度,即比他钱多的人有多少
for(auto& vec : richer) {
//将钱多的连向钱少的
graph[vec[0]].push_back(vec[1]);
++inDegree[vec[1]];
}
vector<int> ans(n);
//ans[x]的产生,要么是x自己,要么是不少于x的钱的人,初始为自身即可
for(int i = 0; i < n; ++i) ans[i] = i;
//iota(ans.begin(), ans.end(), 0);
queue<int> q;
for(int i = 0; i < n; ++i)
if(inDegree[i] == 0) q.push(i);
//从最有钱的开始找,最有钱的话最安静的人一定是他自己,然后一直,次有钱的,再次的...
while(!q.empty()) {
int x = q.front(); q.pop();
for(int y : graph[x]) {
//如果比x没钱的人y,其安静值大于x的安静值,则更新y的安静值即ans[y]=ans[x]
if(quiet[ans[x]] < quiet[ans[y]]) ans[y] = ans[x];
//更新后,将y的入度,即比x有钱的人数减一,若是没有了,则加入队列,进行减值
if(--inDegree[y] == 0) q.push(y);
}
}
return ans;
}
};
December 16th : 1610. 可见点的最大数目
这道题,是环形数组的范围滑动窗口问题(见)。主要的点在于如何将二维散点转换为极角,这里使用了
atan2
\texttt{atan2}
atan2 函数,也就是数学中的arctan
。将得到的极角坐标排序后,从各个极角开始,查找极角为开始的顺/逆时针angle范围内的点的个数,取里边的最大值即可。
需要注意的是:
- atan2 \texttt{atan2} atan2 的返回值范围为 [ − π , π ] [-\pi,\pi] [−π,π],它的覆盖范围为 2 π 2\pi 2π,而 极角+angle \textit{极角+angle} 极角+angle 会出现大于 π \pi π 的情况,则可以在原数组中将每个点 极 角 + 360 ° 极角 + 360\degree 极角+360°,防止反转的问题;
- 在求极角时,对于坐标刚好处于 location \textit{location} location 的元素需要单独进行统计,因为 arctan(0) \textit{arctan(0)} arctan(0) 未定义;
class Solution {
public:
int visiblePoints(vector<vector<int>>& points, int angle, vector<int>& location) {
int sameLoction = 0;
vector<double> degrees;
for(auto& point : points) {
if(point[0] == location[0] && point[1] == location[1]) {
sameLoction++;
continue;
}
double degree = atan2(location[1]-point[1], location[0]-point[0]);
//double degree = atan2(point[1] - location[1], point[0] - location[0]);
degrees.push_back(degree);
}
sort(degrees.begin(), degrees.end());
int n = degrees.size();
for(int i = 0; i < n; ++i) degrees.push_back(degrees[i] + 2 * M_PI);
int maxVPnt = 0, right = 0;
double scope = angle * M_PI / 180 ;
for(int i = 0; i < n; ++i) {
while(right < degrees.size() && degrees[right] <= degrees[i] + scope) right++;
maxVPnt = max(maxVPnt, right - i);
}
return sameLoction + maxVPnt;
}
};
December 17th : 1518. 换酒问题
迭代过程模拟即可,下一轮空瓶子的数量是再原先兑换后剩下的基础上,加上兑换后得到的酒的瓶数。
class Solution {
public:
int numWaterBottles(int numBottles, int numExchange) {
int numEmptyBottles = numBottles, numFullBottles = 0;
int ret = numBottles;
while(numEmptyBottles >= numExchange) {
numFullBottles = numEmptyBottles / numExchange;
numEmptyBottles = numEmptyBottles % numExchange + numFullBottles;
ret += numFullBottles;
}
return ret;
}
};
/*******************************************************************************************************/
class Solution {
public:
int numWaterBottles(int numBottles, int numExchange, int numEmptyBottles = 0) {
cout << "numBottles:" << numBottles << " numEmptyBottles:" << numEmptyBottles << endl;
//当空瓶和满瓶数量和小于兑换瓶数,则递归结束,返回满瓶数量
if(numEmptyBottles+numBottles < numExchange) return numBottles;
//如果大于等于兑换瓶数,则下一轮满瓶数为本轮空瓶+上轮剩余空瓶和地板除以兑换瓶数
//下一轮空瓶数为本轮空瓶+上轮剩余空瓶和对兑换瓶数求余数
return numBottles +
numWaterBottles((numBottles+numEmptyBottles)/numExchange,
numExchange,
(numBottles+numEmptyBottles)%numExchange);
}
};
December 18th : 419. 甲板上的战舰
和岛屿数量类似,使用dfs或bfs将岛屿填平即可,只是这里的岛屿是横向或者纵向的,所以岛屿数量的代码在这里是通用的,发现不是战舰的部分,dfs时返回即可。但是这里更改了board地图。
根据战舰是横向或是纵向的特点,则可以知道战舰一定会有一个左上角,而且两战舰间一定是有空格分隔,没有相邻的情况,依照这个题意,我们可以统计战舰的左上角个数,也即是战舰的个数了。
有点奇怪的是,第二种方法的时间空间复杂度和第一种实际上差不多,可能是数据不够强吧。
//dfs
class Solution {
public:
int countBattleships(vector<vector<char>>& board) {
//岛屿数量类似,空间复杂度On,时间复杂度On
int ships = 0;
int m = board.size(), n = board[0].size();
for(int i = 0; i < m; ++i) {
for(int j = 0; j < n; ++j) {
if(board[i][j] == 'X') {
ships++;
dfs(board, i, j);
}
}
}
return ships;
}
private:
vector<int> directions = {-1, 0, 1, 0, -1};
void dfs(vector<vector<char>>& board, int r, int c) {
if(r == board.size() || r == -1 || c == board[0].size() || c == -1 || board[r][c] == '.') return;
board[r][c] = '.';
for(int i = 0; i < 4; ++i) {
dfs(board, r+directions[i], c+directions[i+1]);
}
}
};
/****************************************************************************************************************/
//统计左上角
class Solution {
public:
int countBattleships(vector<vector<char>>& board) {
int m = board.size(), n = board[0].size();
int ships = 0;
if(board[0][0] == 'X') ships++;
for(int i = 1; i < n; ++i) if(board[0][i-1] != 'X' && board[0][i] == 'X') ships++;
for(int i = 1; i < m; ++i) if(board[i-1][0] != 'X' && board[i][0] == 'X') ships++;
for(int i = 1; i < m; ++i) {
for(int j = 1; j < n; ++j) {
if(board[i-1][j] != 'X' && board[i][j-1] != 'X' && board[i][j] == 'X')
ships++;
}
}
return ships;
}
};
December 19th : 997. 找到小镇的法官
简单的图论题目,考察的只是基本的图中节点的出度和入度的基本概念。对于法官而言,其在trust整个遍历完后的出度为0(不信任任何人),入度为n-1(所有的人都信任他,除了他自己),且题目中限定只有一个法官。所以开两个数组记录每个人的入度(被信任),出度(信任他人),然后遍历每个人的信任和被信任情况,若是符合上述的出度为0,入度为n-1,则该人就是小镇法官。
此外,还可以只开一个数组,入度++,出度–,这样,只有最后入度为n-1的那个人是小镇法官。
class Solution {
public:
int findJudge(int n, vector<vector<int>>& trust) {
vector<pair<int, int>> vec(n+1);
for(int i = 0; i < trust.size(); ++i) {
vec[trust[i][0]].second++;
vec[trust[i][1]].first++;
}
for(int i = 1; i <= n; ++i) {
if(vec[i].first == n-1 && vec[i].second == 0)
return i;
}
return -1;
}
};
/*********************************************************************/
//只开一个数组
class Solution {
public:
int findJudge(int n, vector<vector<int>>& trust) {
vector<int> inDegree(n+1);
for(auto& vec : trust) {
inDegree[vec[0]]--;
inDegree[vec[1]]++;
}
for(int i = 1; i <= n; ++i) {
if(inDegree[i] == n-1)
return i;
}
return -1;
}
};