(I) 扎实的基础知识:
1.编程语言(C++):概念理解
分析代码运行结果
写代码定义一个类或类的成员函数(构造函数,析构函数,运算符重载)
《Effective C++》,《C++ primer》
2.数据结构(二叉树和链表)
(1)数组:内存连续,根据下标O(1)读写,时间效率高,可实现简单的哈希表,预先分配内存,总有空闲区域,空间效率低(动态数组vector)。数组名为指向数组第一个元素的指针,可通过指针访问数组元素(对数组名直接取sizeof求得的是数组大小,若用数组名当参数传递或定义指针指向数组,则sizeof结果为4个字节,因为32位系统,任意指针均为4个字节)
(2)字符串:内存连续的若干字符,以‘\0’结尾故需要一个额外字符开销,注意不要越界,需要遍历找到字符串长度,当几个指针幅值给相同的常量字符串时,实际指向相同的内存地址。
(3)链表:动态数据结构,创建时无需知道长度,插入时重新分配内存。然后调整指针链接,每添加一个结点分配一次内存,无闲置内存,空间效率比数组高。插入结点:
void AddToTail(ListNode ** pHead, int value)
{
ListNode *pNew = new ListNode ();
pNew -> val = value;
pNwa -> next = NULL;
if (*pHead == NULL)
pHead = pNew;
else
{
ListNode *pNode = *pHead;
while (pNode -> next != NULL)
pNode = pNode -> next;
pNode -> nexrt = pNew;
}
}
注意pHead为指向指针的指针,当往空链表中插入时,插入的结点即为头指针,由于会改变头指针,故设为指向指针的指针,否则出了这个函数仍为空指针
找到第i个结点需要从头遍历,时间效率为O(n),找到某值结点并删除:
void AddToTail(ListNode ** pHead, int value)
{
ListNode *pNew = new ListNode ();
pNew -> val = value;
pNwa -> next = NULL;
if (*pHead == NULL)
pHead = pNew;
else
{
ListNode *pNode = *pHead;
while (pNode -> next != NULL)
pNode = pNode -> next;
pNode -> nexrt = pNew;
}
}
其他: 把末尾节点指针指向头结点,环形链表
每个结点既有指向下一结点也有指向前一结点,双向链表
每个结点既有指向下一结点也有指向任意结点,复杂链表
另外有栈相关操作(先入后出):
stack<ListNode *> node;
Node.push(pNode);
pNode= node.top();
node.pop();
(4)树:除根结点外每个结点只有一个父结点;除叶结点外所有结点有一个或多个子结点,父结点与子结点间用指针链接。常见的为二叉树,每个结点最多两个子结点,最重要的操作为遍历(前序遍历,中序遍历,后序遍历各有递归和循环两种实现方法需了如指掌),另外还有宽度遍历。
补充:
//***前序遍历(递归版):
vector<int> preorderTraversal(TreeNode* root){
vector<int> ret;
PreOrder(root,ret);
return ret;
}
void PreOrder(TreeNode* root,vector<int> &ret){
if(root==NULL) return;
ret.push_back(root->val);//存储根节点
PreOrder(root->left,ret);//访问左子树
PreOrder(root->right,ret);//访问右子树
}
//***前序遍历(非递归版):
vector<int> preorderTraversal(TreeNode* root) {
vector<int> ret;
if (root == NULL)
return ret;
stack<TreeNode*> st;
st.push(root);
while(!st.empty())
{
TreeNode* tp = st.top();
st.pop();
ret.push_back(tp -> val);
if(tp -> right !=NULL)
st.push(tp -> right);
if(tp -> left !=NULL)
st.push(tp -> left);
}
return ret;
}
//***中序遍历(递归版):
vector<int> inorderTraversal(TreeNode* root){
vector<int> ret;
inOrder(root,ret);
return ret;
}
void inOrder(TreeNode* root,vector<int> &ret){
if(root==NULL) return;
inOrder(root->left,ret);//访问左子树
ret.push_back(root->val);//存储根节点
inOrder(root->right,ret);//访问右子树
}
//***中序遍历(非递归版):
vector<int> inorderTraversal(TreeNode* root) {
vector<int> ret;
TreeNode* p = root;
stack<TreeNode*> st;
while(!st.empty()||p!=NULL)
{//仍有左子树则继续找左子树
if(p)
{
st.push(p);
p = p ->left;
}
else
{//已无左子树
p = st.top();
st.pop();
ret.push_back(p -> val); //输出栈顶,实为当前根结点
if (p -> right) //虽无左子树,但该根结点可能还有右子树,若有则入栈
{
st.push(p -> right);
p = p -> right ->left; //判断右子树是否有左子树
}
else
p=NULL; //没有右子树则赋空值,让if(p)判断弹出下一个结点
}
}
return ret;
}
//***后序遍历(递归版):
vector<int> postorderTraversal(TreeNode* root){
vector<int> ret;
postOrder(root,ret);
return ret;
}
void postOrder(TreeNode* root,vector<int> &ret){
if(root==NULL) return;
postOrder(root->left,ret);//访问左子树
postOrder(root->right,ret);//访问右子树
ret.push_back(root->val);//存储根节点
}
//***后序遍历(非递归版):将先序遍历的左右子树顺序反过来后逆序输出
vector<int> postorderTraversal(TreeNode* root) {
vector<int> ret;
if (root == NULL)
return ret;
stack<TreeNode*> st;
st.push(root);
while(!st.empty())
{
TreeNode* tp = st.top();
st.pop();
ret.push_back(tp -> val);
if(tp -> left !=NULL)
st.push(tp -> left);
if(tp -> right !=NULL)
st.push(tp -> right);
}
reverse(ret.begin(), ret.end());
return ret;
}
//***层次遍历
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ret;
if(root==NULL) return ret;
queue<TreeNode*> que;
que.push(root);
while(!que.empty())
{
vector<int> temp;
queue<TreeNode*> tmpQue;//存储下一层需要访问的节点
while(!que.empty())//从左到右依次访问本层
{
TreeNode* tempNode = que.front();
que.pop();
temp.push_back(tempNode->val);
if(tempNode->left!=NULL) tmpQue.push(tempNode->left);//左子树压入队列
if(tempNode->right!=NULL) tmpQue.push(tempNode->right);//右子树压入队列
}
ret.push_back(temp);
que=tmpQue;//访问下一层
}
return ret;
}
特例:二叉搜索树,左子树结点总是小于等于根结点而右子结点总是大于等于根结点,可在O(logn)的时间内找到一个结点。
特例:堆分为最大堆和最小堆指的是根结点最大或最小,快速找到最值可用堆解决。
特例:红黑树,根为红和黑两种,通过规则确保从根结点到叶结点的最长路径不超过最短路径的二倍,set,multiset,map,multimap均基于红黑树实现。
(5)栈和队列:栈先入后出,最后push的最先pop;队列先入先出,注意二者的相互联系
3.算法(二分查找,归并排序和快速排序,动态规划和贪婪算法)。
(1)查找
查找:顺序查找,二分查找,哈希表查找,二叉排序树查找
在排序的数组或部分排序的数组中查找---二分查找(信手拈来)
int binarySerach(int[] array, int key)
{
int left = 0;
int right = array.length - 1;
// 这里必须是 <=
while (left <= right)
{
int mid = (left + right) / 2;
if (array[mid] == key)
return mid;
else if (array[mid] < key) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
return -1;
}
哈希表查找:O(1)的时间效率,但需要额外的空间来实现哈希表
(2)排序
比较各种排序算法空间消耗,平均和最坏时间复杂度(烂熟于胸)
快排的代码:
int Partition(intdata[], int lentgth, int start, int end)
{
if (data == NULL || length <= 0 || start< 0 || end >= length)
throw new exception(“ InvalidParameters”);
int index = RandomInRange(start, end);
Swap(&data[index], &data[end]);
int small = start – 1;
for(index = start; index < end; ++index)
{
if(data[index]< data[end])
{
++ small;
if (small != index)
Swap (&data[index],&data[small]
}
}
++ small;
Swap(&data[small],&data[end]);
return small;
}
VoidQuickSort ( int data[], int length, int start, int end)
{
if (start == end)
return;
int index = Partition(data, lenth, start,end);
if (index > start)
QuickSort (data, length, start, index – 1);
if (index < end)
QuickSort (data, length, index + 1, end);
}
(3)递归和循环
若无特殊要求,优先使用递归算法。
递归简洁,但时间和空间消耗大(调用栈溢出),且很多计算是重复的,性能不好
(4)位运算:与,或,异或,左移(右侧补零),右移(有符号数左移补符号位,某则补零)。
***一个整数减一后再与原来做位与运算,相当于把二进制最右边一个1变为0
(II)高质量的代码:完整性,完成基本功能,边界条件,内存覆盖,特殊输入(空指针,0,空字符串),错误处理(输入不合法),笔试时可能需要考虑可重复输入
(III)分析思路清晰: 举例具体化
画图形象化(数据结构链表等)
分解简单化(递归—分治法和动态规划)
(IV)优化时间和空间效率:
(V)学习沟通能力:观点明确,逻辑清晰,团队合作意识,最近看的书和学到的技术,对新概念的理解能力,知识迁移能力,抽象建模能力,发散思维能力