在面试的过程中,软件主管为了考察面试者的代码水平,往往让面试者手写一段经典代码,比如字符串拷贝函数strcpy()、字符串连接函数strcat()、单链表反转、冒泡排序、二叉树的三种遍历(先序、中序、后序)等。这就需要面试者在复习的过程中理清算法的原理、画算法流程图或者UML类图、会写伪代码哪怕是中文也行。
下面介绍常见的几种手写代码:
- 字符串拷贝函数strcpy()
//字符串复制,从src拷贝到dst
char* strcpy(char *dst, const char* src) {
assert(dst != NULL && src != NULL );
char *ret = dst;
while ((*dst++ = *src++) != '\0');
return ret;
}
- 字符串连接函数strcat()
char *strcat(char* strDst, const char* strSrc) {
assert(strDst != NULL && strSrc != NULL);
char *ret = strDst;
while (*strDst != '\0'){
strDst++;
}
while ((*strDst++ = *strSrc++) != '\0');
return ret;
}
- 单链表反转
struct ListNode {
int m_data;
ListNode *m_next;
ListNode(int x) : m_data(x), m_next(NULL) {}
};
ListNode* reverseList(ListNode *head) {
ListNode *pre = NULL, *next = NULL;
ListNode *cur = head;
while (cur != NULL) {
next = cur->m_next;
cur->m_next = pre;
pre = cur;
cur = next;
}
return pre;
}
//或者如下:
ListNode* reverseList(ListNode *head) {
ListNode *p = NULL, *q = NULL;
ListNode *cur = head;
while (cur != NULL) {
q = cur->m_next;
cur->m_next = p;
p = cur;
cur = q;
}
return p;
}
- 冒泡排序
#include <stdlib.h>
#include <iostream>
using namespace std;
template<typename T>
//整数或浮点数皆可使用
void bubble_sort(T arr[], int len)
{
int i, j; T temp;
for (i = 0; i < len - 1; i++)
for (j = 0; j < len - 1 - i; j++)
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
int arr[] = { 61, 17, 29, 22, 34, 60, 72, 21, 50, 1, 62 };
int len = (int) sizeof(arr) / sizeof(*arr);
bubble_sort(arr, len);
for (int i = 0; i < len; i++)
cout << arr[i] << ' ';
cout << endl;
float arrf[] = { 17.5, 19.1, 0.6, 1.9, 10.5, 12.4, 3.8, 19.7, 1.5, 25.4, 28.6, 4.4, 23.8, 5.4 };
len = (int) sizeof(arrf) / sizeof(*arrf);
bubble_sort(arrf, len);
for (int i = 0; i < len; i++)
cout << arrf[i] << ' ';
cout << endl;
system("pause");
return 0;
}
- 二叉树的先序、中序、后序遍历,递归方式实现
typedef char ElemType;
typedef struct btnode
{
ElemType data;
struct btnode* lchild;
struct btnode* rchild;
}BTNode;
//先序遍历(根-->左子树 -->右子树)
void PreOrder(BTNode *b)
{
if (b != NULL)
{
printf("%c ", b->data); //访问根结点
PreOrder(b->lchild);
PreOrder(b->rchild);
}
}
//中序遍历(左子树 --> 根 -->右子树)
void InOrder(BTNode *b)
{
if (b != NULL)
{
InOrder(b->lchild);
printf("%c ", b->data); //访问根结点
InOrder(b->rchild);
}
}
//后序遍历(左子树 -->右子树 --> 根)
void PostOrder(BTNode *b)
{
if (b != NULL)
{
InOrder(b->lchild);
InOrder(b->rchild);
printf("%c ", b->data); //访问根结点
}
}
- 二叉树的先序、中序、后序遍历,非递归方式实现
//-------------- 非递归实现
//先序遍历的非递归方法:由先序遍历的过程可知,先访问根结点,再访问左子树,最后访问右子树。
//因此,先将根结点进栈,在栈不空时循环: 出栈p,访问*p结点,将其右孩子进栈,再将左孩子结点进栈。
//算法如下:
void PreOrder1(BTNode *b)
{
BTNode *St[100], *p;
int top = -1;
if (b != NULL)
{
top++; //根结点入栈
St[top] = b;
while (top > -1) //栈不为空时循环
{
p = St[top]; //进栈并访问该结点
top--;
printf("%c ", p->data);
if (p->rchild != NULL) //右孩子结点入栈
{
top++;
St[top] = p->rchild;
}
if (p->lchild != NULL) //左孩子结点入栈
{
top++;
St[top] = p->lchild;
}
}
printf("\n");
}
}
//中序遍历的非递归方法:由中序遍历的过程可知,采用一个栈保存需要返回的结点指针,先扫描(并非访问)根结点的
//所有左结点并将它们一一入栈。然后,出栈一个结点,显然该结点没有左孩子结点或者右孩子结点已访问过,则访问它。
//接着,扫描该结点的右孩子结点,将其进栈,再扫描该右孩子结点的所有左结点并一一入栈,如此这样,直到栈空为止。
//算法如下:
void InOrder1(BTNode *b)
{
BTNode *St[100], *p;
int top = -1;
if (b != NULL)
{
p = b;
while (top > -1 || p != NULL)
{
while (p != NULL) //扫描*p的所有左结点并入栈
{
top++;
St[top] = p;
p = p->lchild;
}
if (top > -1)
{
p = St[top]; //出栈*p的结点
top--;
printf("%c ", p->data); //访问之
p = p->rchild; //扫描*p的右孩子结点
}
}
printf("\n");
}
}
//后序遍历的非递归方法:由后序遍历的过程可知,采用一个栈保存需要返回的结点指针,先扫描根结点的
//所有左结点并将它们一一入栈,出栈一个结点*b即当前结点,然后扫描该结点的右孩子结点并入栈,再扫描
//该右孩子结点的所有左结点并入栈。当一个结点的左、右孩子结点均被访问后再访问该结点,如此这样,
//直到栈空为止。
// 其中的难点是如何判断一个结点*b的右孩子已访问过,为此需要用p保存刚刚访问过的结点(初值为NULL),
//若b->rchild = p成立(在后序遍历中,*b的右孩子结点一定刚好在*b之前访问),说明*b的左、右子树均已访问,
//现在应访问*b.
// 从上面的过程可知,栈中保存的是当前结点*b的所有祖先结点(均未访问过)。
//算法如下:
void PostOrder1(BTNode *b)
{
BTNode *St[100], *p;
int flag, top = -1; //栈指针置初值
if (b != NULL)
{
do
{
while (b != NULL) //将*b的所有左结点进栈
{
top++;
St[top] = b;
b = b->lchild;
}
p = NULL; //p指向栈结点的前一个已访问的结点
flag = 1; //设置b的访问标记为已访问过
while (top != -1 && flag)
{
b = St[top]; //取出当前的栈顶元素
if (b->rchild == p) //右孩子不存在或后孩子已被访问,则访问*b
{
printf("%c ", b->data); //访问*b结点
top--;
p = b; //p指向被访问的结点
}
else
{
b = b->rchild; //b指向右孩子结点
flag = 0; //设置未被访问的标记
}
}
} while (top != -1);
printf("\n");
}
}
- 求二叉树的高度
//求二叉树的高度
int BTNodeDepth(BTNode *b)
{
int lchildDep = 0, rchildDep = 0;
if (b == NULL)
return 0; //空树的高度为0
else{
lchildDep = BTNodeDepth(b->lchild); //左子树的高度
rchildDep = BTNodeDepth(b->rchild); //右子树的高度
return (lchildDep > rchildDep) ? (lchildDep + 1) : (rchildDep + 1); //取最大值再加1即可
}
}
8.将字符串中的所有空格去掉,要求时间复杂度O(n),空间复杂度O(1),并统计空格的个数。
思路:
a) pSpace指向第一个空格字符;
b) pChar指向pSpace后的第一个非空格字符;
c) 交换pSpace和pChar的内容,且++pSpace,使其指向下一个空格字符;
d) 重复步骤b)和c),直到pSpace或pChar指向’\0’;
e) *pSpace = ‘\0’,结束算法;
int removeSpace(char *pStr) {
char *pSpace = NULL, *pChar = NULL;
if (pStr == NULL) {
return 0;
}
//统计空格个数
char *pCnt = pStr;
int cnt = 0;
while (*pCnt != '\0') {
cnt++;
}
//找到第一个空格字符
for (pSpace = pStr; *pSpace != ' '; ++pSpace);
while (*pSpace != '\0') {
//找到空格后面的第一个非空字符
for (pChar = pSpace; *pChar == ' '; ++pChar);
if (*pChar != '\0') { //没有字符串末尾
//将该字符与空格进行交换
*pSpace = *pChar;
*pChar = ' ';
++pSpace;
}
else {
break;
}
}
//交换完成后,之间将pSpace的位置设置为\0结尾即可
*pSpace = '\0';
return cnt;
}
9.二叉树的层次遍历,使用队列实现。
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode *root) {
vector<vector<int>> res;
if (!root)
return res;
queue<TreeNode*> qu;
qu.push(root);
while (!qu.empty()) {
vector<int> tmp;
int len = qu.size();
for (int i = 0; i < len; i++) {
TreeNode *node = qu.front();
qu.pop();
tmp.push_back(node->val);
if (node->left)
qu.push(node->left);
if (node->right)
qu.push(node->right);
}
res.push_back(tmp);
}
return res;
}
};
- 判断一个二叉树树是否为二叉搜索树。
二叉搜索树的判断条件:若中序遍历该二叉树,得到的数组为从小到大的有序排列,则该树为二插搜索树,否则不是。
可以用一个静态变量保持当前节点的前驱节点,若当前节点的值小于前驱节点,说明该树不是一颗二叉树搜索树。
代码如下:
bool isBST(TreeNode *root) {
static TreeNode* prev = NULL;
if(root != NULL) {
if(!isBST(root->left)) {
return false;
}
if(prev != NULL && root->data < prev->data) {
return false;
}
prev = root;
if(!isBST(root->right)) {
return false;
}
}
return true;
}
- 翻转一颗二叉树
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) {
return NULL;
}
invertTree(root->left);
invertTree(root->right);
swap(root->left, root->right);
return root;
}
- 手写一个String字符串类,包括构造函数、拷贝构造、赋值构造、析构函数。
class String {
public:
String(const char *cstr = 0); //const的正确使用,默认参数
String(const String &str); //参数格式
String& operator=(const String &str); //返回值,操作符函数写法,参数格式
~String();
String(String &&str) noexcept ; //移动构造函数参数格式,不抛出异常,声明和定义都要写,写在初始化列表前面
String& operator=(String &&str) noexcept ; //返回类型,赋值运算符函数写法,参数类型,不抛出异常
private:
char *m_data; //底层数据存储形式,私有的
};
String::String(const char *cstr) { //无返回值,定义不写默认参数
if (cstr) { //注意空字符串的情况
m_data = new char[strlen(cstr) + 1]; //求c语言格式字符串长度用strlen,动态分配内存
strcpy(m_data, cstr); //拷贝c语言字符串用strcpy,目标参数在前
}
else {
m_data = new char[1];
*m_data = '\0'; //字符串以'\0'字符结尾
}
}
String::String(const String &str) {
m_data = new char[strlen(str.m_data) + 1]; //动态分配内存,求c语言字符串长度用strlen,字符串以'\0'字符结尾
strcpy(m_data, str.m_data); //c语言字符串拷贝用strcpy
}
String& String::operator=(const String &str) {
if (this == &str) return *this; //避免自我赋值
delete[] m_data; //先释放=左边的字符串的数据指针
m_data = new char[strlen(str.m_data) + 1]; //动态分配内存,求c语言字符串长度用strlen,字符串以'\0'字符结尾
strcpy(m_data, str.m_data); //c语言字符串拷贝
return *this; //返回引用
}
String::~String() {
delete[] m_data; //释放内存
m_data = nullptr; //指针置空
}
String::String(String &&str) noexcept { //承诺不抛出异常
m_data = str.m_data;
str.m_data = nullptr; //原来的数据指针置空
}
String& String::operator=(String &&str) noexcept { //承诺不抛出异常
if (this == &str) return *this; //避免自我赋值
delete[] m_data; //释放内存,注意[]
m_data = str.m_data; //直接拷贝指针,不需要重新分配内存
str.m_data = nullptr; //原指针置空
return *this; //返回引用
}