目录
源代码:
面试题03. 数组中重复的数字
//面试题03. 数组中重复的数字
//在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。
//数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。
//请找出数组中任意一个重复的数字。
//示例 1:
//输入:
//[2, 3, 1, 0, 2, 5, 3]
//输出:2 或 3
//限制:
//2 <= n <= 100000
//解题思路:
//使用set记录数字,查到有重复的则返回
#include <iostream>
#include <vector>
#include <unordered_set> //无序的,比set效率更高
using namespace std;
int findRepeatNumber(vector<int>& nums) {
if(nums.empty()){
cout<<"nums is empty"<<endl;
return 0;
}
unordered_set<int> tmp;//set只有键,map有键和值两个。键是唯一的 默认为升序 unordered_set表示不排序
for(int num : nums){
if(tmp.find(num)!=tmp.end()){
return num;
}
tmp.insert(num);
}
cout<<"无重复数字"<<endl;
return 0;
}
int main() {
//测试用例1:正常用例
vector<int> nums={2,3,1,0,2,5,3};
//测试用例2:空数组
//vector<int> nums;
//测试用例3:数组中没有重复的数字
//vector<int> nums={2,8,1,0,7,5,3};
cout<<findRepeatNumber(nums);
return 0;
}
面试题04. 二维数组中的查找
// Created by zzh on 2020/4/26.
//面试题04. 二维数组中的查找
//在一个 n * m 的二维数组中,
//每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。
//请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
//示例:
//现有矩阵 matrix 如下:
//[
//[1, 4, 7, 11, 15],
//[2, 5, 8, 12, 19],
//[3, 6, 9, 16, 22],
//[10, 13, 14, 17, 24],
//[18, 21, 23, 26, 30]
//]
//给定 target = 5,返回 true。
//给定 target = 20,返回 false。
//限制:
//0 <= n <= 1000
//0 <= m <= 1000
//解题思路:
//从左下角或者右上角开始查找,会方便很多。
// 比如从左下角。比目标值小则右移,比目标值大则上移
#include <iostream>
#include <vector>
using namespace std;
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
if(matrix.empty())
return false;
int m=matrix.size();
int n=matrix[0].size();
int row=m-1;//行
int col=0;//列
while(row>=0&&col<n){
if(matrix[row][col]==target)
return true;
else if(matrix[row][col]>target)
row--;
else
col++;
}
return false;
}
int main() {
//测试用例1:正常用例
vector<vector<int>> matrix = {{1,4,7,11,15},
{2,5,8,12,19},
{3,6,9,16,22},
{10,13,14,17,24},
{18,21,23,26,30}};
int target = 5;
//int target = 20;
//测试用例2:空数组
//vector<vector<int>> matrix;
cout<<findNumberIn2DArray(matrix, target);
return 0;
}
面试题05. 字符串替换空格
// Created by zzh on 2020/4/26.
//面试题05. 替换空格
//请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
//示例 1:
//输入:s = "We are happy."
//输出:"We%20are%20happy."
//限制:
//0 <= s 的长度 <= 10000
//思路:
// 1. 如果允许重新生成一个字符串,问题则非常简单。
// 2. 如果是在原字符串上做修改,为了降低复杂度,先计算替换后的总长度,然后从后往前进行挪动修改会更好。
#include <iostream>
#include <string>
using namespace std;
/*
//先按思路1
string replaceSpace(const string& s) {
if(s.empty()){
cout<<"s is empty"<<endl;
return s;
}
string res;
for(char c:s){
if(c==' ')
res+="%20";
else
res+=c;
}
return res;
}
*/
//按思路2
void replaceSpace(string& s) {
if(s.empty()){
cout<<"s is empty"<<endl;
return;
}
int cnt=0;
for(char c:s){
if(c==' ')
cnt++;
}
int i=s.size()-1,j=i+2*cnt;
while(i>=0 && i<j){
if(s[i]==' '){
s[j--]='0';
s[j--]='2';
s[j--]='%';
i--;
}
else
s[j--]=s[i--];
}
}
int main() {
//测试用例1:正常用例
string s = "We are happy.";
//s.resize(100);//保证后边有足够的空间
//测试用例2:空字符串
//string s="";
replaceSpace(s);
cout << s;
return 0;
}
面试题06. 从尾到头打印链表
// Created by zzh on 2020/4/26.
//面试题06. 从尾到头打印链表
//输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
//
//示例 1:
//输入:head = [1,3,2]
//输出:[2,3,1]
//
//限制:
//0 <= 链表长度 <= 10000
#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>
using namespace std;
struct ListNode{
int val;
ListNode* next;
explicit ListNode(int x):val(x),next(nullptr){}
};
/*
//方法一 使用reverse函数
vector<int> reversePrint(ListNode* head) {
if(head == nullptr){
cout<<"head is nullptr"<<endl;
return {0};
}
ListNode* p=head;
vector<int> res;
while(p!=nullptr){
res.push_back(p->val);
p=p->next;
}
reverse(res.begin(),res.end());
return res;
}
*/
//方法二 使用栈的先进后出特性
vector<int> reversePrint(ListNode* head) {
if(head == nullptr){
cout<<"head is nullptr"<<endl;
return {0};
}
ListNode* p=head;
stack<int> s;
vector<int> res;
while(p!=nullptr){
s.push(p->val);
p=p->next;
}
while(!s.empty()){
res.push_back(s.top());
s.pop();
}
return res;
}
int main() {
//测试用例1:正常用例
auto head = new ListNode(1);
head->next = new ListNode(2);
head->next->next = new ListNode(3);
//测试用例2:nullptr
//ListNode* head = nullptr;
vector<int> res;
res = reversePrint(head);
for(int i:res)
cout << i;
return 0;
}
面试题07. 重建二叉树
代码中用到的头文件见文末“BinaryTree.h”
// Created by zzh on 2020/4/26.
//面试题07. 重建二叉树
//输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
//例如,给出
//前序遍历 preorder = [3,9,20,15,7]
//中序遍历 inorder = [9,3,15,20,7]
//返回如下的二叉树:
// 3
// / \
// 9 20
// / \
// 15 7
//限制:
//0 <= 节点个数 <= 5000
//题目分析:
//前序遍历:根左右 中序遍历 左中右
//前序遍历的首个元素即为根节点 root 的值;
//在中序遍历中搜索根节点 root 的索引 ,可将中序遍历划分为 [ 左子树 | 根节点 | 右子树 ] 。
//根据中序遍历中的左(右)子树的节点数量,可将前序遍历划分为 [ 根节点 | 左子树 | 右子树 ] 。
//自此可确定 三个节点的关系 :1.树的根节点、2.左子树根节点、3.右子树根节点(即前序遍历中左(右)子树的首个元素)。
//子树特点: 子树的前序和中序遍历仍符合以上特点
//根据子树特点,我们可以通过同样的方法对左(右)子树进行划分,每轮可确认三个节点的关系 。此递推性质让我们联想到用 递归方法 处理
#include <vector>
#include "BinaryTree.h"
using namespace std;
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if(preorder.empty()||inorder.empty())
return nullptr;
auto head = new TreeNode(preorder[0]);
int root=0;
for(int i=0;i<inorder.size();i++){
if(inorder[i]==preorder[0]){
root=i;
break;
}
}
vector<int> left_pre(preorder.begin()+1,preorder.begin()+root+1);//左闭右开
vector<int> right_pre(preorder.begin()+root+1,preorder.end());
vector<int> left_in(inorder.begin(),inorder.begin()+root);
vector<int> right_in(inorder.begin()+root+1,inorder.end());
head->left=buildTree(left_pre,left_in);
head->right=buildTree(right_pre,right_in);
return head;
}
int main() {
//测试用例1:正常用例
vector<int> preorder = {3,9,20,15,7};
vector<int> inorder = {9,3,15,20,7};
//测试用例2:nullptr
// vector<int> preorder;
// vector<int> inorder = {9,3,15,20,7};
auto res = buildTree(preorder, inorder);
PrintTree(res);
return 0;
}
面试题09. 用两个栈实现队列
// Created by zzh on 2020/4/26.
//面试题09. 用两个栈实现队列
//用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
//示例 1:
//输入:
//["CQueue","appendTail","deleteHead","deleteHead"]
//[[],[3],[],[]]
//输出:[null,null,3,-1]
//示例 2:
//输入:
//["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
//[[],[],[5],[2],[],[]]
//输出:[null,-1,null,null,5,2]
//提示:
//1 <= values <= 10000
//最多会对 appendTail、deleteHead 进行 10000 次调用
#include <iostream>
#include <stack>
using namespace std;
class CQueue {
public:
stack<int> stack1;
stack<int> stack2;
CQueue() {
}
void appendTail(int value) {
stack1.push(value);
}
int deleteHead() {
if(stack1.empty())
return -1;
while(!stack1.empty()){
int tmp=stack1.top();
stack1.pop();
stack2.push(tmp);
}
int res=stack2.top();
stack2.pop();
while(!stack2.empty()){
int tmp=stack2.top();
stack2.pop();
stack1.push(tmp);
}
return res;
}
};
int main() {
//测试用例1:正常用例
auto* obj = new CQueue();
obj->appendTail(7);
int param_2 = obj->deleteHead();
//测试用例2:nullptr
cout << param_2;
return 0;
}
面试题10- I. 斐波那契数列
// Created by zzh on 2020/4/26.
//面试题10- I. 斐波那契数列
//写一个函数,输入 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。
#include <iostream>
using namespace std;
int fib(int n) {
if (n == 0)
return 0;
else if (n == 1)
return 1;
else {
int a = 0;
int b = 1;
int sum = 0;
while (n > 1) { //动态规划
sum = (a + b) % 1000000007;
a = b;
b = sum;
n--;
}
return b;
}
}
int main(){
cout << fib(10);
return 0;
}
面试题10- II. 青蛙跳台阶问题
// Created by zzh on 2020/4/26.
//面试题10- II. 青蛙跳台阶问题
//一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
//答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
//示例 1:
//输入:n = 2
//输出:2
//示例 2:
//输入:n = 7
//输出:21
//提示:
//0 <= n <= 100
#include <iostream>
using namespace std;
int numWays(int n) {
if(n==0||n==1)
return 1;
else if(n==2)
return 2;
else{
int a=1;
int b=2;
int c=0;
while(n>2){
c=(a+b)%1000000007;
a=b;
b=c;
n--;
}
return c;
}
}
int main(){
cout << numWays(9);
return 0;
}
面试题11. 旋转数组的最小数字
// Created by zzh on 2020/4/26.
//面试题11. 旋转数组的最小数字
//把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。
//例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
//示例 1:
//输入:[3,4,5,1,2]
//输出:1
//示例 2:
//输入:[2,2,2,0,1]
//输出:0
#include <iostream>
#include <vector>
using namespace std;
int minArray(vector<int>& numbers) {
if(numbers.empty())
return 0;
if(numbers.size()==1)
return numbers[0];
int left=0;
int right=numbers.size()-1;
//mid要与right作比较,因为可能有整个数组都旋转的情况,right肯定在右边序列。而left不确定是左边序列还是右边
while(left<right){
int mid=(left+right)/2;
if(numbers[mid]>numbers[right])
left=mid+1;
else if(numbers[mid]<numbers[right])
right=mid;
else //相等的时候,比较特殊,无法判断最小值在左在右 如[1,0,1,1,1],[1,1,1,0,1]
right--;//缩小范围,并且不会删掉最小值 left++则有可能删掉最小值
}
return numbers[left];
}
int main(){
vector<int> numbers = {3,4,5,1,2};
cout << minArray(numbers);
}
面试题12. 矩阵中的路径
// Created by zzh on 2020/4/26.
//面试题12. 矩阵中的路径
//请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。
//路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。
//如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。
//例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
//[['a','b','c','e'],
//['s','f','c','s'],
//['a','d','e','e']]
//但矩阵中不包含字符串“abfb”的路径,
//因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
//示例 1:
//输入:board = [['A','B','C','E'],['S','F','C','S'],['A','D','E','E']], word = 'ABCCED'
//输出:true
//示例 2:
//输入:board = [['a','b'],['c','d']], word = "abcd"
//输出:false
//提示:
//1 <= board.length <= 200
//1 <= board[i].length <= 200
#include <iostream>
#include <string>
#include <vector>
using namespace std;
bool def(vector<vector<char>>& board, string word,int i,int j,int k){
if(i<0||j<0||i>=board.size()||j>=board[0].size()||board[i][j]!=word[k])
return false;
if(k==word.size()-1)
return true;
char tmp=board[i][j];
board[i][j]='/';
bool res=def(board, word,i+1,j,k+1)||def(board, word,i-1,j,k+1)||def(board, word,i,j+1,k+1)||def(board, word,i,j-1,k+1);
board[i][j]=tmp;
return res;
}
bool exist(vector<vector<char>>& board, const string& word) {
for(int i=0;i<board.size();i++)
for(int j=0;j<board[0].size();j++){
if(def(board, word,i,j,0))
return true;
}
return false;
}
int main() {
vector<vector<char>> board = {{'A','B','C','E'},{'S','F','C','S'},{'A','D','E','E'}};
string word = "ABCCED";
cout << exist(board, word);
return 0;
}
面试题13. 机器人的运动范围
//
// Created by zzh on 2020/4/26.
//面试题13. 机器人的运动范围
//地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。
//一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),
//也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。
// 但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
//示例 1:
//输入:m = 2, n = 3, k = 1
//输出:3
//示例 2:
//输入:m = 3, n = 1, k = 0
//输出:1
//提示:
//1 <= n,m <= 100
//0 <= k <= 20
#include <iostream>
#include <vector>
using namespace std;
int get(int i){//得到各位的和
int res=0;//局部变量要给初值,不然不确定
for(;i;i/=10){
res += i%10;
}
return res;
}
int movingCount(int m, int n, int k) {
int res=0;
vector<vector<int>> vis(m,vector<int>(n,0));//m*n维,初值为0
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(get(i)+get(j)>k)
continue;
if(i==0&&j==0)
vis[i][j]=1;
if(j==0&&i>0)//这两个分开,而不直接用左和上的或,是为了考虑边上的行和列,防止越界
vis[i][j] = vis[i-1][j];//和上边的求或
if(i==0&&j>0)
vis[i][j] = vis[i][j-1];//和左边的求或
if(i>0&&j>0)
vis[i][j]=vis[i-1][j]|vis[i][j-1];
res+=vis[i][j];
}
}
return res;
}
int main() {
int m = 2, n = 3, k = 1;
cout << movingCount(m, n, k);
return 0;
}
面试题14- I. 剪绳子
//
// Created by zzh on 2020/4/26.
//面试题14- I. 剪绳子
//给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),
//每段绳子的长度记为 k[0],k[1]...k[m] 。请问 k[0]*k[1]*...*k[m] 可能的最大乘积是多少?
//例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
//示例 1:
//输入: 2
//输出: 1
//解释: 2 = 1 + 1, 1 × 1 = 1
//示例 2:
//输入: 10
//输出: 36
//解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
//提示:
//2 <= n <= 58
#include <iostream>
#include <algorithm>
using namespace std;
int cuttingRope(int n) {
if (n<=3)
return n-1;
int a=n/3;
int b=n-3*a;
if(b==0)
return pow(3,a);
else if(b==1)
return pow(3,a-1)*4;
else
return pow(3,a)*b;
}
int main() {
int n = 8;
cout << cuttingRope(n);
return 0;
}
面试题14- II. 剪绳子 II
//
// Created by zzh on 2020/4/26.
//面试题14- II. 剪绳子 II
//给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1)
// 每段绳子的长度记为 k[0],k[1]...k[m] 。请问 k[0]*k[1]*...*k[m] 可能的最大乘积是多少?
// 例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
//答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
#include <iostream>
using namespace std;
int cuttingRope(int n) {
if(n<=3)
return n-1;
long res=1;
while(n>4){
res = (res*3)%1000000007;
n -=3;
}
res = (res*n)%1000000007;
return res;
}
int main() {
int n = 8;
cout << cuttingRope(n);
return 0;
return 0;
}
面试题15. 二进制中1的个数
//
// Created by zzh on 2020/4/26.
//面试题15. 二进制中1的个数
//请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。
//例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
#include <iostream>
using namespace std;
/*
//无符号int型,最简单
int hammingWeight(uint32_t n) {
int res=0;
while(n){
if(n&1)
res++;
n >>= 1;
}
return res;
}
//有符号型,可能为负数,最高位为1,右移之后最高位仍补1,会死循环,因此不能使用右移
//负数以补码形式存储,原码->符号位不变,其他位取反->反码 +1 ->补码
int hammingWeight(int n) {
int res=0;
unsigned int flag=1;
while(flag){
if(n&flag)
res++;
flag <<= 1;
}
return res;
}
*/
//利用特性,n=n&(n-1),会去掉n从右边开始的第一个1,时间复杂度更低,负数也适用
int hammingWeight(int n) {
int res=0;
while(n){
n=n&(n-1);
res++;
}
return res;
}
int main() {
int n=7;
cout << hammingWeight(n);
return 0;
}
面试题16. 数值的整数次方
//
// Created by zzh on 2020/4/26.
//面试题16. 数值的整数次方
//实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
//示例 1:
//输入: 2.00000, 10
//输出: 1024.00000
//示例 2:
//输入: 2.10000, 3
//输出: 9.26100
//示例 3:
//输入: 2.00000, -2
//输出: 0.25000
//解释: 2-2 = 1/22 = 1/4 = 0.25
//说明:
//-100.0 < x < 100.0
//n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。
#include <iostream>
using namespace std;
//暴力循环求解复杂度较高,改进用二分法x^n=(x^2)^(n/2)
double myPow(double x, int n) {
long a=n;
if(x==0)
return x;
if(n==0)
return 1;
if(n<0){
a=-a;
x=1/x;
}
double res=x;
double tmp=1;
while(a>=2){
if(a&1)
tmp *= res;//存储那些奇数项
res *= res;
a >>= 1; //等价于a/2
}
res *= tmp;
return res;
}
int main() {
double x=7.1;
int n=2;
cout << myPow(x, n);
return 0;
}
面试题17. 打印从1到最大的n位数
//
// Created by zzh on 2020/4/26.
//面试题17. 打印从1到最大的n位数
//输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
//示例 1:
//输入: n = 1
//输出: [1,2,3,4,5,6,7,8,9]
//说明:
//用返回一个整数列表来代替打印 n 为正整数
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> printNumbers(int n) {
vector<int> res;
int i=1;
int tmp=pow(10,n)-1;
while(tmp--){
res.push_back(i++);
}
return res;
}
int main() {
int n = 2;
vector<int> res = printNumbers(n);
for(int i:res)
cout << i;
return 0;
}
面试题18. 删除链表的节点
//
// Created by zzh on 2020/4/26.
//面试题18. 删除链表的节点
//给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
//返回删除后的链表的头节点。
//注意:此题对比原题有改动
//示例 1:
//输入: head = [4,5,1,9], val = 5
//输出: [4,1,9]
//解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
//示例 2:
//输入: head = [4,5,1,9], val = 1
//输出: [4,5,9]
//解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
//说明:
//题目保证链表中节点的值互不相同,若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点
#include <iostream>
using namespace std;
struct ListNode {
int val;
ListNode *next;
explicit ListNode(int x) : val(x), next(NULL) {}
};
ListNode* deleteNode(ListNode* head, int val) {
ListNode* tmp=head;
if(tmp->val==val)
return head->next;
while(tmp->next->val!=val){
tmp=tmp->next;
}
tmp->next=tmp->next->next;
return head;
}
int main() {
auto head = new ListNode(4);
head->next = new ListNode(1);
head->next->next = new ListNode(5);
head->next->next->next = new ListNode(9);
head = deleteNode(head, 5);
auto tmp=head;
while(tmp){
cout<<tmp->val<<" ";
tmp = tmp->next;
}
return 0;
}
面试题19. 正则表达式匹配
//
// Created by zzh on 2020/4/26.
//面试题19. 正则表达式匹配
//请实现一个函数用来匹配包含'. '和'*'的正则表达式。
//模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。
//在本题中,匹配是指字符串的所有字符匹配整个模式。
//例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但与"aa.a"和"ab*a"均不匹配。
//示例 1:
//输入:
//s = "aa"
//p = "a"
//输出: false
//解释: "a" 无法匹配 "aa" 整个字符串。
//示例 2:
//输入:
//s = "aa"
//p = "a*"
//输出: true
//解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
//示例 3:
//输入:
//s = "ab"
//p = ".*"
//输出: true
//解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
//示例 4:
//输入:
//s = "aab"
//p = "c*a*b"
//输出: true
//解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
//示例 5:
//输入:
//s = "mississippi"
//p = "mis*is*p*."
//输出: false
//s 可能为空,且只包含从 a-z 的小写字母。
//p 可能为空,且只包含从 a-z 的小写字母以及字符 . 和 *,无连续的 '*'。
#include <iostream>
#include <string>
using namespace std;
bool isMatch(string s, string p) {
if(p.empty()) return s.empty();
if(p[1] == '*'){
return isMatch(s, p.substr(2)) || (!s.empty() && (s[0] == p[0] || p[0] == '.')) && isMatch(s.substr(1), p);
}
else{
return !s.empty() && (s[0] == p[0] || p[0] == '.') && (isMatch(s.substr(1), p.substr(1)));
}
}
int main() {
string s = "mississippi";
string p = "mis*is*p*.";
cout << isMatch(s, p);
return 0;
}
面试题20. 表示数值的字符串
// Created by zzh on 2020/4/26.
//面试题20. 表示数值的字符串
//请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。
//例如,字符串"+100"、"5e2"、"-123"、"3.1416"、"0123"都表示数值,
//但"12e"、"1a3.14"、"1.2.3"、"+-5"、"-1E-16"及"12e+5.4"都不是。
//解题思路
//1)先去除字符串首尾的空格
//2)然后根据e划分指数和底数
//3)判断指数和底数是否合法即可
#include <iostream>
using namespace std;
bool judgeBase(string s){//判断底数是否正常
if (s.empty())
return false;
int cnt_point = 0;
bool res = false;
for(int i=0;i<s.size();i++){
if(s[i]=='+'||s[i]=='-'){//符号位如果不在第一位,则false
if(i!=0) return false;
else continue;
}
else if(s[i]=='.'){//小数点多于一位,false
cnt_point++;
if(cnt_point>1||cnt_point==s.size()) return false;
}
else if(s[i]<'0'||s[i]>'9'){//出现非数字,false
return false;
}
else//至少有一个正常数字,才变成true
res = true;
}
return res;//全部正常
}
bool judgeExponent(string s){//判断指数是否正常
if (s.empty())
return false;
bool res = false;
for(int i=0;i<s.size();i++){
if(s[i]=='+'||s[i]=='-'){//符号位如果不在第一位,则false
if(i!=0) return false;
else continue;
}
else if(s[i]<'0'||s[i]>'9'){//出现非数字,false
return false;
}
else//至少有一个正常数字,才变成true
res = true;
}
return res;
}
bool isNumber(string s) {
int i = s.find_first_not_of(' ');//找到第一个不是空格的位置
if(i == string::npos) return false;//如果找不到
int j = s.find_last_not_of(' ');//找到最后一个不是空格的位置
s = s.substr(i,j-i+1);//从i开始,截取j-i+1个元素
int pos = s.find('e');//找e的位置
if(pos == string::npos) //如果没有e
return judgeBase(s);
else
return judgeBase(s.substr(0, pos)) && judgeExponent(s.substr(pos+1));
}
int main() {
// string s = ".";
// string s = "-4e+";
// string s = "e1";
// string s = "";
string s = "5e2";
cout << isNumber(s);
return 0;
}
面试题21. 调整数组顺序使奇数位于偶数前面
// Created by zzh on 2020/4/27.
//面试题21. 调整数组顺序使奇数位于偶数前面
//输入一个整数数组,实现一个函数来调整该数组中数字的顺序,
//使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
//解题思路:
//像这种数组分前后的,考虑使用前后两个指针。
//如果都是奇数,前指针后移。都是偶数,后指针前移
//前奇后偶,同时移动。前偶后奇,则交换位置。
#include <iostream>
#include <vector>
using namespace std;
vector<int> exchange(vector<int>& nums) {
int i = 0, j = nums.size() - 1;
while (i < j) {
bool a = nums[i] & 1;
bool b = nums[j] & 1;
if (a && b)//都是奇数
i++;
else if (!a && !b)//都是偶数
j--;
else if (a && !b) {//a奇b偶
i++;
j--;
} else {//a偶b奇
int tmp = nums[i];
nums[i++] = nums[j];
nums[j--] = tmp;
}
}
return nums;
}
int main() {
vector<int> nums = {1,2,3,4};
auto res = exchange(nums);
for(int n:res)
cout << n << ' ';
return 0;
}
面试题22. 链表中倒数第k个节点
// Created by zzh on 2020/4/27.
//面试题22. 链表中倒数第k个节点
//输入一个链表,输出该链表中倒数第k个节点。
//为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
//例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。
//这个链表的倒数第3个节点是值为4的节点。
//示例:
//给定一个链表: 1->2->3->4->5, 和 k = 2.
//返回链表 4->5.
//解题思路:
//这类题,使用两个指针,后边的指针先走k步,然后二者同步走,直到后指针到头
#include <iostream>
#include "List.h"
using namespace std;
ListNode* getKthFromEnd(ListNode* head, int k) {
if(head==nullptr||k<=0)
return nullptr;
ListNode* pre=head;
ListNode* cur=head;
while(k--){
if(cur==nullptr)
return nullptr;
cur=cur->next;
}
while(cur){
pre=pre->next;
cur=cur->next;
}
return pre;
}
int main() {
vector<int> list = {1,2,3,4,5};
ListNode* head = createList(list);
ListNode* res = getKthFromEnd(head, 3);
cout << res->val;
return 0;
}
面试题24. 反转链表
// Created by zzh on 2020/4/27.
//面试题24. 反转链表
//定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
//示例:
//输入: 1->2->3->4->5->NULL
//输出: 5->4->3->2->1->NULL
//限制:
//0 <= 节点个数 <= 5000
#include <iostream>
#include <vector>
#include "List.h"
#include <stack>
using namespace std;
ListNode* reverseList(ListNode* head) {
if(head == nullptr)
return head;
ListNode* pre= nullptr;
ListNode* cur=head;
ListNode* tmp=head;
while(cur){
tmp=cur->next;//暂存当前节点的下一个节点的指针
cur->next=pre;//当前节点的下一节点更改至前一节点
pre=cur;//前节点后移
cur=tmp;//当前节点后移
}
return pre;
}
int main() {
vector<int> list = {1,2,3,4,5};
ListNode* head = createList(list);
ListNode* res = reverseList(head);
printList(res);
return 0;
}
面试题25. 合并两个排序的链表
// Created by zzh on 2020/4/27.
//面试题25. 合并两个排序的链表
//输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
//示例1:
//输入:1->2->4, 1->3->4
//输出:1->1->2->3->4->4
//限制:
//0 <= 链表长度 <= 1000
#include <iostream>
#include <vector>
#include "List.h"
using namespace std;
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1==nullptr && l2==nullptr)
return nullptr;
if(l1==nullptr && l2!=nullptr)
return l2;
if(l1!=nullptr && l2==nullptr)
return l1;
/*解法1 创建出一个新链表
ListNode* sum=new ListNode(0);
ListNode* cur=sum;
while(l1!=NULL&&l2!=NULL){
if(l1->val<=l2->val){
cur->next=new ListNode(l1->val);
cur=cur->next;
l1=l1->next;
}
else{
cur->next=new ListNode(l2->val);
cur=cur->next;
l2=l2->next;
}
}
while(l1==NULL&&l2!=NULL){
cur->next=new ListNode(l2->val);
cur=cur->next;
l2=l2->next;
}
while(l1!=NULL&&l2==NULL){
cur->next=new ListNode(l1->val);
cur=cur->next;
l1=l1->next;
}
return sum->next;
*/
//解法2,使用原链表,调整两个链表每个节点间的指向关系
auto* cur=new ListNode(0);
ListNode* res=cur;
while(l1!=nullptr&&l2!=nullptr){
if(l1->val<=l2->val){
cur->next=l1;//下一个节点指向l1
l1=l1->next;//l1后移
}
else{
cur->next=l2;
l2=l2->next;
}
cur=cur->next; // 当前节点后移
}
cur->next = l1==nullptr?l2:l1;//接上没到头的部分
return res->next;
}
int main() {
vector<int> list1 = {1,2,3,4,5};
vector<int> list2 = {7,8,9};
ListNode* head1 = createList(list1);
ListNode* head2 = createList(list2);
ListNode* res = mergeTwoLists(head1,head2);
printList(res);
return 0;
}
面试题26. 树的子结构
// Created by zzh on 2020/4/29.
//面试题26. 树的子结构
//输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
//B是A的子结构, 即 A中有出现和B相同的结构和节点值。
//例如:
//给定的树 A:
// 3
// / \
// 4 5
// / \
// 1 2
//给定的树 B:
// 4
// /
// 1
//返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
//示例 1:
//输入:A = [1,2,3], B = [3,1]
//输出:false
//示例 2:
//输入:A = [3,4,5,1,2], B = [4,1]
//输出:true
//限制:
//0 <= 节点个数 <= 10000
//思路:使用递归,isSubStructure递归使得A每个节点都能尝试作为初始节点,
// helper递归使得能一直比较到叶子节点
#include <iostream>
#include <vector>
#include "BinaryTree.h"
using namespace std;
bool helper(TreeNode* A, TreeNode* B){
if(A==nullptr||B==nullptr)
return (B == nullptr);//如果B遍历到叶子节点以下了,则是子树,如果A先到叶子节点以下了,则不是
if(A->val!=B->val)
return false;
else//如果当前节点值相等,则继续向下比较左右子树
return helper(A->left,B->left) && helper(A->right,B->right);
}
bool isSubStructure(TreeNode* A, TreeNode* B) {
if(A==nullptr||B==nullptr){
return false;
}
//helper用于判断从当前节点开始,是否为子树。
// 另两个递归,是节点下移,递归使得A每个节点都能尝试作为初始节点
return helper(A,B)||isSubStructure(A->left,B)||isSubStructure(A->right,B);
}
int main() {
vector<int> A={3,4,5,1,2};
vector<int> B={4,1};
auto a = createTree(A);
auto b = createTree(B);
cout << isSubStructure(a,b);
return 0;
}
面试题27. 二叉树的镜像
// Created by zzh on 2020/4/27.
//面试题27. 二叉树的镜像
//请完成一个函数,输入一个二叉树,该函数输出它的镜像。
//例如输入:
// 4
// / \
// 2 7
// / \ / \
//1 3 6 9
//镜像输出:
// 4
// / \
// 7 2
// / \ / \
//9 6 3 1
//示例 1:
//输入:root = [4,2,7,1,3,6,9]
//输出:[4,7,2,9,6,3,1]
//限制:
//0 <= 节点个数 <= 1000
#include <iostream>
#include "BinaryTree.h"
using namespace std;
TreeNode* mirrorTree(TreeNode* root) {
if(root== nullptr)
return nullptr;
swap(root->left,root->right);//root节点存的两个指针交换位置
mirrorTree(root->left);
mirrorTree(root->right);
return root;
}
int main() {
auto p1 = createTreeNode(4);
auto p2 = createTreeNode(2);
auto p3 = createTreeNode(7);
auto p4 = createTreeNode(1);
auto p5 = createTreeNode(3);
auto p6 = createTreeNode(6);
auto p7 = createTreeNode(9);
connectTreeNode(p1,p2,p3);
connectTreeNode(p2,p4,p5);
connectTreeNode(p3,p6,p7);
PrintTree(p1);
p1 = mirrorTree(p1);
PrintTree(p1);
return 0;
}
面试题28. 对称的二叉树
// Created by zzh on 2020/4/27.
//面试题28. 对称的二叉树
//请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
//例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
// 1
// / \
// 2 2
// / \ / \
//3 4 4 3
//但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
// 1
// / \
// 2 2
// \ \
// 3 3
//示例 1:
//输入:root = [1,2,2,3,4,4,3]
//输出:true
//示例 2:
//输入:root = [1,2,2,null,3,null,3]
//输出:false
#include <iostream>
#include <vector>
#include "BinaryTree.h"
using namespace std;
bool sub(TreeNode* left,TreeNode* right){
if(left==nullptr&&right!=nullptr)
return false;
if(left!=nullptr&&right==nullptr)
return false;
if(left==nullptr&&right==nullptr)
return true;
if (left->val==right->val)
return sub(left->left,right->right)&&sub(left->right,right->left);
else
return false;
}
bool isSymmetric(TreeNode* root) {
if(root==nullptr)
return true;
return sub(root->left,root->right);
}
int main() {
vector<int> vec = {1,2,2,3,4,4,3};
//vector<char> vec = {'1','2','2','#','3','#','3'}; //#号表示空节点
auto root = createTree(vec);
bool res = isSymmetric(root);
cout << res;
return 0;
}
面试题29. 顺时针打印矩阵
// Created by zzh on 2020/4/27.
//面试题29. 顺时针打印矩阵
//输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
//示例 1:
//输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
//输出:[1,2,3,6,9,8,7,4,5]
//示例 2:
//输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
//输出:[1,2,3,4,8,12,11,10,9,5,6,7]
//限制:
//0 <= matrix.length <= 100
//0 <= matrix[i].length <= 100
//思路:不断循环,不断缩小范围,直到上下或者左右碰头
#include <iostream>
#include <vector>
#include <typeinfo>
using namespace std;
vector<int> spiralOrder(vector<vector<int>>& matrix) {
if(matrix.empty())
return {};
int top=0,bottom=matrix.size()-1,left=0,right=matrix[0].size()-1;
vector<int> res;
while(true){
for(int i=left;i<=right;i++)
res.push_back(matrix[top][i]);
top++;
if(top>bottom)
break;
for(int i=top;i<=bottom;i++)
res.push_back(matrix[i][right]);
right--;
if(left>right)
break;
for(int i=right;i>=left;i--)
res.push_back(matrix[bottom][i]);
bottom--;
if(top>bottom)
break;
for(int i=bottom;i>=top;i--)
res.push_back(matrix[i][left]);
left++;
if(left>right)
break;
}
return res;
}
int main() {
vector<vector<int>> matrix = {{1,2,3},{4,5,6},{7,8,9}};
auto res = spiralOrder(matrix);
for(int i:res){
cout << i;
}
return 0;
}
面试题30. 包含min函数的栈
// Created by zlgybz on 2020/5/1.
//面试题30. 包含min函数的栈
//定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
//示例:
//MinStack minStack = new MinStack();
//minStack.push(-2);
//minStack.push(0);
//minStack.push(-3);
//minStack.min(); --> 返回 -3.
//minStack.pop();
//minStack.top(); --> 返回 0.
//minStack.min(); --> 返回 -2.
//思路:使用一个辅助栈用来记录当前位置非严格降序的最小值
#include <iostream>
#include <stack>
using namespace std;
class MinStack {
public:
/** initialize your data structure here. */
MinStack() {
}
void push(int x) {
s.push(x);
if(m.empty()||x<=m.top())//如果m为空或者当前x最小
m.push(x);
}
void pop() {
if(s.top()==m.top())//如果当前是最小元素,则都吐出
m.pop();
s.pop();
}
int top() {
return s.top();
}
int min() {
return m.top();
}
stack<int> s;
stack<int> m;
};
int main() {
MinStack minStack;
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
cout << minStack.min() << endl;
minStack.pop();
cout << minStack.top() << endl;
cout << minStack.min() << endl;
return 0;
}
面试题31. 栈的压入、弹出序列
// Created by zlgybz on 2020/5/1.
//面试题31. 栈的压入、弹出序列
//输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。
//假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,
//序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
//思路:
//尝试按照 popped 中的顺序模拟一下出栈操作,如果符合则返回 true,否则返回 false。这里用到的贪心法则是如果栈顶元素等于 popped 序列中下一个要 pop 的值,则应立刻将该值 pop 出来。
//使用一个栈 st 来模拟该操作。将 pushed 数组中的每个数依次入栈,同时判断这个数是不是 popped 数组中下一个要 pop 的值,如果是就把它 pop 出来。最后检查栈是否为空。
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
if(pushed.empty()||popped.empty())
return true;
stack<int> st;
int j=0;
for(int i=0;i<pushed.size();i++){//挨个压入
st.push(pushed[i]);
//由于压入栈的所有数字均不相等,如果当前栈顶元素等于要吐出的元素,则吐出
while(!st.empty() && j<popped.size() && st.top()==popped[j]){
st.pop();
j++;
}
}
return st.empty();//是否全部出完
}
int main() {
vector<int> A={1,2,3,4,5};
vector<int> B={4,3,5,1,2};
cout << validateStackSequences(A, B) << endl;
return 0;
}
面试题32 - I. 从上到下打印二叉树
// Created by zlgybz on 2020/5/1.
//面试题32 - I. 从上到下打印二叉树
//从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
//例如:给定二叉树: [3,9,20,null,null,15,7],
// 3
// / \
// 9 20
// / \
// 15 7
//返回:
//[3,9,20,15,7]
//提示:
//节点总数 <= 1000
//思路:使用队列的先进先出特性,分层打印
#include <iostream>
#include <vector>
#include "BinaryTree.h"
using namespace std;
vector<int> levelOrder(TreeNode* root) {
vector<int> res;
queue<TreeNode*> tmp;
if (root == nullptr){
return {};
}
else{
tmp.push(root);
}
while(!tmp.empty()){
auto front = tmp.front();
if(front == nullptr){//已经到了叶子节点的下方
tmp.pop();
}
else{
res.push_back(front->val);
tmp.push(front->left);
tmp.push(front->right);
tmp.pop();
}
}
return res;
}
int main() {
vector<int> A={3,9,20,-1,-1,15,7};//-1表示空节点
auto root = createTree(A);
auto res = levelOrder(root);
for(int i:res)
cout << i << ' ';
return 0;
}
面试题32 - II. 从上到下分行打印二叉树
// Created by zlgybz on 2020/5/1.
//面试题32 - II. 从上到下打印二叉树 II
//从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
//例如:给定二叉树: [3,9,20,null,null,15,7],
// 3
// / \
// 9 20
// / \
// 15 7
//返回其层次遍历结果:
//[
//[3],
//[9,20],
//[15,7]
//]
//提示:
//节点总数 <= 1000
//思路:和上一题相比,增加一个for循环,用于分隔每行。
#include <iostream>
#include <vector>
#include "BinaryTree.h"
using namespace std;
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
queue<TreeNode*> tmp;
if (root == nullptr){
return {};
}
else {
tmp.push(root);
}
while(!tmp.empty()){
int length = tmp.size();
vector<int> vec;
for (int i=0;i<length;i++){
auto front = tmp.front();
vec.push_back(front->val);
if(front->left!=nullptr) tmp.push(front->left);
if(front->right!=nullptr) tmp.push(front->right);
tmp.pop();
}
res.push_back(vec);
}
return res;
}
int main() {
vector<int> A={3,9,20,-1,-1,15,7};//-1表示空节点
auto root = createTree(A);
auto res = levelOrder(root);
for(int i=0;i<res.size();i++){
for(int j:res[i])
cout << j << ' ';
cout << endl;
}
return 0;
}
面试题32 - III. 从上到下 之字型分行 打印二叉树
// Created by zlgybz on 2020/5/1.
//面试题32 - III. 从上到下打印二叉树 III
//请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
//例如:
//给定二叉树: [3,9,20,null,null,15,7],
// 3
// / \
// 9 20
// / \
// 15 7
//返回其层次遍历结果:
//[
//[3],
//[20,9],
//[15,7]
//]
//提示:
//节点总数 <= 1000
//思路:和上一题相比,要使用双端队列,队列的存储顺序依旧是从前到后对应树的从左到右
// 但是用flag区分奇偶行,实现奇偶行不同的取和放操作。
#include <iostream>
#include <vector>
#include "BinaryTree.h"
using namespace std;
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
deque<TreeNode*> tmp;
if (root == nullptr){
return {};
}
else {
tmp.push_back(root);
}
bool flag = true;
while(!tmp.empty()){
int length = tmp.size();
vector<int> vec;
for (int i=0;i<length;i++){
if(flag){//奇数行,前取后放
auto front = tmp.front();
vec.push_back(front->val);
if(front->left!=nullptr) tmp.push_back(front->left);//先放左节点
if(front->right!=nullptr) tmp.push_back(front->right);
tmp.pop_front();
}
else{//偶数行,后取前放
auto front = tmp.back();
vec.push_back(front->val);
if(front->right!=nullptr) tmp.push_front(front->right);//先放右节点
if(front->left!=nullptr) tmp.push_front(front->left);
tmp.pop_back();
}
}
flag = !flag;
res.push_back(vec);
}
return res;
}
int main() {
vector<int> A={3,9,20,-1,-1,15,7};//用-1来表示空节点
auto root = createTree(A);
auto res = levelOrder(root);
for(auto & re : res){
for(int j:re)
cout << j << ' ';
cout << endl;
}
return 0;
}
面试题33. 二叉搜索树的后序遍历序列
后序遍历定义: [ 左子树 | 右子树 | 根节点 ] ,即遍历顺序为 “左、右、根”。
二叉搜索树定义: 左子树中所有节点的值 < 根节点的值;右子树中所有节点的值 > 根节点的值;其左、右子树也分别为二叉搜索树。
与解析处理不同的地方:
- m初始值给-1,用以判断如果找不到大于根节点的节点的情况,此时全是左子树。
- 当右子树区间出现不符合条件的节点时,直接返回false即可。
// Created by zlgybz on 2020/5/1.
//面试题33. 二叉搜索树的后序遍历序列
//输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
//参考以下这颗二叉搜索树:
// 5
// / \
// 2 6
// / \
// 1 3
//示例 1:
//输入: [1,6,3,2,5]
//输出: false
//示例 2:
//输入: [1,3,2,6,5]
//输出: true
//提示:
//数组长度 <= 1000
#include <iostream>
#include <vector>
using namespace std;
bool helper(vector<int>& postorder,int start,int end){
if(start>=end)
return true;
int mid = -1;
for(int i=start;i<end;i++){//找到第一个右子树的索引
if(postorder[i]>postorder[end]){
mid = i;
break;
}
}
if(mid==-1)//没找到,说明全是左子树
return helper(postorder,start,end-1);
for(int j=mid;j<end;j++){//判断右边的是否都大于根节点
if(postorder[j]<postorder[end])
return false;
}
return helper(postorder,start,mid-1) && helper(postorder,mid,end-1);
}
bool verifyPostorder(vector<int>& postorder) {
if(postorder.empty())
return true;
return helper(postorder,0,postorder.size()-1);
}
int main() {
vector<int> A={4, 8, 6, 12, 16, 14, 10};
cout << verifyPostorder(A) << endl;
return 0;
}
面试题34. 二叉树中和为某一值的路径
// Created by zlgybz on 2020/5/1.
//面试题34. 二叉树中和为某一值的路径
//输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
//示例:给定如下二叉树,以及目标和 sum = 22,
// 5
// / \
// 4 8
// / / \
// 11 13 4
// / \ / \
// 7 2 5 1
//返回:
//[
//[5,4,11,2],
//[5,8,4,5]
//]
//提示:节点总数 <= 10000
#include <iostream>
#include <vector>
#include "BinaryTree.h"
using namespace std;
//或者使用类,将res和vec变成成员变量也可以。
void helper(vector<vector<int>> &res,vector<int> &vec,TreeNode* root, int sum){
if(!root)//到达了叶子节点的下一层
return;
sum -= root->val;
vec.push_back(root->val);
if(sum==0&&root->left==nullptr&&root->right==nullptr)//到了叶子节点且满足要求
res.push_back(vec);
helper(res,vec,root->left,sum);//执行这一行,直到一个路径的叶子节点然后return,才会再执行下一行
helper(res,vec,root->right,sum);
vec.pop_back();//一层一层吐出,吐出的起点是叶子节点下边两个都return,然后到达这一行吐出叶子节点的值,然后往上递推吐出
}
vector<vector<int>> pathSum(TreeNode* root, int sum) {
vector<vector<int>> res;
vector<int> vec;
helper(res,vec,root,sum);
return res;
}
int main() {
vector<int> A={5,4,8,11,-1,13,4,7,2,-1,-1,-1,-1,5,1};
auto root = createTree(A);
auto res = pathSum(root,22);
PrintTree(root);
for(auto & re : res){
for(int j:re)
cout << j << ' ';
cout << endl;
}
return 0;
}
面试题35. 复杂链表的复制
// Created by zlgybz on 2020/5/1.
//面试题35. 复杂链表的复制
//请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
//示例 1:
//输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
//输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
//示例 2:
//输入:head = [[1,1],[2,1]]
//输出:[[1,1],[2,1]]
//示例 3:
//输入:head = [[3,null],[3,0],[3,null]]
//输出:[[3,null],[3,0],[3,null]]
//示例 4:
//输入:head = []
//输出:[]
//解释:给定的链表为空(空指针),因此返回 null。
//提示:-10000 <= Node.val <= 10000
//Node.random 为空(null)或指向链表中的节点。节点数目不超过 1000 。
//思路:关键在于使用哈希表,将复制的节点和原来的节点一一对应,这样才能方便地建立random连接。
//另外还可以用BFS或者DFS等方法。
#include <iostream>
#include <unordered_map>
using namespace std;
class Node {
public:
int val;
Node* next;
Node* random;
explicit Node(int x) {
val = x;
next = nullptr;
random = nullptr;
}
};
Node* copyRandomList(Node* head) {
if(head==nullptr)
return head;
unordered_map<Node*,Node*> m;
//创建所有节点,并储存在哈希表中和原链表一一对应
for(auto it = head;it!=nullptr;it=it->next){
m[it] = new Node(it->val);
}
//将新建链表每个节点的next和random通过哈希表对应赋值
for(auto it = head;it!=nullptr;it=it->next){
m[it]->next = m[it->next];
m[it]->random = m[it->random];
}
return m[head];
}
int main() {
auto p0 = new Node(7);
auto p1 = new Node(13);
auto p2 = new Node(11);
auto p3 = new Node(10);
auto p4 = new Node(1);
p0->next =p1;
p0->random = nullptr;
p1->next =p2;
p1->random = p0;
p2->next = p3;
p2->random = p4;
p3->next =p4;
p3->random = p2;
p4->random = p0;
auto res = copyRandomList(p0);
auto tmp = res;
// 0 1 2 3 4
//[[7,null],[13,0],[11,4],[10,2],[1,0]]
while(tmp){
cout << tmp->val << ' ';
if(tmp->random)
cout << tmp->random->val;
else
cout << "null";
cout << ',';
tmp = tmp->next;
}
return 0;
}
面试题36. 二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。
要求不能创建任何新的节点,只能调整树中节点指针的指向
/*
// Definition for a Node.
class Node {
public:
int val;
Node* left;
Node* right;
Node() {}
Node(int _val) {
val = _val;
left = NULL;
right = NULL;
}
Node(int _val, Node* _left, Node* _right) {
val = _val;
left = _left;
right = _right;
}
};
*/
class Solution {
public:
void helper(Node* root, Node*& head,Node*& pre){ //递归函数
if(root==nullptr)//每到叶子节点的下一个,返回
return;
//左
helper(root->left,head,pre);//往左走,进入新一轮左根右递归
if(head==nullptr){//一直往左走,走到了最左边叶子节点的下一个,返回之后,走到这里
head = root;//头结点
pre = root;//暂存,用于和下一个节点连接
}
//根
else{//如果不是最左边叶子节点
root->left = pre;//当前节点左边是pre
pre->right = root;//pre的右边是当前节点
pre = root;//pre后移
}
//右
helper(root->right,head,pre);//往右走,重新进入左根右递归
}
Node* treeToDoublyList(Node* root) {
if(root==nullptr)//特殊输入
return nullptr;
Node* head=nullptr,*pre=nullptr;
helper(root,head,pre);//树的中序 递归函数进行节点连接
head->left = pre;//头尾节点连接
pre->right = head;
return head;
}
};
面试题37. 序列化二叉树
// Created by zlgybz on 2020/5/1.
//面试题37. 序列化二叉树
//请实现两个函数,分别用来序列化和反序列化二叉树。
//示例:
//你可以将以下二叉树:
// 1
// / \
// 2 3
// / \
// 4 5
//序列化为 "[1,2,3,null,null,4,5]"
//测试用法:deserialize(serialize(root))应该与root相同;
//思路:
//serialize:使用队列层序输出树,使用ostringstream更方便的转换成字符串
//deserialize:使用istringstream分离字符串的每个元素。
// 空节点对应空指针,实际节点对应实际节点的指针,存入一个数组。然后在节点之间建立连接关系
#include <iostream>
#include <vector>
#include <c++/sstream>
#include "BinaryTree.h"
using namespace std;
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
if(!root)
return "";
ostringstream res;//使用ostringstream,是为了能将null和数值一起转成字符串
//使用队列,以层序输出到res中,注意以空格隔开,方便下一个函数读取
queue<TreeNode*> q;
q.push(root);
while(!q.empty()){
if(q.front()==nullptr){
res << "null ";
q.pop();
}
else{
res << q.front()->val << " ";
q.push(q.front()->left);
q.push(q.front()->right);
q.pop();
}
}
return res.str();
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
if (data.empty())
return nullptr;
istringstream input(data);//使用istringstream,是为了能将整套数据挨个读取,因为中间用空格隔开的
string tmp;
vector<TreeNode*> vec;//层序存储每个节点的指针,里面也包括了每个叶子节点的下一层节点
while(input>>tmp){
if(tmp=="null")//叶子节点的下一节点
vec.push_back(nullptr);
else//正常带数值的节点
vec.push_back(new TreeNode(stoi(tmp)));//stoi 将字符串转换成数值
}
//设置每个节点的指向关系
int j=1;
for(int i=0;j<vec.size()-1;i++){//遍历vec的每个指针,直到最后一层,剩下的全是叶子节点的下一层时
if(vec[i]==nullptr) continue;//叶子节点的下一节点,则不做处理
vec[i]->left = vec[j++];//对应的规律
vec[i]->right = vec[j++];
}
return vec[0];
}
int main() {
vector<int> vec = {1,2,3,-1,-1,4,5};
TreeNode* head = createTree(vec);//使用头文件中的函数生成输入二叉树
PrintTree(head);
string res = serialize(head);//将二叉树转换成字符串并输出
cout << res << endl;
TreeNode* tree = deserialize(res);//将字符串转换成树结构并打印出来
PrintTree(tree);
return 0;
}
面试题38. 字符串的排列
解题思路:回溯+剪枝
排列方案数量: 对于一个长度为 n 的字符串(假设字符互不重复),其排列共有 n×(n−1)×(n−2)…×2×1种方案。
排列方案的生成方法: 根据字符串排列的特点,考虑深度优先搜索所有排列方案。即通过字符交换,先固定第 1 位字符( n种情况)、再固定第 2位字符( n−1 种情况)、... 、最后固定第 n 位字符( 1 种情况)。
重复方案与剪枝: 当字符串存在重复字符时,排列方案中也存在重复方案。为排除重复方案,需在固定某位字符时,保证 “每种字符只在此位固定一次” ,即遇到重复字符时不交换,直接跳过。从 DFS 角度看,此操作称为 “剪枝” 。
// Created by zlgybz on 2020/5/1.
//面试题38. 字符串的排列
//输入一个字符串,打印出该字符串中字符的所有排列。
//你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
//示例:
//输入:s = "abc"
//输出:["abc","acb","bac","bca","cab","cba"]
//限制:
//1 <= s 的长度 <= 8
#include <iostream>
#include <vector>
#include <set>
using namespace std;
void helper(string& s,int start,set<string>& st) {
if(start==s.size()-1){//终止条件,当回溯进行到最后一个元素时
st.insert(s);//插入当前的排列
return;//返回,回到上一层,也就是倒数第二层
}
for(int i=start;i<s.size();i++){
swap(s[start],s[i]);//交换,使s[i]在s[start]位置
helper(s,start+1,st);//当前情况下,start后移,继续操作余下的元素
swap(s[start],s[i]);//将元素再交换回来,i++,进入下一个循环,直到交换到最后一个元素,本层结束,返回上一层
}
}
vector<string> permutation(string s) {
if(s.empty())//特殊输入
return {""};
set<string> st;//集合,用于筛掉重复字符串
helper(s,0,st);//回溯函数
return vector<string>(st.begin(),st.end());//返回整个集合
}
int main() {
string input = "abc";
auto res = permutation(input);
for(const string& s:res)
cout << s << ',';
return 0;
}
面试题39. 数组中出现次数超过一半的数字
// Created by zlgybz on 2020/5/1.
//面试题39. 数组中出现次数超过一半的数字
//数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
//你可以假设数组是非空的,并且给定的数组总是存在多数元素。
//示例 1:
//输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
//输出: 2
//限制:1 <= 数组长度 <= 50000
//1哈希表记录(当前)
//2排序,找中间位置元素
//3摩尔投票:
//初始化: 票数统计 votes=0 , 众数 x;
//循环抵消: 遍历数组 nums 中的每个数字 num ;
//当 票数 votes 等于 0 ,则假设 下个数字 num 为 众数 x ;
//当 num=x 时,票数 votes 自增 1 ;否则,票数 votes 自减 1 。
//返回值: 返回 众数 x 即可。
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
int majorityElement(vector<int>& nums) {
int length = nums.size()/2;
unordered_map<int,int> m;
for(int i:nums){
if(m.find(i)!=m.end())
m[i]++;
else
m[i]=1;
if(m[i]>length)
return i;
}
return 0;
}
int main() {
vector<int> A={1, 2, 3, 2, 2, 2, 5, 4, 2};
cout << majorityElement(A) << endl;
return 0;
}
面试题40. 最小的k个数
// Created by zlgybz on 2020/5/1.
//面试题40. 最小的k个数
//输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
//示例 1:
//输入:arr = [3,2,1], k = 2
//输出:[1,2] 或者 [2,1]
//示例 2:
//输入:arr = [0,1,2,1], k = 1
//输出:[0]
//限制:
//0 <= k <= arr.length <= 10000
//0 <= arr[i] <= 10000
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> getLeastNumbers(vector<int>& arr, int k) {
vector<int> res;
sort(arr.begin(),arr.end());
for(int i=0;i<k;i++)
res.push_back(arr[i]);
return res;
}
int main() {
vector<int> A={3,4,5,1,2};
auto res = getLeastNumbers(A, 2);
for(int i:res)
cout << i << ' ';
return 0;
}
面试题41. 数据流中的中位数
// Created by zlgybz on 2020/5/1.
//面试题41. 数据流中的中位数
//如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
//例如,
//[2,3,4] 的中位数是 3
//[2,3] 的中位数是 (2 + 3) / 2 = 2.5
//设计一个支持以下两种操作的数据结构:
//void addNum(int num) - 从数据流中添加一个整数到数据结构中。
//double findMedian() - 返回目前所有元素的中位数。
//示例 1:
//输入:
//["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
//[[],[1],[2],[],[3],[]]
//输出:[null,null,null,1.50000,null,2.00000]
//示例 2:
//输入:
//["MedianFinder","addNum","findMedian","addNum","findMedian"]
//[[],[2],[],[3],[]]
//输出:[null,null,2.00000,null,2.50000]
//限制:最多会对 addNum、findMedia进行 50000 次调用。
//方法一,每进来一个新元素就对数组进行sort排序,时间复杂度较高
//方法二,使用二分查找的lower_bound找到插入的位置,使用insert插入。
//方法三,使用大小堆:https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/solution/you-xian-dui-lie-by-z1m/
#include <iostream>
#include <vector>
using namespace std;
class MedianFinder {
public:
/** initialize your data structure here. */
MedianFinder() {//构造函数
}
void addNum(int num) {//添加元素
if(store.empty())
store.push_back(num);
else{
//insert,在某个位置插入元素
//lower_bound()函数,给定有序数组的起点和终点,找到第一个大于等于num的位置索引,其内部使用的是二分法查找
//upper_bound()函数,它返回大于num的第一个元素的索引
store.insert(lower_bound(store.begin(),store.end(),num),num);
}
}
double findMedian() {
int n=store.size();
return n&1 ? store[n/2] : (store[n/2]+store[n/2-1])*0.5;
}
vector<int> store;
};
int main() {
MedianFinder* obj = new MedianFinder();
obj->addNum(1);
obj->addNum(2);
cout << obj->findMedian() << ' ';
obj->addNum(3);
cout << obj->findMedian() << ' ';
return 0;
}
面试题42. 连续子数组的最大和
// Created by zlgybz on 2020/5/1.
//面试题42. 连续子数组的最大和
//输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
//要求时间复杂度为O(n)。
//示例1:
//输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
//输出: 6
//解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
//提示:
//1 <= arr.length <= 10^5
//-100 <= arr[i] <= 100
//思路:动态规划,判断是保留前边子序的贡献还是另起一个新子序
#include <iostream>
#include <vector>
using namespace std;
int maxSubArray(vector<int>& nums) {
if(nums.empty())
return 0;
int res = nums[0];
int res_max=nums[0];
//动态规划的方法
for(int i=1;i<nums.size();i++){
//以当前值结尾的子序的最大值。如果前边的贡献是正的,则加上,否则从从当前值开始另起一个新子序
res = max(nums[i],res+nums[i]);
res_max = max(res,res_max);//历史子序的最大值
}
return res_max;
}
int main() {
vector<int> A={-2,1,-3,4,-1,2,1,-5,4};
cout << maxSubArray(A) << endl;
return 0;
}
面试题43. 1~n整数中1出现的次数
解析:需要通过找规律来分析。
我们假设高位为high,当前位为cur,低位为low,i代表着需要统计的位置数(1对应个位,10对应十位,100对应百位),则对每一位的个数count有:
cur=0,count = high*i; 高位在0到high-1这high个数中,cur都可以取1, 并且low有从0到i-1 这i种取值
cur=1,count=high*i+low+1 高位在0到high-1的情况与上边相同,当高位取high时,cur还可以取1,此时低位还有从0到low这low+1个取值
cur>1,count=high*i+i 高位在0到high-1的情况与上边相同。高位取high,cur取1时,低位有从0到i-1 这i种取值
最终累加所有位置上的个数即最终答案。
// Created by zlgybz on 2020/5/1.
//面试题43. 1~n整数中1出现的次数
//输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。
//例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。
//示例 1:
//输入:n = 12
//输出:5
//示例 2:
//输入:n = 13
//输出:6
//限制:1 <= n < 2^31
#include <iostream>
#include <vector>
using namespace std;
int countDigitOne(int n) {
int res = 0;
long i = 1;//从各位开始,i=10代表十位,i=100代表百位 1 <= n < 2^31,所以用long(4字节32位) int在16位编译器上为16位
while(n/i!=0){
long high = n/(10*i);//将当前位和低位舍掉,得到高位的数字
int cur = (n/i)%10;//将低位舍掉,再对10取余得到当前位的数字
long low = n%i;//对i取余,得到低位的数字
if(cur==0)
res += high*i;
else if(cur==1)
res += high*i + low + 1;
else
res += high*i + i ;
i *= 10;
}
return res;
}
int main() {
cout << countDigitOne(1410065408) << endl;
return 0;
}
面试题44. 数字序列中某一位的数字
找规律的题
// Created by zlgybz on 2020/5/1.
//面试题44. 数字序列中某一位的数字
//数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
//请写一个函数,求任意第n位对应的数字。
//示例 1:
//输入:n = 3
//输出:3
//示例 2:
//输入:n = 11
//输出:0
//限制:
//0 <= n < 2^31
#include <iostream>
#include <vector>
using namespace std;
int findNthDigit(int n){
if(n==0)//将第0位的0额外考虑
return 0;
//以下每层都考虑是从索引1开始 1-9 9个数 10-99 90个数 100-999 900个数
int start = 1;//每一层的起点
int digit = 1;//每一层的一个数的位数
while(n-9*start*digit>0){ //得到第n位所在的数在哪一层,其起点是start,位数为digit
n -= 9*start*digit;
start *= 10;
digit += 1;
}
int num = start + (n-1)/digit;//得到第n位所在的数
// n进行减一操作,是因为如果刚好是digit的整数倍,意味着是某个数的最后一位。比如n=digit,则n应该是start的最后一位,不用start+1
string s = to_string(num);
int i = (n-1)%digit;//在数的哪一位,索引从0开始,所以减一
return (s[i]-'0');
}
int main() {
cout << findNthDigit(11) << endl;
return 0;
}
面试题45. 把数组排成最小的数
思路:使用sort排序,自定义一个排序规则函数。
// Created by zlgybz on 2020/5/1.
//面试题45. 把数组排成最小的数
//输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
//示例 1:
//输入: [10,2]
//输出: "102"
//示例 2:
//输入: [3,30,34,5,9]
//输出: "3033459"
//提示:0 < nums.length <= 100
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//如果不用lambda表达式,需要再定义一个排序规则函数
//bool compare(string a,string b){
// return(a+b<b+a);
//}
string minNumber(vector<int>& nums) {
vector<string> tmp;
for(int i:nums)
tmp.push_back(to_string(i));
// lambda表达式方式一
// auto compare = [](const string& a,const string& b){return(a+b<b+a);};
// sort(tmp.begin(),tmp.end(),compare);
//lambda表达式方式二
sort(tmp.begin(),tmp.end(),[](const string& a,const string& b){return(a+b<b+a);});//排序规则,按照compare都为true排
string s;
for(const auto& i:tmp)
s += i;
return s;
}
int main() {
vector<int> A={3,30,34,5,9};
cout << minNumber(A) << endl;
return 0;
}
面试题46. 把数字翻译成字符串
// Created by zlgybz on 2020/5/1.
//面试题46. 把数字翻译成字符串
//给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。
//一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
//示例 1:
//输入: 12258
//输出: 5
//解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
//提示:0 <= num < 231
//思路:类似于跳台阶问题,这种寻找所有组合可能性的,大多使用递归回溯方法
#include <iostream>
#include <vector>
using namespace std;
int helper(string s,int pos){ //递归回溯函数
if(pos>=s.size()-1)//这里面包括了到达最后一位和最后一位之后的一位
return 1;
if(s[pos]=='0'|| s.substr(pos,2) > "25")//只有一种选择,后移一位
return helper(s,pos+1);
else //10到25之间,有两种选择
return helper(s,pos+1)+helper(s,pos+2); //这里pos+2有可能到达最后一位之后,此时返回的是1+1
}
int translateNum(int num) {
string s = to_string(num);
return helper(s,0);
}
int main() {
cout << translateNum(12258) << endl;
return 0;
}
面试题47. 礼物的最大价值
class Solution {
public:
//采用这种回溯递归的方法,可以解,但中间会出现很多重复计算,会超时。和上楼梯,斐波那契数列类似
//如果是在求最大解的同时要得到最优的路径,则可以采用此类方法
//从前往后的回溯递归
// int helper(vector<vector<int>>& grid,int row,int col,int i,int j,int res){
// res += grid[row][col];
// if(row==i&&col==j)
// return res;
// else if(row==i&&col<j)
// return helper(grid,row,col+1,i,j,res);
// else if(row<i&&col==j)
// return helper(grid,row+1,col,i,j,res);
// else
// return max(helper(grid,row,col+1,i,j,res),helper(grid,row+1,col,i,j,res));
// }
//从后往前的回溯递归
// int helper(vector<vector<int>>& grid,int row,int col){
// if(row==0&&col==0)
// return grid[row][col];
// else if(row==0&&col>0)
// return grid[row][col]+helper(grid,row,col-1);
// else if(row>0&&col==0)
// return grid[row][col]+helper(grid,row-1,col);
// else
// return grid[row][col]+max(helper(grid,row,col-1),helper(grid,row-1,col));
// }
// int maxValue(vector<vector<int>>& grid) {
// int i = grid.size()-1;
// int j = grid[0].size()-1;
// int tmp = 0;
// //int res = helper(grid,0,0,i,j,tmp);
// int res = helper(grid,i,j);
// return res;
// }
//时间复杂度更低的方法:使用一个二维数组记录到达每个格子的最大礼物价值
int maxValue(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
vector<vector<int>> tmp(m,vector<int>(n,0));
// 将第0行和第0列单独拿出来,可以减少判断,缩短时间
tmp[0][0]=grid[0][0];
for(int i=1;i<m;i++)//第0列
tmp[i][0]=tmp[i-1][0]+grid[i][0];
for(int i=1;i<n;i++)//第0行
tmp[0][i]=tmp[0][i-1]+grid[0][i];
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
tmp[i][j]=max(tmp[i-1][j],tmp[i][j-1])+grid[i][j];
}
}
return tmp[m-1][n-1];
}
//再优化空间复杂度的话,就是使用一维数组,只记录上一行的每个格子的最大礼物价值,比较难想到
//当前行当前坐标左边的情况实时更新
// int maxValue(vector<vector<int>>& grid) {
// int r = grid.size(), c = grid[0].size();
// vector<int> dp(c+1,0);//左边多定义一个,用于处理第0列的情况
// for(int i = 0; i < r; i++)
// {
// for(int j = 0; j < c; j++)
// {
// dp[j+1] = max(dp[j],dp[j+1]) + grid[i][j];
//dp[j+1]和grid[i][j]对应 等式右边dp[j]代表左边的 dp[j+1]代表上边的
// }
// }
// return dp[c];
// }
};
面试题48. 最长不含重复字符的子字符串
// Created by zlgybz on 2020/5/1.
//面试题48. 最长不含重复字符的子字符串
//请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
//示例 1:
//输入: "abcabcbb"
//输出: 3
//解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
//示例 2:
//输入: "bbbbb"
//输出: 1
//解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
//示例 3:
//输入: "pwwkew"
//输出: 3
//解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
//请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
//提示:
//s.length <= 40000
class Solution {
public:
//思路:动态规划,滑动窗口,哈希表
//左右指针确定滑动窗口,哈希表存储每个字符最后一次出现的索引的位置
int lengthOfLongestSubstring(string s) {
unordered_map<char,int> m;
int left = 0;
int res = 0;
for(int right=0;right<s.size();right++){
if(m.find(s[right])!=m.end()){//哈希表中有该字符
left = max(left, m[s[right]]+1);//该字符如果在滑动窗口内,则滑动窗口左指针移至上次出现该字符的后一位
m[s[right]]=right;//更新这个字符的索引位置
}
else
m[s[right]]=right;//加入这个字符的索引位置
res = max(res,right-left+1);
}
return res;
}
};
面试题49. 丑数
class Solution {
public:
int nthUglyNumber(int n) {
int a=0,b=0,c=0;//三个索引的位置
vector<int> vec(n,0);
vec[0] = 1;
for(int i=1;i<n;i++){
int n2 = vec[a]*2, n3 = vec[b]*3, n5 = vec[c]*5;
vec[i] = min(min(n2,n3),n5);//取三个的最小值
cout << vec[i] << ' ';
//更新abc的值,是为了使其满足约束条件,前一个乘上2/3/5小于等于vec[i],当前乘上2/3/5之后大于vec[i]
if((vec[i])==n2) a++;
if ((vec[i])==n3) b++;//不能用else if 因为要每个都判断一次,防止重复元素出现。即保证每个索引满足的条件
if ((vec[i])==n5) c++;
}
return vec[n-1];
}
};
面试题50. 第一个只出现一次的字符
// Created by zlgybz on 2020/5/1.
//面试题50. 第一个只出现一次的字符
//在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。
//示例:
//s = "abaccdeff"
//返回 "b"
//s = ""
//返回 " "
//限制:
//0 <= s 的长度 <= 50000
#include <iostream>
#include <unordered_map>
using namespace std;
char firstUniqChar(string s) {
unordered_map<char,int> m; //unordered_map无序,查找更快
for(char i:s)
m[i]++;
for(char i:s){
if(m[i]==1)
return i;
}
return ' ';
}
int main() {
cout << firstUniqChar("leetcode") << endl;
return 0;
}
面试题51. 数组中的逆序对
// Created by zzh on 2020/7/9.
//剑指 Offer 51. 数组中的逆序对
//在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
//输入一个数组,求出这个数组中的逆序对的总数。
//示例 1:
//输入: [7,5,6,4]
//输出: 5
//限制:
//0 <= 数组长度 <= 50000
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
int reversePairs(vector<int>& nums) {
//方法一:暴力搜索 超时
// int cnt = 0;
// for(int i=0;i<nums.size();i++){
// for(int j=i+1;j<nums.size();j++){
// if(nums[i]>nums[j])
// cnt++;
// }
// }
// return cnt;
//方法二:归并排序的过程中记录逆序对的个数
int n = nums.size();
vector<int> tmp(n);
return mergeSort(nums, tmp, 0, n-1);
}
int mergeSort(vector<int>& nums, vector<int>& tmp, int left, int right){
if(left>=right)//递归分到只有一个元素的时候,则直接返回0
return 0;
int mid = left + (right-left)/2;
int inv_count = mergeSort(nums,tmp,left,mid)+mergeSort(nums,tmp,mid+1,right);//先将左右两部分各自的逆序对加起来
//以下是求解将当前左右两部分归并的时候,新发现的逆序对
int i = left, j=mid+1, pos = left;
while(i<=mid && j<=right){//两部分都不为空时
if(nums[i]<=nums[j]){//如果左边第一个小,说明这个数是当前最小的,对逆序对没有贡献
tmp[pos++] = nums[i++];
// inv_count += (j-(mid+1));
}
else{//如果右边第一个小,则右边的这个元素与左边现有的元素都构成逆序对
tmp[pos++] = nums[j++];
inv_count += mid-(i-1);
}
}
while(i<=mid){ //左边剩余的处理完
tmp[pos++] = nums[i++];
}
while(j<=right){ //右边剩余的处理完
tmp[pos++] = nums[j++];
}
copy(tmp.begin()+left, tmp.begin()+right+1, nums.begin()+left); //将tmp排好序的部分拷贝到nums的对应位置中
return inv_count;
}
};
int main(int argc, char **argv) {
vector<int> nums = {7,5,6,4};
auto obj = new Solution;
auto res = obj->reversePairs(nums);
cout << res << endl;
return 0;
}
面试题52. 两个链表的第一个公共节点
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
//思路:一个链表到尾了与另一个链表的头相接。
//如果有相交节点,两个指针碰头的位置就是第一个共同节点,
//如果没有相交,则碰头的位置将是遍历完两个链表时的nullptr
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(!headA || !headB)
return nullptr;
ListNode* tmpA = headA;
ListNode* tmpB = headB;
while(tmpA!=tmpB){
tmpA = tmpA==nullptr ? headB : tmpA->next;
tmpB = tmpB==nullptr ? headA : tmpB->next;
}
return tmpA;
}
};
面试题53 - I. 在排序数组中查找数字
统计一个数字在排序数组中出现的次数。
总结一下写二分查找法的思路:
while(left < right) 在退出循环的时候,一定有 left == right 成立,因此,在循环中,不断排除不是目标元素的区间,以确定下一轮搜索的区间,进而确定边界如何设置,在退出循环的时候,根据情况,需要单独做一次判断;
在循环体里,对 nums[mid] 与 target 的大小关系的判断,可以二分,也可以三分,本题采用三分去判断,只是为了演示怎么设置边界:先得到目标元素可能在哪个区间里,然后再设置边界,这样不容易出错,建议写成注释;可以二分的好处是只要一边判断是正确的,另一边就是其反面,考虑的内容会少一些;
看到边界是 left = mid 的时候,取中间数的计算表达式应该上取整,即在括号里加 1;
编码时应该先写主干逻辑,然后分支逻辑作为私有函数。
class Solution {
public:
int search(vector<int>& nums, int target) {
int cnt = 0;
//方法一:使用STL算法函数1:
//lower_bound()函数,给定有序数组的起点和终点,找到第一个大于等于num的位置索引,其内部使用的是二分法查找
//upper_bound()函数,它返回大于num的第一个元素的索引
//cnt = upper_bound(nums.begin(),nums.end(),target) - lower_bound(nums.begin(),nums.end(),target);
// //方法二:”使用STL算法函数2:
// count : 在序列中统计某个值出现的次数
// count_if : 在序列中统计与某谓词匹配的次数
//cnt = count(nums.begin(),nums.end(),target);
//方法三:手写二分法查找起点终点索引
if(nums.empty())return 0;
int n=nums.size();
int left=0,right=n-1,mid;
int x;
int y;
while(left<right){
mid=(left+right)/2;
if(nums[mid]>=target)right=mid;
else left=mid+1;
}
if(nums[left]!=target)return 0;
x=left;
right=n;
while(left<right){
mid=(left+right)/2;
if(nums[mid]<=target) left=mid+1;
else right=mid;
}
y=left;
cnt = y-x;
return cnt;
}
};
面试题54. 二叉搜索数的第k大节点
给定一棵二叉搜索树,请找出其中第k大的节点。
/**
* 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:
vector<int> vec;
void helper(TreeNode* root){
if(!root)
return;
helper(root->right);
vec.push_back(root->val);
helper(root->left);
}
int kthLargest(TreeNode* root, int k) {
helper(root);
return vec[k-1];
}
};
55-1 二叉树的深度
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
/**
* 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 maxDepth(TreeNode* root) {
if(!root)
return 0;
return max(maxDepth(root->left),maxDepth(root->right)) + 1;
}
};
55-2平衡二叉树
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
/**
* 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 helper(TreeNode* root){
// if(!root)
// return 0;
// return max(helper(root->left),helper(root->right))+1;
// }
// bool isBalanced(TreeNode* root) {
// if(!root)
// return true;
// if(abs(helper(root->left)-helper(root->right))>1) return false;
// return isBalanced(root->left) && isBalanced(root->right);
// }
//方法二:后序遍历,左右中,从下往上判断,没有重复计算
bool helper(TreeNode* root,int& Depth){//使用一个辅助变量Depth用于记录当前节点深度,使用引用使其值可改变和使用
if(!root){
Depth = 0;
return true;
};
int left = 0,right = 0;
if(helper(root->left,left)&&helper(root->right,right)){//如果左右树都满足,则判断当前节点,如果满足则深度加1
if(abs(left-right)>1)
return false;
else{
Depth = max(left,right)+1;
return true;
}
}
return false;//左右树有不满足的
}
bool isBalanced(TreeNode* root) {
if(!root)
return true;
int Depth = 0;
return helper(root,Depth);
}
};
56-1 数组中数字出现的次数
一个整型数组 nums
里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
int tmp = 0;
for(int i:nums){//找到这两个数字异或的值
tmp ^= i;
}
int div = 1;
while((tmp&div)==0)//找到tmp第一个不为0的位,也就是两个数字不用的位
div <<= 1;
int a=0,b=0;
for(int i:nums){//根据那个不同的位将所有数分成两类,然后分别异或
if(i&div)
a ^= i;
else
b ^= i;
}
return {a,b};
}
};
56-2 数组中出现的的次数2
在一个数组 nums
中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
class Solution {
public:
int singleNumber(vector<int>& nums) {
int res=0;
for(int i=0;i<32;i++){
int cnt = 0;
for(int num:nums){
if(num&1<<i)
cnt++;
}
res |= (cnt%3==0?0:1<<i);
}
return res;
}
};
57 和为s的两个数字
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int left=0,right = nums.size()-1;
while(left<right){
int tmp = nums[left]+nums[right];
if(tmp==target)
return {nums[left],nums[right]};
else if(tmp>target)
right--;
else
left++;
}
return {};
}
};
头文件
List.h
#ifndef SWORD_TO_OFFER_LIST_H
#define SWORD_TO_OFFER_LIST_H
#include <vector>
using namespace std;
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
//输入vector,生成链表,返回头指针
ListNode* createList(const vector<int>& list){
auto* res = new ListNode(0);
ListNode* tmp = res;
for(int i : list){
tmp->next = new ListNode(i);
tmp = tmp->next;
}
return res->next;
}
#endif //SWORD_TO_OFFER_LIST_H
BinaryTree.h
// Created by zzh on 2020/4/26.
#ifndef SWORD_TO_OFFER_BINARYTREE_H
#define SWORD_TO_OFFER_BINARYTREE_H
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
explicit TreeNode(int x):val(x),left(nullptr),right(nullptr){}
};
///创建二叉树的三种方法/
//1.先创建节点,然后连接各节点
TreeNode* createTreeNode(int val){
auto res = new TreeNode(val);
return res;
}
void connectTreeNode(TreeNode* root,TreeNode* left, TreeNode* right){
root->left = left;
root->right = right;
}
//2. 可通过int型数组(层序)创建二叉树,(注意这里数组得是满序,所有的空节点都要用-1表示,叶子节点的下一节点不表示)
TreeNode* createTree(const vector<int> &vec){
vector<TreeNode*> vecTree;
for(int i:vec){
if(i==-1) vecTree.push_back(nullptr);
else vecTree.push_back(new TreeNode(i));
}
int j=1;
for(int i=0;j<vecTree.size();i++){
if(vecTree[i]==nullptr) continue;//中间层叶子节点的下一层
vecTree[i]->left = vecTree[j++];
vecTree[i]->right = vecTree[j++];
}
return vecTree[0];
}
//3. 方法二的递归实现方式
void createTreeCore(TreeNode* &root, const vector<int> &vec, int len, int index){
if(index>=len||vec[index]==-1)
return;
root = new TreeNode((vec[index]));
createTreeCore(root->left,vec,len,2*index+1);//画一个二叉树可发现左右节点和根节点编号的规律
createTreeCore(root->right,vec,len,2*index+2);
}
TreeNode* createTree2(const vector<int> &vec){
TreeNode* root = nullptr;
int len = vec.size();
createTreeCore(root, vec, len,0);
return root;
}
///以层序方式输出打印二叉树 使用队列//
void PrintTree(TreeNode* root)
{
queue<TreeNode*> tmp;
if (root == nullptr){
cout << "PringtTree failed: root is nullptr!";
return;
}
else{
tmp.push(root);
}
int cur_level = 1, next_level = 0;//记录每行有多少个节点,用于换行
while(!tmp.empty()){
auto front = tmp.front();
if(front == nullptr){//已经到了叶子节点的下方,打出#号,方便检查
cout << "# ";
tmp.pop();
}
else{
cout << front->val << " ";
tmp.push(front->left);
tmp.push(front->right);
next_level += 2;
tmp.pop();
}
cur_level--;
if(cur_level==0){
cur_level = next_level;
next_level = 0;
cout << endl;
}
}
}
#endif //SWORD_TO_OFFER_BINARYTREE_H