剑指Offer 09-12题解
目前我做过的剑指Offer…
剑指Offer 09. 用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
来源:力扣(LeetCode)
思路:
一个栈用作插入 (即队列入队)appendTail() 一个栈用于删除(即队列出队)deleteHead()
代码:
/**
* Your CQueue object will be instantiated and called as such:
* CQueue* obj = new CQueue();
* obj->appendTail(value);
* int param_2 = obj->deleteHead();
*/
class CQueue{
public:
stack<int> stack1,stack2; // 需用到的两个栈(1进 2出)
CQueue(){// 创建时清空两个栈
while(!stack1.empty())
stack1.pop();
while(!stack2.empty())
stack2.pop();
}
void appendTail(int value)
{
stack1.push(value); //入栈
}
int deleteHead()
{
if(stack2.empty())
while(!stack1.empty()) // 转移必须转空
{
stack2.push(stack1.top());
stack1.pop();
}
if(stack2.empty()) // 两个栈实际是联通的都空时才为队列空
return -1;
else
{
int deleteVal=stack2.top();
stack2.pop();
return deleteVal;
}
}
};
剑指Offer 10-l. 斐波那契数列
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
来源:力扣(LeetCode)
分析:
若是n值很小,那完全可以求出第n项的值再取模
但当n值较大时,可能会出现计算结果越界的情况
数列的第n项是由前两项之和,而前两项的和取模的值并不会影响最终结果取模
因此我们计算出每一项的取模的值 即可
动态规划思想 第三项只与前两项有关
代码:时间(n) 空间(1)
class Solution {
public:
int fib(int n) {
int f[2]={0,1}; // f[2]存放f[n-1]和f[n-2]的值
if(n<2) // 当n<2的情况
return f[n];
int fn;
while(--n) // 循环计算第n项
{
fn=(f[0]+f[1])%1000000007; // 结果取模
f[0]=f[1]; // 更新前两项值
f[1]=fn;
}
return fn;
}
};
递归也行,但没必…算了写一遍
class Solution {
public:
int fib(int n) {
if(n<2)
return n;
return digui(0,1,--n);
}
int digui(int a,int b,int n )
{
if(n<1) //弹出条件
return b;
return digui(b,(a+b)%1000000007,--n);
}
};
剑指Offer 10-ll. 青蛙跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
来源:力扣(LeetCode)
分析:
其实就是斐波那契的应用题
短腿青蛙每次能跳一级台阶或两级台阶,跳到第n级台阶有两种可能
一种从第n-1级台阶跳一级跳上来,或是从第n-2级台阶蹦两级蹦上来
所以跳到第n级台阶的跳法就是跳到前两级台阶的跳法之和
代码: 时间(n) 空间(1)
//和斐波那契数列一样
😄 😄 😄
剑指Offer 11. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
来源:力扣(LeetCode)
分析:
原先数组时有序且递增的
进行旋转后 数组 可以说是 两个有序数组拼接而成的且第一个有序数列的最小值大于等于第二有序数列最小值
只需要找到那个拐点即可,若没有在输出第一元素
方法一: 暴力
代码: 时间(n) 空间(1)
class Solution {
public:
int minArray(vector<int>& numbers) {
for(int i=0;i<numbers.size()-1;++i) //暴力遍历
{
if(numbers[i]>numbers[i+1])
return numbers[i+1];
}
return numbers[0];
}
};
方法二: 二分法
思路:
取数组中值,判断拐点在前半个区间还是在后半个区间
重复上述操作,缩小不能再缩小 后即是拐点
有一点不太好的 就是会有重复元素(这会产生一种特殊情况)
代码: 时间(log n) 空间(1)
class Solution {
public:
int minArray(vector<int>& numbers) {
int head=0,end=numbers.size()-1; // 数组头尾指针
while(head<end) // 元素个数大于一循环
{
int m=(head+end)/2; //取中值位置
if(numbers[m]>numbers[end]) // 判断拐点是否不在前区间
head=m+1;
else if(numbers[m]<numbers[end]) // 判断是否不在后区间
end=m;
else // 此情况是不在前区间 后区间首尾相等
--end; // 将后区间 尾指针前移
}
return numbers[end];
}
};
剑指Offer 12. 矩阵中的路径
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[["a","**b**","c","e"], ["s","**f**","**c**","s"], ["a","d","**e**","e"]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
来源:力扣(LeetCode)
分析
主要意思就是在 矩阵中找到对应的字符串
可从矩阵的任意接口开始
下一个接口位未进入过的相邻接口
方法:DFS + 剪枝 + 回溯
遍历每一个入口 开始DFS
DFS中 剪枝条件 = 越界 + 与字符不匹配
DFS函数返回时 还原函数内对其的操作 :( 标记 已进入)
代码: 时间(3^k nm) 空间(k)
class Solution {
public:
bool exist(vector<vector<char>>& board, string word) {
int bi=board.size(),bj=board[0].size(); // 计算边界值
for(int i=0;i<bi;++i) // 遍历每一个入口
{
for(int j=0;j<bj;++j)
{
if(dfs(board,word,i,j,0)) // DFS结果为真输出true
return true;
}
}
return false;
}
bool dfs(vector<vector<char>> &board,const string &word,int i,int j,int k)
{// i , j 定位board k 定位 word
if(i<0||i>=board.size()
||j<0||j>=board[0].size()
||board[i][j]!=word[k])
// 判断是否越界 和是否匹配
return false;
if(k==word.size()-1) // 判断是否到底
return true;
board[i][j]='\0'; // 未到底 继续DFS前 设置已进入标记
bool result=(dfs(board,word,i+1,j,k+1)
||dfs(board,word,i-1,j,k+1)
||dfs(board,word,i,j-1,k+1)
||dfs(board,word,i,j+1,k+1));
// 函数返回前 还原标记原内容
board[i][j]=word[k];
return result;
}
};