【剑指offer-第二版】习题感悟(一刷)
- 1. 面试流程
- 2. 面试的基础知识
- 2.1 C++基础知识
- 面试题1:赋值运算符函数
- 面试题2:实现单例模式
- 2.2 数据结构
- 面试题3:数组中重复的数字
- 面试题4:二维数组中查找
- 面试题5:替换字符串中的空格
- 面试题6:从尾到头打印链表
- 面试题7:重建二叉树
- 面试题8:二叉树的下一个节点
- 面试题9:两个栈实现队列or两个队列实现栈
- 2.3 算法和数据操作
- 面试题10:斐波那契数列
- 快速排序算法
- 面试题11:旋转数组的最小数字
- 面试题12:矩阵中的路径
- 面试题13:机器人的运动范围
- 面试题14:剪绳子
- 面试题15:二进制中1的个数
- 3. 高质量的代码
- 面试题16:数值的整数次方
- 面试题17:删除链表的结点
- 面试题18:调整数组顺序使奇数位于偶数前面
- 面试题19:链表中倒数第k个节点
- 面试题23:链表中环的入口节点
- 面试题24:反转链表
- 面试题25:合并两个排序的链表
- 面试题25.1:合并k个已排序的链表
- 面试题26:树的子结构
- 面试题27:二叉树的镜像
- 面试题28:对称的二叉树
- 面试题29:顺时针打印矩阵
- 面试题30:包含min函数的栈
- 面试题31:栈的压入、弹出序列
- 面试题32:从上到下打印二叉树
- 面试题33:二叉搜索树的后序遍历序列
- 面试题34:二叉树中和为某一值的路径
- 面试题35:复杂链表的复制
- 面试题36:二叉搜索树与双向链表
- 面试题37:在排序数组中查找数字
- 面试题38: 0~n-1中缺失的数字
- 面试题39:二叉搜索树的第k大节点
- 面试题40:二叉树的深度
- 面试题41:判断平衡二叉树
- 面试题42:数组中数字出现的次数
- 面试题43:数组中唯一出现一次的数字
- 面试题44:和为s的两个数
- 面试题45:和为s的连续正整数序列
- 面试题46:翻转单词顺序
- 面试题47:左旋转字符串
- 面试题48:滑动窗口的最大值
- 面试题49:队列中的最大值
- 面试题60:n个骰子的点数
- 面试题61:扑克牌中的顺子
- 面试题62:股票的最大利润
- 面试题63:求解1+2+...+n
- 面试题64:不用加减乘除做加法
- 面试题65:树中两个节点的最低公共祖先
- 面试题66:二叉树的公共祖先
- 面试题67:删掉一个元素以后全为 1 的最长子数组
- 面试题68:分割回文串(阿里实习笔试)
1. 面试流程
一般分为三轮面试:2-3轮技术面,1轮HR面
每一轮技术面试分为3个环节:行为面试,技术面试,提问环节
- 行为面试:自我介绍,按照简历上内容进行项目的提问;
- 技术面试:编程语言,数据结构与算法等基础知识,手撕代码的能力等;
- 提问环节:推荐提问和应聘职位相关的问题,或者项目团队氛围怎么样等等。
2. 面试的基础知识
2.1 C++基础知识
面试题1:赋值运算符函数
class CMyString
{private:char* m_pData;public:CMyString(const char* pData = nullptr);CMyString(const CMyString& str);~CMyString(void);CMyString& operator=(const CMyString& str){if(this != &str){// 调用拷贝构造函数,作用域之外直接析构释放内存CMyString strTemp(str);// 交换临时变量char* pTemp = strTemp.m_pData;strTemp.m_pData = m_pData;m_pData = pTemp;}return *this; }
};CMyString::CMyString(const char* pData)
{if(pData == nullptr){m_pData = new char[1];m_pData ='\0';}else{m_pData = new char[strlen(pData)+1];strcpy_s(m_pData, strlen(pData)+1, pData);}
}
面试题2:实现单例模式
单例模式:设计一个类,只能生成该类的一个实例。
#include <iostream>
#include <mutex>
using namespace std;class CSingleton{private:CSingleton(){}static CSingleton* m_pInstance;static mutex mux_;public:static CSingleton *GetInstance(){if(m_pInstance == nullptr){lock_guard<mutex> lock(mux_);m_pInatance = new CSingleton();}return m_pInstance;}
};CSingleton* CSingleton::m_pInstance = nullptr;
2.2 数据结构
面试题3:数组中重复的数字
题目1:找出数组中重复的数字
// 解法1:使用hash表
bool duplicate(int number[], int length, int* duplication)
{if(number == nullptr || length <=0) return false;int * hashTable = new int[length]();for(int i=0; i<length; i++){if(hashTable[number[i]]){*duplication = number[i];return true;}else{hashTable[number[i]] = 1;}}delete[] hashTable;return false;
}
题目2:不修改数组找出重复的数字
// 哈希表求解
int duplicate(int number[], int length)
{if(number == nullptr || length<=0)return -1;int hashTable = new int[length]();for(int i=0; i<length; i++){if(hashTable[number[i]])return i;elsehashTable[number[i]] = 1;}delete[] hashTable;return -1;
}
面试题4:二维数组中查找
// 思路:按照从右上角查找比较,按照比较结果删除一列或者一行
bool findMatrix(int* matrix, int rows, int cols, int number)
{if(matrix != nullptr && cols>0 && rows>0){int row = 0;int col = cols-1;while(row<rows && col>=0){ if(matrix[row*cols+col] == number)return true;else if(matrix[row*cols+col] > number)col--;elserow++;}}return false;
}
面试题5:替换字符串中的空格
// 思路:统计字符串的长度和空格个数,然后从后向前检索替换,注意临界条件
void ReplaceBlank(char string[], int length)
{if (string == nullptr || length <= 0)return;int origenLength = 0;int blankLength = 0;int i = 0;while (string[i] != '\0'){origenLength++;if (string[i] == ' ')blankLength++;++i;}int newLength = origenLength + blankLength * 2;if (newLength > length)return;while (newLength > origenLength || origenLength >= 0){if (string[origenLength] == ' '){string[newLength--] = '0';string[newLength--] = '2';string[newLength--] = '%';}elsestring[newLength--] = string[origenLength];origenLength--;}
}// 使用string
string ReplaceBlank(string s)
{string ss;for(int i=0; i<s.size(); i++){s[i]==' ' ? ss+="%20" : ss+=s[i];}return ss;
}
面试题6:从尾到头打印链表
// 思路:使用栈存储,实现先进后出
void ListReverse(ListNode* head)
{stack<ListNode*> stk;ListNode* node = head;while(node != nullptr){stk.push(node);node = node->next;}while(!stk.empty()){cout<<stk.top()->value<<" ";stk.pop();}
}
面试题7:重建二叉树
// 根据前序遍历,中序遍历重建二叉树
// 思路:先根据前序遍历找到根结点,在利用根结点信息在中序遍历中查找根结点序号。最后递归嗲用构建左右子树
class TreeNode
{
public:int m_value;TreeNode* left;TreeNode* right;TreeNode(int x) : m_value(x), left(nullptr), right(nullptr) { }
};TreeNode* BuildTree1(vector<int> preorder, vector<int> inorder)
{if (preorder.size() != inorder.size() || preorder.size() == 0)return nullptr;int root = preorder[0];TreeNode* node = new TreeNode(root);int root_idx = -1;for (int i = 0; i < inorder.size(); ++i){if (inorder[i] == root){root_idx = i;break;}}if (root_idx == -1)return nullptr;int leftsize = root_idx;int rightsize = inorder.size() - root_idx - 1;node->left = BuildTree(vector<int>(preorder.begin() + 1, preorder.begin() + leftsize + 1), vector<int>(inorder.begin(), inorder.begin() + leftsize));node->right = BuildTree(vector<int>(preorder.end() - rightsize, preorder.end()), vector<int>(inorder.end() - rightsize, inorder.end()));return node;}
面试题8:二叉树的下一个节点
// 思路:可以分为两种情况
// 情况1:该节点存在右子树,则下一节点就是右子树的最左子节点
// 情况2:该节点不存在右子树,可根据该节点的父节点的左子结点是否与本身相同判断,然后确定下一个子节点class TreeLinkNode
{int m_value;TreeLinkNode* left;TreeLinkNode* right;TreeLinkNode* parent;TreeLinkNode(int x) : m_value(x), left(nullptr), right(nullptr), parent(nullptr){}
};TreeLinkNode* GetNextNode(TreeLinkNode* pNode)
{if(pNode == nullptr) return nullptr;TreeLinkNode* pNext = nullptr;if(pNode->righe != nullptr){TreeLinkNode* pRight = pNode->right;whiel(pRight->left != nullptr)pRight = pRight->left;pNext = pRight;}else{TreeLinkNode* pParent = pNode;while(pParent->parent != nullptr){if(pParent->parent->left == pParent){pNext = pParent->parent;break;}pParent = pParent->parent;}}return pNext;
}
面试题9:两个栈实现队列or两个队列实现栈
// 思路:利用一个栈专门用于出队列,另一个栈用于入队列class cQueue
{
public:cQueue() {}~cQueue() {}// 实现入队列void appendTail(const int& node){stack1.push(node);}// 实现出队列int deleteHead(){//if(stack.empty() && stack2.empty())//return ;if(stack2.empty()){while(!stack1.empty()){stack2.push(stack1.top());stack1.pop();}}int n = stack2.top();stack2.pop();return n;}private:stack<int> stack1;stack<int> stack2;
};// 两个队列实现栈
// 思路:保证其中一个栈为空
class cStack
{
public:cStack() {}~cStack() {}void appendHead(const int& node){if (!que1.empty()){que1.push(node);}else{que2.push(node);}}int deteleNode(){//if (que1.empty() && que2.empty())//return ;int node = 0;if (!que1.empty()){int num = que1.size();while (num > 1){que2.push(que1.front());que1.pop();--num;}node = que1.front();que1.pop();}else{int num = que2.size();while (num > 1){que1.push(que2.front());que2.pop();--num;}node = que2.front();que2.pop();}return node;}private:queue<int> que1;queue<int> que2;
};
2.3 算法和数据操作
面试题10:斐波那契数列
// 思路:采用递归的方式调用效率低,原因在于存在重复的调用
// 变种,小青蛙跳台阶(根据跳的方式累加)
int Fn(int n)
{if(n<=1) return n;int result = 0; // 保存结果 f(n)int pre = 0; // f(n-2)int cur = 1; // f(n-1)for(int i=2; i<=n; i++){// 计算f(n-1)+f(n-2)的结果result = pre + cur;pre = cur; // 向下传递cur = result; // 向下传递}return result;
}
快速排序算法
//思路:首先找到系列中的基准(一般选第一个);然后找到起始和终止结点;之后从左到右和从右到左遍历。最后递归实现左右边的排序
void QuickSort(int* data, int start, int end)
{if(start < end){int std = data[start];int low = start;int high = end;while(low<high){while(low<high && data[high]>std)--high;data[low] = data[high];while(low<high && data[low]<=std)++low;data[high] = data[low];}data[low] = std;QuickSort(data, start, low-1);QuickSort(data, low+1, end);}elsereturn ;
}
面试题11:旋转数组的最小数字
//思路:需要根据序列确定不同情况
// 1.如果序列中包含相同的数字,则需要找该序列的最小值
// 2. 如果序列式正常递增,则需要利用二分法查找//查找最小数字
int minInorder(vector<int> vec)
{int temp = vec[0];for(auto& v:vec){if(v<temp)temp = v;}return temp;
}int MinOrder(vector<int> rotateVec)
{int size = rotateVec.size();if(size == 0) return 0;if(size == 1 && rotareVec[0]<rotateVec[size-1]) return rotateVec[0];int low = 0;int high = size-1;while((high-low)>1){int mid = (high+low)>>1;if(rotateVec[mid]==rotateVec[low] && rotateVec[mid]==rotateVec[high])return minInorder(rotateVec);if(rotateVec[mid] <= rotateVec[high])high = mid;else if(rotateVec[mid] >= rotateVec[low])low = mid;}return rotateVec[high];
}
面试题12:矩阵中的路径
// 思路:使用回溯的方法实现// 1. 主题
bool hasPath(string matrix, int cols, int rows, string str)
{if(matrix.size() == 0 || cols<0 || row<0 || str.size() == 0)return false;bool* visited = new bool[rows*cols];memset(visited, 0, rows*cols);int pathLength = 0;for(int row=0; row<rows; row++){for(int col=0; col<cols; col++){// 调用回溯算法if(hasCore(matrix, cols, rows, col, row, str, pathLength, visited)){delete[] visited;return true;}}}delete[] visited;return false;
}// 2. 回溯法核心
bool hasCore(string matrix, int cols, int rows, int col, int row, string str, int& pathLength, bool* visited)
{if(str[pathLength] == '\0')return true;bool haspath = false;if(row>0 && col>0 && col<cols && row<rows && str[pathLength] == matrix[col+cols*row] && !visited[col+cols*row]){pathLength--;visited[col+cols*row] = true;haspath = hasCore(matrix, cols, rows, col-1, row, str, pathLength, visited)|| hasCore(matrix, cols, rows, col+1, row, str, pathLength, visited)|| hasCore(matrix, cols, rows, col, row-1, str, pathLength, visited)|| hasCore(matrix, cols, rows, col, row+1, str, pathLength, visited);if(!haspath){pathLength--;visited[col+cols*row] = false;}}return haspath;
}
面试题13:机器人的运动范围
// 思路:保证机器人满足一下三个条件,表示可以运动。依旧使用回溯法
// 1. 机器人的下一个位置是边界处
// 2. 机器人已经走过该路径
// 3. 数位之和大于阈值
int getNumber(int data)
{int sum = 0;while(data!=0){sum += data%10;data = data /10;}return sum;
}int CountRobotCore(int threshold, int cols, int rows, int col, int row, bool* visited)
{if(threshold<(getNumber(col)+getNumber(row)) || col<0 || row<0 ||col>=cols || row>=rows || visited[col+cols*row])return 0;int res = 1;visited[col+cols*row] = true;res + = CountRobotCore(threshold, cols, rows, col-1, row, visited)+ CountRobotCore(threshold, cols, rows, col+1, row, visited)+ CountRobotCore(threshold, cols, rows, col, row-1, visited)+ CountRobotCore(threshold, cols, rows, col, row+1, visited);return res;
}int CountRobot(int threshold, int rows, int cols)
{if(rows<0 || cols<0 || threshold<0)return 0;bool* visited = new bool[cols*rows];memset(visited,0,rows*cols);int result = CountRobotCore(threshold, cols, rows, 0, 0, visited);delete[] visited;return res;}
面试题14:剪绳子
// 思路:采用动态规划(DP)或者贪心算法// 1. 动态规划
int maxDPCut(int length)
{// 由于最少必须减一次if(length<2) return 0;if(length == 2) return 1;if(length == 3) return 3;// 当绳子长度大于等于4时,不减绳子的收益大于剪绳子,因此需要重新给小于4的赋值vector<int> product{0,1,2,3};int max = 0;for(int i=4; i<=length; i++){max = 0;for(int j=1; j<=i/2; j++) // 分为两份,防止重复计算{int products = product[j]*product[i-j];if(max<products)max = products;}product.push_back(max);}return product[length];
}
面试题15:二进制中1的个数
总结:
- 位运算包含5种运算:与、或、异或、左移、右移;
- 左移:左边丢弃,右边补0;右移:右边丢弃,左边根据是否具有符号确定补0或1;
- 尽量使用移位操作替代2的整数此房的乘除法,使用位与运算判断一个数的奇偶性(n&1);
- 把一个整数减一后再和原来整数做位与运算,得到的结果相当于把原来整数的二进制表示中最右边的1变成0;
//思路:可以采用自身减一与自身与操作来消除1,此操作便可以用来记录1的个数int NumberOf1(int n)
{int count = 0;while(n){count++;n = n & (n-1);}return count;
}// 输入两个整数m,n,计算需要改变m的二进制表示中的多少位才能得到n.
int getNumberof1(int m, int n)
{int count = 0;int data = m^n; // 使用异或将两者转换为不同位为1, 接下来统计1的个数while(data){count++;data = data & (data-1);}return count;
}
3. 高质量的代码
面试题16:数值的整数次方
// 思路:需要考虑底数为0,指数为负数的情况double Power(double base, int exponent)
{if(base == 0.0 && exponent<0) // 底数为0且指数为负数,直接返回0.0;return 0.0;if(exponent== 0)return 1.0;int absExp = exponent <0 ? -exponent : exponent;double result = 1.0;for(int i=0; i<absExp; i++){result *= base;}if(exponent <0)result = 1.0/result ;return exponent <0 ? 1.0/result : result;
}// 递归算法
double pow(double base, int exps)
{if(exps == 0) return 1.0;double res = pow(base, exps>>1);if(exps&1 == 1)return res * res * base;else return res * res;
}double Power(double base, int exponent)
{if(base == 0.0) return 0.0;int AbsExp = exponent < 0 ? -exponent : exponent ;return exponent < 0 ? 1.0/pow(base, AbsExp) : pow(base, AbsExp);
}
面试题17:删除链表的结点
// 思路:判断头结点是否是要删除的结点,如果是,则返回下一个节点;否则判断下一个节点是要要删除,以此类推
class ListNode
{
public:int val;ListNode* next;ListNode(int v):val(v),next(nullptr) { }
};ListNode* deleteNode(ListNode* head, int val)
{if(head->val == val) return head->next;ListNode* node = head;whiel(node->next != nullptr){if(node->next->val == val){node->next = node->next->next;return head;}node = node->next;}return head;
}
// 思路: 删除链表中重复的数字.
// 首先创建链表,该链表的下一个节点指向头指针,并创建pre与cur用于遍历和删除节点
ListNode* deleteDupli(ListNode* head)
{if(head==nullptr) return head;ListNode* dyNode = new ListNode(-1);dyNode->next = head;ListNode* pre = dyNode;ListNode* cur = head;while(cur){if(cur->next && cur->next->val == cur->val) // 如果遇到重复节点{while(cur->next && cur->next->val == cur->val) // 遍历跳过cur= cur->next;pre->next = cur->next; // 删除重复的结点}else // 如果没遇到重复结点{pre = cur; // 移动前驱结点}cur = cur->next; // 不管有没有重复节点,都移动cur}return dyNode->next; //返回链表的下一个节点(不包括头结点)
}
面试题18:调整数组顺序使奇数位于偶数前面
// 思路:使用双指针遍历,左边指针如果是奇数,则叠加;右边指针是偶数,则叠加
vector<int> recorder(vector<int>& vec)
{int left = 0;int right = vec.size()-1;while(left<right){if((vec[left]&1) == 1){left++;continue;}if((vec[right]&1) == 0){right--;continue;}swap(vec[left++], vec[right--]);}return vec;
}
面试题19:链表中倒数第k个节点
// 思路:使用两个指针,保证快指针移动k个节点,慢指针在快指针移动k个节点后开始移动,直到快指针为空指针
ListNode* FindKnode(ListNode* head, int k)
{if(head == nullptr || k<=0){return nullptr;}ListNode* pre = head;ListNode* cur = head;for(int i=0; i<k; i++){if(cur!=nullptr){cur = cur->next;}else{return nullptr; // 说明k大于链表长度}}while(cur!=nullptr){cur = cur->next;pre = pre->next;}return pre;
}
// 快慢指针遍历
ListNode* midNode(ListNode* head)
{if(head == nullptr) return nullptr;ListNode* fast = head;ListNode* slow= head;while(fast && fast->next){fast = fast->next->next;slow= slow->next;} return sold;
}
面试题23:链表中环的入口节点
// 思路:快指针路径是慢指针路径的2倍,使用快慢指针找到相遇节点,然后从头开始遍历链表,直到和slow相遇,返回慢指针节点
ListNode* EntryLoop(ListNode* head)
{if(head == nullptr) return nullptr;ListNode* fast=head;ListNode* slow=head;while(fast && fast->next){fast = fast->next->next;slow = slow->next;if(fast==slow){ListNode* node = head;while(node != slow) // 两者路径长度相同 (a+b+c+b) = 2(a+b) => a = c{node = node->next;slow = slow->next;}return slow; // 找到环入口即返回}}return nullptr; // 表示没有环
}
面试题24:反转链表
// 思路:使用三个指针进行保存,分别为当前指针、前一个指针、后一个指针
ListNode* reverseList(ListNode* head)
{ListNode* cur = head;ListNode* pre = nullptr;ListNode* next = nullptr;while(cur != nullptr){next = cur->next;cur->next = pre;pre = cur;cur = next;}return pre;
}
面试题25:合并两个排序的链表
// 思路:采用非递归的方式,按照大小顺序进行递增
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2)
{ListNode* node = new ListNode(-1);ListNode* retNode = node;while(l1 && l2){if(l1->val > l2->val){node->next = l2;l2 = l2->next;}else{node->next = l1;l1 = l1->next;}node = node->next;}if(l2 == nullptr) node->next = l1;if(l1 == nullptr) node->next = l2;return retNode->next;
}
面试题25.1:合并k个已排序的链表
// 思路:属于上一个的变种。只需要遍历即可
ListNode* mergeKLists(vector<ListNode*> &lists)
{int len = lists.size();if(len == 0) return nullptr;ListNode* head = lists[0];for(int i=1; i<len; i++){}
}ListNode* twoLists(ListNode* l1, ListNode* l2)
{ListNode* node = new ListNode(-1);ListNode* n = node;while(l1 && l2){ if(l1->val > l2->val){node->next = l2;l2 = l2->next;}else{node->next = l1;l1 = l1->next;}node = node->next;}node->next = (l1==nullptr)? l2 : l1;return n->next;
}
面试题26:树的子结构
// 思路:使用递归的方式,两种递归,1是递归遍历寻找与子树根结点相同;2是找到与子树根结点相同后,开始遍历比较两个子树,递归遍历
class TreeNode
{int val;TreeNode* left;TreeNode* right;TreeNode(int v) : val(v), left(nullptr), right(nullptr) { }
};
// 主函数,用于递归查找与根结点是否相同
bool SubTree(TreeNode* A, TreeNode* B)
{if(A==nullptr || B==nullptr) return false;bool SubFlag = false;if(A->val == B->val) SubFlag = AhaveB(A, B);if(!SubFlag) SubFlag = SubTree(A->left, B);if(!SubFlag) SubFlag = SubTree(A->right, B);return SubFlag;
}// 子函数:用于递归比较与子树是否相同
bool AhaveB(TreeNode* A, TreeNode* B)
{if(B==nullptr) return true;if(A==nullptr) return false;if(A->val != B->val) return false;return AhaveB( A->left, B->left) && AhaveB( A->right, B->right);
}
面试题27:二叉树的镜像
// 思路:递归遍历,先交换左右子树的结点,然后进行左右子树递归
TreeNode* mirroeTree(TreeNode* root)
{if(root == nullptr) return nullptr; // 递归结束条件TreeNode* temp = root->left; // 交换左右子树root->left = root->right;root->right = temp;mirrorTree(root->left); // 递归左子树mirrorTree(root->right); // 递归左子树return root;
}
面试题28:对称的二叉树
// 思路:判断二叉树是否为对称的,即左子树的左 == 右子树的右, 左子树的右 == 右子树的左
// 主函数
bool isSymmetric(TreeNode* root)
{if(root == nullptr) return true;return Mirror(root->left, root->right);
}// 子函数
bool Mirror(TreeNode* left, TreeNode* right)
{// 先判断结点为空的情况if(left==nullptr && right==nullptr) return true;if(left==nullptr || right==nullptr) return false;// 判断结点不为空时,结点值不同if(left->val != right->val) return false;bool outSide = Mirror(left->left, right->right);bool inSide = Mirror(left->right, right->left);return outSide && inSide;
}
面试题29:顺时针打印矩阵
// 思路:首先判断矩阵是否为空,然后在开始打印,按照:从左到右,从上到下,从右到左,从下到上的顺序vector<int> spiralOrder(vector<vector<int>>& matrix) {if(matrix.size()==0 || matrix[0].size()==0) return {};int rows = matrix.size();int cols = matrix[0].size();vector<int> result;int row = 0, col = -1;while(rows>0 && cols>0){// 从左到右for(int i=0; i<cols; i++){col++;result.push_back(matrix[row][col]);}rows--;if(rows==0 || cols==0) break; // 一旦存在行或列为空,则需要跳出来// 从上到下for(int i=0; i<rows; i++){row++;result.push_back(matrix[row][col]);}cols--;if(rows==0 || cols==0) break;// 从右到左for(int i=0; i<cols; i++){col--;result.push_back(matrix[row][col]);}rows--;if(rows==0 || cols==0) break;// 从下到上for(int i=0; i<rows; i++){row--;result.push_back(matrix[row][col]);}cols--;if(rows==0 || cols==0) break;}return result;}
面试题30:包含min函数的栈
// 思路:添加一个辅助栈,用来保存每添加一个数据时的最小值
class MinStack {
public:/** initialize your data structure here. */stack<int> m_data, m_min;MinStack() { }void push(int x) {m_data.push(x);if(m_min.empty()){m_min.push(x);} else{int temp = m_min.top();if(temp > x) m_min.push(x);else m_min.push(temp);}}void pop() {m_data.pop();m_min.pop();}int top() {return m_data.top();}int min() {return m_min.top();}
};
面试题31:栈的压入、弹出序列
// 思路:添加一个辅助占, 每次想辅助栈中添加元素,都需要用栈顶数据跟弹出序列的数据进行比较,如果相等,栈就需要出栈顶;同时弹出序列向后移动。压入和弹出序列相等的条件是,辅助栈为空。
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {bool result = false;if(pushed.size() != popped.size()) return result;stack<int> m_data;int idx = 0;for(auto& vec : pushed){m_data.push(vec);while(!m_data.empty() && m_data.top() == popped[idx]){m_data.pop();idx++;}}if(m_data.empty()) result = true;return result; }
面试题32:从上到下打印二叉树
// 思路, 使用队列想入先出的方式,不断加入节点
vector<int> levelOrder(TreeNode* root)
{if(root == nullptr) return {};queue<TreeNode*> que;que.push(root);vector<int> result;while(!que.empty()){TreeNode* node = que.front();que.pop();result.push_back(node->val);if(node->left != nullptr) que.push(node->left);if(node->right != nullptr) que.push(node->right);}return result;
}// 思路:打印二叉树,每层打印到一行
vector<vector<int>> levelOrder(TreeNode* root)
{if(root == nullptr) return {};queue<TreeNode*> que;que.push(root);vector<vector<int>> result;while(!que.empty()){vector<int> temp;int sz = que.size();while(sz--){TreeNode* node = que.front();temp.push_back(node->val);que.pop();if(node->left != nullptr) que.push(node->left);if(node->right != nullptr) que.push(node->right);}result.push_back(temp);}return result;
}// 思路:之字型打印二叉树; 需要注意偶数层需要反转,奇数层不变
vector<vector<int>> levelOrder(TreeNode* root) {if(root == nullptr) return {};queue<TreeNode*> que;que.push(root);int level = 0; // 用于记录层数vector<vector<int>> result;while(!que.empty()){int sz = que.size();vector<int> temp;level++;while(sz--){TreeNode* node = que.front();temp.push_back(node->val);que.pop();if(node->left != nullptr) que.push(node->left);if(node->right != nullptr) que.push(node->right);} if((level & 1) == 0) reserve(temp); // 偶数需要反转result.push_back(temp);}return result;
}void reserve(vector<int>& vec)
{int sz = vec.size();for(int i=0; i<sz/2; i++){int temp = vec[i];vec[i] = vec[sz-i-1];vec[sz-i-1] = temp;}
}
面试题33:二叉搜索树的后序遍历序列
// 思路:判断二叉搜索树(二叉排序树)是否是后序遍历(左右根)// 主函数
bool versfyPostorder(vector<int>& postorder)
{if(postorder.size() == 0) return true;return dfsOrder(postorder, 0, postorder.size()-1);
}bool dfsOrder(vector<int> vec, int left, int right)
{if(left >= right) return true;int root = vec[right];int i = left;for(; i<right; i++) // 找到比根结点大的第一个结点位置{if(vec[i] > root)break;}for(int j=i; j<right; j++) // 如果右子树存在比根结点小的数,则说明该序列不是后续遍历序列{if(vec[j] < root)return false;}return defOrder(vec, left, i-1) && defOrder(vec, i, right-1); // 递归遍历左右子树
}
面试题34:二叉树中和为某一值的路径
// 思路:使用前序递归的形式遍历二叉树,保存路径,然后比较路径之和。最后将所有路径上的数字保存vector<vector<int>> FindPath(TreeNode* root, int sum)
{if(root == nullptr) return {};vector<int> path;vector<vector<int>> result;findPath(root, sum, path, result);return result;
}void findPath(TreeNode* root, int sum, vector<int>& path, vector<vector<int>>& res)
{// 1. 对根结点保存path.push_back(root->val);// 2. 判断当前是否满足和要求if(root->val == sum && root->left==nullptr && root->right==nullptr){res.push_back(path);}// 3. 不满足和条件,就开始遍历左子树和右子树(需要注意,此时sum改变,需要减掉当前节点的值)if(root->left) findPath(root->left, sum-root->val, path, res);if(root->right) findPath(root->left, sum-root->val, path, res);path.pop_back();
}
面试题35:复杂链表的复制
// 思路:可以使用哈希表进行拷贝复制
class Node
{
public:int val;Node* next;Node* random;Node(int _val) {val = _val; next = NULL; random = NULL;}
};Node* copyRandomList(Node* head)
{if(head == nullptr) return nullptr;// 1. 创建哈希表,并遍历链表unordered_map<Node*, Node*> u_map;Node* cur = head;while(curr != nullptr){u_map[cur] = new Node(cur->val);cur = cur->next;}// 2. map映射复制cur = head;while(cur != nullptr){u_map[cur]->next = u_map[cur->next];u_map[cur]->random = u_map[cur->random];cur = cur->next;}return u_map[head];
}
面试题36:二叉搜索树与双向链表
// 思路:由于是二叉搜索树,因此需要采用中序遍历。
class Solution{
public:Node* treeToDoublyList(Node* root){if(root == nullptr) return nullptr;inOrder(root);head->left = pre;pre->right = head;return head;}private:Node* pre;Node* head;void inOrder(Node* cur){if(cur == nullptr) return ;// 中序遍历inOrder(cur->left);if(pre != nullptr) pre->right = cur;else head = cur;cur->left = pre;pre = cur;inOrder(cur->right); }
};
面试题37:在排序数组中查找数字
// 思路:可用哈希表,或者 二分法查找。 其中哈希表较为占用内存int search(vector<int>& nums, int target)
{return BinaryLeft(nums, target+1)-BinaryLeft(nums, target);
}int BinaryLeft(vector<int>& nums, int target)
{int left = 0;int right = nums.size()-1;int mid = 0;while(left<=right){mid = (left+right)>>1;if(nums[mid] < target){left = mid+1;}else{right = mid-1;}}return left;
}
面试题38: 0~n-1中缺失的数字
// 思路:使用二分法查找,如果相等则在右边,否则在左边void missingNumber(vector<int>& nums)
{return BinaryNum(nums);
}int BinaryNum(vector<int> nums)
{int left = 0;int right = nums.size()-1;int mid = 0;while(left<=right){mid = (left+right)>>1;if(nums[mid] == mid)left = mid + 1;elseright = mid - 1;}return left;
}
面试题39:二叉搜索树的第k大节点
// 思路: 使用中序遍历可以得到二叉搜索树按照从小到大的排序,而我们需要从大到小的排序,因此需要按照从右到左int kthLargest(TreeNode* root, int k)
{if(root == nullptr || k==0) return 0;vector<int> result;mid_dfs(root, result);return result[k-1];}void mid_dfs(TreeNode* root, vector<int>& vec)
{if(root == nullptr) return ;mid_dfs(root->right, vec);vec.push_back(root->val);mid_dfs(root->left, vec);
}
面试题40:二叉树的深度
// 思路: 采用层序遍历,借助queueint maxDepth(TreeNode* root)
{if(root == nullptr) return 0;queue<TreeNode*> que;que.push(root);int depth=0;while(!que.empty()){++depth;int size = que.size();while(size--){TreeNode* node = que.front();que.pop();if(node->left) que.push(node->left);if(node->right) que.push(node->right);}}return depth;
}
面试题41:判断平衡二叉树
// 思路:如果时平衡二叉树,则说明任意节点的左、右子树的高度差不超过1.可以使用后序遍历。bool res = true;
bool idBalance(TreeNode* root)
{dfs(root);return res;
}int dfs(TreeNode* root)
{if(root == nullptr) return 0;int left = dfs(root->left);int right = dfs(root->right);if(abs(left-right)>1) res = false;return max(left, right)+1;
}
面试题42:数组中数字出现的次数
// 思路:可以使用异或实现检测数组中出现不重复的数字,如果一个数组中只有一个不重复的数字,仅需要遍历一遍即可。vector<int> singleNumbers(vector<int>& nums)
{int first=0, second=0, mid=1;for(auto &n : nums) // 遍历一遍数组可以找到两个不重复数字的组合{first ^= n;}while( (first&mid) == 0) // 找到两个数字第一个不同的位,利用该位将数组分为两份,然后分别查找mid <<=1;first = 0;for(auto& n : nums){if(n&mid) first ^= n;else second ^= n; }return {first, second};
}
面试题43:数组中唯一出现一次的数字
// 思路:可以使用哈希表int singleNumber(vector<int>& nums)
{int res = 0;unordered_map<int, int> map_n;for(auto &n:nums) map_n[n]++;for(auto &n:nums){if(map_n[n] == 1){res = n;break;}}return res;
}
面试题44:和为s的两个数
// 思路:使用双指针,前后各一个指针。循环向前逼近
vector<int> twoSum(vector<int>& nums, int target)
{int begin = 0;int end = nums.size()-1;while(end > begin){int res = nums[begin]+nums[end];if(res > target) end--;else if(res < target) begin++;else return vector<int>{nums[begins], nums[end]};}return vector<int>();
}
面试题45:和为s的连续正整数序列
// 思路:可以使用滑动窗口算法,左闭右开。主要判断条件时 左边界小于目标值的一半vector<vector<int>> findContinuousSequence(int target)
{int left = 1; // 左边界int right = 1; // 右边界int sum = 0; // 中间值vector<vector<int>> result;while(left <= target/2){if(sum < target){sum += right;right++;}else if(sum > target){sum -= left;left++;}else{vector<int> res;for(int i=left; i<right; i++){res.push_back(i);}result.push_back(res);sum -= left;left++;}}return result;
}
面试题46:翻转单词顺序
// 思路:采用反转单词顺序,同时需要按照:完全反转句子顺序,去除多余空格(多于2个空格),完全翻转后在对每一个单词反转。string reverseWords(string s)
{int begin = 0; // 起始反转位置int end = s.size()-1; // 终止反转点reserve(s, begin, end); // 反转整个句子begin = 0;end = 0;while(s[end] == '\0') // 如果没有到字符串最后一位{if(s[begin] == ' '){s.erase(begin,1); // 移除多余的空格}else if(s[end] == ' '){reserve(s, begin, end-1);end++;begin = end;}else{end++;}}if(end>1 && s[end-1] == ' ') s.erase(end-1,1); // 移除最后一个空格reserve(s, begin, end-1); // 少翻转一次,需要补上(最后一个单词没有反转)return s;}// 反转单词顺序
void reserve(string &s, int begin, int end)
{int mid = (begin+end)>>1;while(begin <= mid){char c = s[begin];s[begin] = s[end];s[end] = c;begin++;end--;}
}
面试题47:左旋转字符串
// 思路:1.可以使用反转字符串的思路,即先反转前半部分字符串,然后反转后半部分字符串,最后反转整个字符串。2. 或者直接使用字符串拼接,前后拼接,取其中从n到len的字符串// 1. 反转字符串思路
string reserveLeftWords(string s, int n)
{ if(s.size() <=1 )return s;int end = s.size()-1;reserve(s, 0, n-1); // 反转前半部分reserve(s, n, end); // 反转后半部分reserve(s, 0, end); // 反转全部return s;
}
void reserve(string &s, int begin, int end)
{ int mid = (begin+end)>>1;while(begin <= mid){ char c = s[begin];s[begin] = s[end];s[end] = c;begin++;end--;}
}// 2. 拼接方法
string reserveLeftWords(string s, int n)
{int len = s.size();s += s; // 拼接字符串return s.substr(n, len); // 取出(n-len)字符串
}
面试题48:滑动窗口的最大值
// 思路:可以使用双端队列实现查找,具体可以看下面代码
vector<int> maxSlidingWindow(vector<int>& nums, int k)
{vector<int> result;deque<int> que;for(int i = 0; i<nums.size(); i++){// 查找滑窗内的最大值,放到队头while( !que.empty() && nums[i] > num[que.back()])que.pop_back();// 判断队头的索引是否超出窗口左边界if( !que.empty() && que.front() < i-k+1)que.pop_front();que.push_back(i);if( i >= k-1) result.push_back(nums[que.front()]);}return result;
}
面试题49:队列中的最大值
// 思路:使用单调不增序列,同时使用队列和双向队列class MyQueue{public:queue<int> que;deque<int> deq;MyQueue(){}int MaxValue(){if(deq.empty()) return -1;return deq.front();}void push_back(int value){que.push(value);while(!deq.empty() && deq.back() < value)deq.pop_back();deq.push_back(value);}int pop_front(){if(que.empty()) return -1;int value = que.front();if(value == deq.front())deq.pop_front();que.pop();return value;}
};
面试题60:n个骰子的点数
// 思路:主要利用动态规划进行求解,其状态转移方程需要构建,还有初始化边界条件vector<double> dicesProbability(int n)
{// 首先定义:二维数组res, 表示第n个骰子和为j出现的次数vector<vector<int>> res(n+1, vector<int>(6*n+1, 0));// 其次定义初始化条件for(int i=1; i<=6; i++)res[1][i] = 1;// 之后开始循环,使用上一时刻的状态定义下一时刻for(int i=2; i<=n; i++)for(int j=2; j<=6*n; j++)for(int k=1; k<=6; k++){if(j<=k) break;res[i][j] += res[i-1][j-k];}int allSum = pow(6,n); // 表示一共计算的次数,用于计算概率vector<double> result; // 保存计算结果// 下面便是将第n个骰子和为j的次数除以总计算次数得到概率for(int i=n; i<=6*n; i++) // 由于和的范围是[n,6*n]{result.push_back(res[n][i]*1.0/allSum);}return result;
}
面试题61:扑克牌中的顺子
// 思路:使用先排序,在统计0的个数,之后则判断相邻间数字的间隔与0的大小bool isStraight(vector<int>& nums)
{sort(nums.begin(), nums.end());int zero_size = 0;int space_sum = 0;for(int i=0; i<4; i++){if(nums[i] == 0){zero_size++;continue;}int temp = nums[i+1]-num[i]; // 计算数字间隔if(temp > 1)space_sum += (temp-1); // 统计相邻数字间的间隔,正常间隔为1if(temp == 0)return false; // 存在重复数字,直接返回}return (zero_size >= space_sum);
}
面试题62:股票的最大利润
// 思路:只需要一次遍历即可,记录最大利润int maxProfit(vector<int>& prices)
{if(prices.size() < 2) return 0; // 少于两个值,输出为0int min_value = prices[0]; // 记录前面的数据的最低值(买入值)int max_price = prices[1] - min_value; // 记录最大利润for(int i=1; i<prices.size(); i++) // 一次遍历{if(prices[i] < min_value) min_value = prices[i];int dif = prices[i]-min_value;if(dif > max_price)max_price = dif;}return max_price;
}
面试题63:求解1+2+…+n
// 思路:由于不能使用乘除,for、 if等运算符和关键字. 可以使用递归或者计算内存的方式
int sumNums(int n)
{n && (n += sumNums(n-1));return n;
}// 利用计算内存 sizef
int sumNums(int n)
{bool a[n][n+1];return (sizeof(a)>>1);
}
面试题64:不用加减乘除做加法
// 思路:使用 位运算进行计算, 分别计算 进位和没进位的加法
// 进位 (a&b)<<1;
// 没进位的加法 a^b;int add(int a, int b)
{while(b != 0) // 进位不为0{int c = (unsigned int)(a&b)<<1;a = a^b;b = c;}return a;
}
面试题65:树中两个节点的最低公共祖先
// 思路:由于是二叉搜索树, 因此可以同时比较树的值
// 如果当前节点比两个节点值都大,说明公共节点在左子树;如果比两个节点都小,说明公共节点都在右字树。其他情况说明当前节点就是公共节点或者公共节点是两者中的一个
TreeNode* LowerCommon(TreeNode* root, TreeNode* p, TreeNode* q)
{TreeNode* node = root;while(true){if(node->val > p->val && node->val > q->val)node = node->left;else if(node->val < p->val && node->val < q->val)node = node->right;elsebreak;}return node;
}
面试题66:二叉树的公共祖先
// 思路:由于是普通二叉树, 需要遍历查找。采用后序遍历
TreeNode* lowestCommon(TreeNode* root, TreeNode* p, TreeNode* q)
{if(!root || !p || !q || root==p || root=q) // 表示当前当前节点就是两个节点中一个return root;TreeNode* left = lowestCommonAncestor(root->left, p, q);TreeNode* right = lowestCommonAncestor(root->right, p, q); // 后序遍历if(!left && !right) return nullptr; // 表示节点两边都没有else if(left && right) return root; // 表示在当前节点两边else if(!left && right) return right; // 表示在右节点这边else return left; // 表示在左节点这边
}
面试题67:删掉一个元素以后全为 1 的最长子数组
// 思路:可以使用滑动窗口进行统计,主要统计滑动窗口内0的个数。如果0个数大于1则需要移动左边界,将多余的0移除出去;如果0个数小于等于1则需要移动右边界int longestSubarray(vector<int>& nums)
{// 首先定义左右边界int left=0, right=0;int size=nums.size(); // 统计数组的大小int count=0; // 表示窗口内0的个数int res=0; // 表示最后的滑窗内最大连续1值while(right<size) // 移动到右边界{count += nums[right]==0; // 统计滑窗内0的个数while(count >1){count -= nums[left]==0; // 将移除掉的0剪掉left++;}res = max(res, right-left+1); // 保存最大窗口数right++; // 说明滑窗内0个数小于等于1}return res-1; // 由于需要减掉0,因此需要为窗口最大值减1
}// 包含输入输入输出的笔试
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;// 滑动窗口算法
int maxSlideWindow(vector<int> nums)
{int left = 0, right = 0;int count = 0;int size = nums.size();int res = 0;while (right < size){count += nums[right] == 0;while (count > 1){count -= nums[left] == 0;left++;}res = max(res, right - left + 1);right++;}return res - 1;
}// 主函数
int main()
{int count = 0;cin >> count;vector<vector<int>> nums;while (count--){int signlCount = 0;cin >> signlCount;int temp = 0;vector<int> nums1;while (signlCount--){cin >> temp;nums1.push_back(temp);}nums.push_back(nums1);}int length = nums.size();for (auto & num : nums){int res = maxSlideWindow(num);cout << res << " ";}cout << endl;//system("pause");return 0;
}
面试题68:分割回文串(阿里实习笔试)
// 思路,使用动态规划
// d[n][k]表示前n个字符被分割为k个回文子串至少需要修改的字符数// 1. 判断回文子串所需要修改的字符数
int cost(string& s, int l, int r)
{int res = 0;for(int i=l, j=r; i<j; i++, j--){if(s[i] != s[j])res++;}return res;
}// 2. 动态规划返回d[n][k]
int reslotion(string& s, int k)
{int n=s.size(); // 字符个数// dp[i][j]表示前i个字符分割为j个子串需要的最少字符数,其中i>=jvector<vector<int>> dp(n+1, vector<int>(k+1, 1e6));dp[0][0] = 0; // 状态初值for(int i=1; i<=n; i++) // 前i个字符串{for(int j=1; j<=min(i,k); j++) // 表示分割为j个回文子串, 且j<=i以保证足够分割{if(j==1) // 表示前i个不分割,直接统计{dp[i][j] = cost(s, 0, i-1);}else{for(int t=j-1; t<i; t++) // 将j个子串再分为 j-1个子串和1个回文串之和,以此类推{dp[i][j] = min(dp[i][j], dp[t][j-1]+cost(s, t, i-1)); // 状态转移方程,使用最小的}}}}return dp[n][k];
}int main()
{int size = 0;cin>>size;vector<string> str;vector<int> count;int temp;while(size--){string temp_str;cin>>temp_str;str.push_back(temp_str);cin>>temp;count.push_back(temp);}for(int i=0; i<str.size(); i++){cout<<reslotion(str[i], count[i])<<endl;}return 0;
}