2022-01-24每日刷题打卡
飞书——每日一题
232. 用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
说明:
你只能使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
进阶:
你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n) ,即使其中一个操作可能花费较长时间。
示例:
输入:
[“MyQueue”, “push”, “push”, “peek”, “pop”, “empty”]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
提示:
1 <= x <= 9
最多调用 100 次 push、pop、peek 和 empty
假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)
准备两个栈,一个入栈insta,一个出栈outsta,当有数插入时,就把数插入到insta中,当要将数出栈时,先判断outsta是否为空,如果为空,就把insta的所有元素都出栈并同属插入到ousta中,比如说:insta的元素顺序现在是1 2 3 4 5,此时5是栈顶元素,1是最先进入的元素,我们把这些元素按照顺序出栈并入到outsta后,顺序是:5 4 3 2 1,此时1就是栈顶元素,我们把1出栈即可。而且1出栈后,下一个栈顶元素就是2,正好对应队列的顺序,返回队列头元素时,也可以直接返回outsta的栈顶元素。但要注意,outsta为空时才要把insta出栈,不为空时outsta直接出栈即可。至于判断队列是否为空,可以直接计算两个栈的元素之和看是否为0,也可以一开始就准备一个全局变量ans=0,进行入栈操作时,ans++,出栈操作时,ans–。那么,只要ans不为0,就说明还有元素在队列里,队列不为空,反之为空。
class MyQueue {
public:
stack<int>insta,outsta;
int ans=0;
MyQueue() {
}
void push(int x) {
insta.push(x);
ans++;
}
int pop() {
if(outsta.size()==0)
{
while(insta.size())
{
outsta.push(insta.top());
insta.pop();
}
}
ans--;
int a=outsta.top();
outsta.pop();
return a;
}
int peek() {
if(outsta.size()==0)
{
while(insta.size())
{
outsta.push(insta.top());
insta.pop();
}
}
return outsta.top();
}
bool empty() {
return ans==0;
}
};
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue* obj = new MyQueue();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->peek();
* bool param_4 = obj->empty();
*/
279. 完全平方数
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
示例 1:
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4
原本写的是广度搜索的专题,但好像动态规划写这题更简洁一点。
准备一个大小为n+1的数组dp,状态定义为,dp[i]代表数字i的完全平方数最少数量为dp[i],比如说,dp[1],就代表数字1的完全平方数最少可以是dp[1],因为1就是完全平方数,所以知道了是1,所以数字1的完全平方数最少就是1。
那么该怎么算呢,我们可以由题意得出一个规律:如果一个数和另一个数的差值正好是一个完全平方数,那么这两个数的完全平方数最少个数相差为1,比如数字1和5,差值是4,是一个完全平方数,所有数字5所需要的最少完全平方数为1所需要的完全平方数+1,即dp[5]=d[1]+1=2;这就是我们的解法,遍历n个数,每遍历一个数,找在这个数范围内的完全平方数,比如5,范围内最小的完全平方数就是4,然后计算dp[5-4]+1,就得到结果2了,当然这也是一个遍历的过程,在寻找完全平方数的过程中维护下最小值。比如12,最大的完全平方数是9,即dp[12]=dp[12-9]+1=4,但测试例已经告诉我们了最少应该是3,所以这个不是最小值,然后我们找下一个完全平方数,4 ,即dp[12]=dp[12-4]+1=dp[8]+1,(8是两个4相加,即最少数为2),这时dp[12]=3,得到了最小值,所以我们在找完全平方数的过程中记得要维护最小值,最后再记录在dp[i]上。
class Solution {
public:
int numSquares(int n) {
vector<int>dp(n+1);
for(int i=1;i<=n;i++)
{
int min_num=INT_MAX;
for(int j=1;j*j<=i;j++)
{
min_num=min(min_num,dp[i-j*j]+1);
}
dp[i]=min_num;
}
return dp[n];
}
};
322. 零钱兑换
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
方法和上题差不多,区别就是上题的完全平方数是自己算出来的,这题的“完全平方数”是存在数组里给你的。
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<int>dp(amount+1,amount+1);
int n=coins.size();
dp[0]=0;
for(int i=1;i<=amount;i++)
{
for(int j=0;j<n;j++)
{
if(coins[j]-i<=0)
{
dp[i]=min(dp[i],dp[i-coins[j]]+1);
}
}
}
return dp[amount]>amount?-1:dp[amount];
}
};
130. 被围绕的区域
给你一个 m x n 的矩阵 board ,由若干字符 ‘X’ 和 ‘O’ ,找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。
示例 1:
输入:board = [[“X”,“X”,“X”,“X”],[“X”,“O”,“O”,“X”],[“X”,“X”,“O”,“X”],[“X”,“O”,“X”,“X”]]
输出:[[“X”,“X”,“X”,“X”],[“X”,“X”,“X”,“X”],[“X”,“X”,“X”,“X”],[“X”,“O”,“X”,“X”]]
解释:被围绕的区间不会存在于边界上,换句话说,任何边界上的 ‘O’ 都不会被填充为 ‘X’。 任何不在边界上,或不与边界上的 ‘O’ 相连的 ‘O’ 最终都会被填充为 ‘X’。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
先遍历周围最外围的四个边,看有没有‘O’,如果有,就广度搜索把它和它联通的所有’O’字符都改为字符‘A’,然后遍历所有的点,看哪个点是’O’,以那个点为起点把它和它相邻的所有‘O’字符都改为’X’。最后,把之前改为‘A’的字符都改回’O’。
class Solution {
public:
int n,m;
typedef pair<int,int>PII;
PII que[100000];
int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
void solve(vector<vector<char>>& board) {
n=board.size(),m=board[0].size();
for(int i=0;i<n;i++)
{
if(board[i][0]=='O')
{
bfs(i,0,'O','A',board);
}
if(board[i][m-1]=='O')
{
bfs(i,m-1,'O','A',board);
}
}
for (int i = 0; i < m; i++)
{
if (board[0][i] == 'O')
{
bfs(0, i, 'O', 'A', board);
}
if (board[n-1][i] == 'O')
{
bfs(n-1, i, 'O', 'A', board);
}
}
for(int i=1;i<n;i++)
for(int j=1;j<m;j++)
if(board[i][j]=='O')
{
bfs(i,j,'O','X',board);
}
for(int i=0;i<n;i++)
{
if(board[i][0]=='A')
{
bfs(i,0,'A','O',board);
}
if(board[i][m-1]=='A')
{
bfs(i,m-1,'A','O',board);
}
}
for (int i = 0; i < m; i++)
{
if (board[0][i] == 'A')
{
bfs(0, i, 'A', 'O', board);
}
if (board[n-1][i] == 'A')
{
bfs(n-1, i, 'A', 'O', board);
}
}
}
void bfs(int x,int y,char s,char c,vector<vector<char>>& board)
{
board[x][y]=c;
que[0]={x,y};
int hh=0,tt=0;
while(hh<=tt)
{
auto t=que[hh++];
for(int i=0;i<4;i++)
{
int a=t.first+dx[i],b=t.second+dy[i];
if(a>=0&&a<n&&b>=0&&b<m&&board[a][b]==s)
{
board[a][b]=c;
que[++tt]={a,b};
}
}
}
}
};
剑指 Offer 67. 把字符串转换成整数
写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
说明:
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。
示例 1:
输入: “42”
输出: 42
此题注意点就是:首位非空格非符号字符是不是数字字符,不是就返回0;符号字符后面的字符是不是数字字符,不是返回0;数转化后是否会超出int类型的范围,如果小于INT_MIN就返回-2147483648,大于INT_MAX就返回2147483647。
先判断一下字符串是否为空,空就返回0,然后预先遍历一下str,把前面的空格都跳过,然后判断第一个非空字符是不是符号,准备一个数ans=1,如果符号为符号,ans=-1,为正号不变,为字母返回0。然后把后面连着的的数字字符保存下来(注意,如果是“42 212”这样的字符,只要42,后面的212不要),然后准备一个long long 类型变量num=0,把字符串一步步转化成数字保存在num上,每次判断一下num是否超出区间,如果超出了就返回相应的最大值。等转换结束后,返回ans*num。
class Solution {
public:
int strToInt(string str) {
int n=str.size();
if(n==0)return 0;
string s;
int i=0,ans=1;
while(i<n&&str[i]==' ')
i++;
if(i>=n)return 0;
else if(str[i]=='+')
{
ans=1;
i++;
}
else if(str[i]=='-')
{
ans=-1;
i++;
}
else if((str[i]>='a'&&str[i]<='z')||(str[i]>='A'&&str[i]<='Z'))return 0;
while(i<n)
{
if(str[i]>='0'&&str[i]<='9')s+=str[i];
else break;
i++;
}
long long num=0;
for(int j=0;j<s.size();j++)
{
num*=10;
num+=s[j]-'0';
if(num>2147483647&&ans==1)return 2147483647;
else if(num>2147483648&&ans==-1)return -2147483648;
}
return ans*num;
}
};
剑指 Offer 14- I. 剪绳子和343. 整数拆分
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
由于两题一样就放一起讲了。
数论的解法,证明也不知道,反正就是尽量把数分成2和3,特别是3,在只有2和3的情况下3越多这个数越大,比如数字6可以分成2+2+2或者3+3,3*3>2 *2 *2。但注意是只有2和3的情况,如果是4你分成3+1那反而比2+2小。还有就是2和3可以组成任何大于1的数,因为2是最小的偶数,3是除1最小的奇数了。
class Solution {
public:
int integerBreak(int n) {
while(n<4)
return n-1;
int ans=1;
while(n>4)
{
ans*=3;
n-=3;
}
return n*ans;
}
};
剑指 Offer 14- II. 剪绳子 II
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m - 1] 。请问 k[0]k[1]…*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
铜上题,只不过要取模,对应的,数要开到long long 。
class Solution {
public:
int cuttingRope(int n) {
while(n<4)
return n-1;
long long ans=1;
while(n>4)
{
ans*=3;
ans%=1000000007;
n-=3;
}
return ans*n%1000000007;
}
};
404. 左叶子之和
计算给定二叉树的所有左叶子之和。
示例:
3
/ \
9 20
/ \
15 7
在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24
注意,他说的是左叶子不是左节点。
深搜,准备一个全局变量ans=0,每次判断当前节点是否为空,为空就直接return结束程序。再判断当前节点是否有左节点,而且它的左节点是否是叶子节点(无左右子节点)如果是,就把值左节点的值加到ans上。再把当前节点的左右节点送去下一次深搜。最后返回ans即可。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int ans=0;
int sumOfLeftLeaves(TreeNode* root) {
dfs(root);
return ans;
}
void dfs(TreeNode *root)
{
if(!root)return;
if(root->left&&!root->left->left&&!root->left->right)ans+=root->left->val;
dfs(root->left);
dfs(root->right);
}
};
463. 岛屿的周长
给定一个 row x col 的二维网格地图 grid ,其中:grid[i][j] = 1 表示陆地, grid[i][j] = 0 表示水域。
网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。
岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。
示例 1:
输入:grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]
输出:16
解释:它的周长是上面图片中的 16 个黄色的边
示例 2:
输入:grid = [[1]]
输出:4
广度搜索,在走迷宫的时候多加一个判断语句。先准备一个计数器ans=0,用来计算一共有几个墙壁(图中黄色线),遍历矩阵,找到值为1的点,以那个点为起点进行广度搜索。在搜索中,把和起点联通的点都由1改为0,然后在我们走的时候,如果遇到墙壁,ans++。那么什么是墙壁,即数组越界、要走的下一个地方数值为0这两个条件。但有一点,我们要记录走过的地方,因为走过的地方会被改成0,我们在一个地块往上下左右四个方向探索时,有可能走到刚刚走过的地块,此时那个地块也变成了0,这样我们计算墙壁数量时会错误的+1,所以我们把走过的地块都标记,当我们的下一步超出矩阵大小、或者是下一步的地块为0且我们没走过时,计数器ans++。最后返回ans即可。
class Solution {
public:
typedef pair<int,int>PII;
PII que[20000];
int ans=0,n,m;
int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
int islandPerimeter(vector<vector<int>>& grid) {
n=grid.size();
m=grid[0].size();
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(grid[i][j]==1)
{
vector<vector<int>>v(n,vector<int>(m,0));
bfs(i,j,grid,v);
}
return ans;
}
void bfs(int x,int y,vector<vector<int>>& grid,vector<vector<int>>& v)
{
grid[x][y]=0;
v[x][y]=1;
que[0]={x,y};
int hh=0,tt=0;
while(hh<=tt)
{
auto t=que[hh++];
for(int i=0;i<4;i++)
{
int a=t.first+dx[i],b=t.second+dy[i];
if(a>=0&&a<n&&b>=0&&b<m&&grid[a][b]==1)
{
que[++tt]={a,b};
grid[a][b]=0;
v[a][b]=1;
}
else if ((a >= n || a < 0) || (b >= m || b < 0) || (grid[a][b] == 0 && v[a][b] == 0))
ans++;
}
}
}
};