数组
特点:连续存储,读写时间效率高。
注意点:
- C++的STL中的vector每次扩容,新容量都是以前容量的2倍。
- 在C/C++中,当数组作为函数参数传递时,数组自动退化成同类型的指针
int GetSize(int data[])
{
return sizeof(data);
}
int _tmain(int argc,_TCHAR* ARGV[])
{
int data1[]={1,2,3,4,5};
int size1=sizeof(data1);
int *data2=data1;
int size2=sizeof(data2);
int size3=GetSize(data1);
printf("%d,%d,%d",size1,size2,size3);
}
结果:size1=20,size2=4,size3=4
面试题3:二维数组的查找
解题思路:选取右上角或者左下角的数值,排除掉多余的行和列,以右上角为例,小于第一行最后一列,排除最后一列,大于第一行该列,排除该行
代码:
#include "stdafx.h"
#include <iostream>
using namespace std;
bool Find(int*matrix,int rows,int columns,int number)
{
bool find=false;
if (matrix!=NULL&&rows>0&&columns>0)
{
int row=0;
int column=columns-1;
while (row<rows&&column>=0)
{
if (matrix[row*columns+column]==number)
{
find=true;
break;
}
else if (matrix[row*columns+column]>number)
{
--column;
}
else
{
++row;
}
}
}
return find;
}
int _tmain(int argc, _TCHAR* argv[])
{
int a[][4]={{1,2,8,9},{2,4,9,12},{4,7,10,13},{6,8,11,15}};
bool find1=Find((int*)a,4,4,4);
bool find2=Find((int*)a,4,4,5);
cout<<"查询结果:"<<find1<<endl;
cout<<"查询结果:"<<find2<<endl;
system("pause");
return 0;
}
字符串
特点:
- 字符串由若干字符组成的序列,C/C++中每个字符串以’\0’作为结尾,每个字符串都有一个额外的开销。
- 为了节省内存,C/C++把常量字符串放到一个单独的内存区域,当几个指针赋值给相同的常量字符串时,它们实际上会指向相同的内存地址。但用常量内存初始化数组,情况却有所不同
int _tmain(int argc,_TCHAR*argv[])
{
char str1[]="hello world";
char str2[]="hello world";
char*str3="hello world";
char*str4="hello world";
if(str1==str2)
{
printf("str1 and str2 are same");
}
else
{
printf("str1 and str2 are not the same");
}
if(str3==str4)
{
printf("str3 and str4 are same");
}
else
{
printf("str3 and str4 are not the same");
}
}
//结果 str1 and str2 are not the same str3 and str4 are the same
str1和str2是两个字符串数组,为它们分配两个长度为12个字节空间,并把内容分别复制到数组中去。初始地址不同
str3和str4无需分配内存存储字符串内容,都指向“hello world”。
值相同
面试题:请事先一个函数,把字符串中的每个空格替换成“%20”,例如输出“we are happy”,则输出“we%20are%20happy”
解题思路:
- 遇到空格替换成%20,正着遇到空格,移动数组,插入%20,字符串长度为n,空格字符,需向后移动O(N)个字符,对含有O(N)个空格字符的字符串而言,时间效率为O(N^2)。这个方法太粗暴
- 找到空格后,移动后面的字符,再替换为%20,是为了防止原地替换,覆盖掉其他内容。数组处理,正着处理不行,反着来。从末尾开始保留,遇到空格替换,可实现不覆盖,不移动,时间效率O(N);
- 原数组处理,不开辟新的空间,计算替换后的数组长度,知晓原来的长度和包含的空格数,最终的长度为原来的长度+2*空格数。
- 取2个索引,一个指向新的位置,一个指向老的位置,不等于空格时,直接复制,并移动两个索引,等于空格时,替换为0 2 %,新索引移动3个,旧索引移动1格。
#include "stdafx.h"
#include <stdlib.h>
void ReplaceBlank(char string[],int length)
{
//边界判断
if (string==NULL&&length<=0)
{
return ;
}
int originalLen=0;
int numBlank=0;
int i=0;
//计算数组的长度和空格数
while (string[i]!='\0')
{
++originalLen;
if (string[i]==' ')
{
++numBlank;
}
++i;
}
//数组自身内存大小判断
int newLen=originalLen+2*numBlank;
if (newLen>length)
{
return;
}
//两个指针或索引
int indexOrignal=originalLen;
int indexNew=newLen;
while (indexOrignal>=0&&indexNew>indexOrignal)
{
if (string[indexOrignal]==' ')
{
string[indexNew--]='0';
string[indexNew--]='2';
string[indexNew--]='%';
}
else
{
string[indexNew--]=string[indexOrignal];
}
--indexOrignal;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
char str[20]="we are happy";
ReplaceBlank(str,20);
printf("%s",str);
system("pause");
return 0;
}
举一反三:如果合并两个数组(包括字符串),如果从前往后复制每个数字(或字符)需要重复移动数字或字符多次,那么我们可以考虑从后往前复制,减少移动次数,从而提高效率
链表
链表的末尾添加一个节点:
phead为指向指针的指针,当往空链表插入节点,新插入的节点就是链表的头指针,会改动头指针,因此必须把phead参数设为指向指针的指针,否则出了函数phead仍是一个空指针
void AddToTail(ListNode**pHead,int value)
{
ListNode*pNew=new ListNode();
pnew->value=value;
pnew->next=NULL;
if(*pHead==NULL)
{
*pHead=pnew;
}
else
{
listnode*pnode=*phead;
while(pnode->next!=NULL)
{
pnode=pnode->next
}
pnode->next=pnew;
}
}
==链表中找到第一个含有某值的节点并删除该节点的代码
struct listnode
{
int value;
listnode*next;
};
void RemoveNode(listnode**phead,int value)
{
if(phead==NULL||*phead==NULL)
{
return;
}
listnode*pdelete=NULL;
if((*phead)->value==value)
{
pdelete=*phead;
*phead=(*phead)->next;
}
else
{
listnode*pnode=*phead;
while (pnode->next!=NULL&&pnode->next->value!=value)
{
pnode=pnode->next;
}
if (pnode->next!=NULL&&pnode->next->value==value)
{
pdelete=pnode->next;
pnode->next=pnode->next->next;
}
}
if (pdelete)
{
delete pdelete;
pdelete=NULL;
}
}
从尾到头打印链表:
面试中改变输入的值,询问面试官是不是允许做修改
分析:
- 从头到尾输出比较简单,修改指针所指的方向,反转过来,就可以从尾到头打印链表,但是修改了输入的结构,不可取
- 从尾到头的打印链表,先进后出,后访问先打印,符合栈的特征,借用栈做中间转换
//从尾到头打印链表 用栈实现
void printListReverse(listnode*phead)
{
if (phead==NULL)
{
return;
}
stack<listnode*> nodestack;
//不修改头结点的指向
listnode*pnode=phead;
while (pnode)
{
nodestack.push(pnode);
pnode=pnode->next;
}
//栈的取值 栈不为空 top pop
while (!nodestack.empty())
{
pnode=nodestack.top();
printf("%d\t",pnode->value);
nodestack.pop();
}
}
//从尾到头打印链表 递归实现
//链表太长 导致函数调用的层级很深,从而导致函数调用栈溢出 显示用栈基于循环实现的代码鲁棒性要好一点
void printListReverse(listnode*phead)
{
if (phead!=NULL)
{
if (phead->next!=NULL)
{
printListReverse(phead->next);
}
printf("%d\t",phead->value);
}
}
树
二叉树:每个节点最多有2个子节点
二叉树的遍历:
- 前序遍历,先访问根节点,再访问左节点,再访问右节点
- 中序遍历:先访问左节点,再访问根节点,最后访问右节点
- 后序遍历:先访问左节点,再访问右节点,最后访问根节点
前,中,后序遍历针对根节点的访问顺序定义的。
3种遍历可用循环和递归2种实现方式,递归的实现比循环的实现简洁的多
二叉树的特例
二叉搜索树:左子节点总是小于或等于根节点,右子节点总是大于或等于根节点
堆:堆分为最大堆和最小堆,最大堆中根节点值最大,最小堆中根节点的值最小。快速查找最大值或最小值的问题可以用堆解决。
红黑树:把树中的节点定义为红黑两种颜色,并通过规则确保从根节点到叶节点的最长路径的长度不超过最短路径的两倍。在C++的STL中,set,multiset,map,multimap等数据结构都是基于红黑树实现。
栈
用两个栈实现队列
//栈的特性先进后出 队列的特性先进先出
//添加元素 push进栈中
//取元素,stack1和顺序和实际要出的顺序相反,利用stack2调整数据的方向,做到先进的数据在stack2的最上面
//取数据时,stack2为空时,把数据从stack1转移到stack2;取最先压入的数据,直接取stack2的栈顶即可
#include "stdafx.h"
//使用模板 创建一个class
template<typename T>class CQueue
{
public:
CQueue(void);
~CQueue(void);
void appendTail(const T&node);
T deleteHead();
private:
stack<T>stack1;
stack<T>stack2;
};
template<typename T> void CQueue::appendTail(const T& element)
{
stack1.push(element);
}
template<typename T> void CQueue::deleteHead()
{
if (stack2.size()<=0)
{
while (stac1.size()>0)
{
T&data=stack1.top();
stack1.pop();
stack2.push(data);
}
}
if (stack2.size()==0)
{
throw new exception("queue is empty");
}
T head=stack2.top();
stack2.pop();
return head;
}
两个队列实现一个栈
template<typename T>class Cstack
{
public:
Cstack(void);
~Cstack(void);
void appendTail(const &T node);
T deleteHead();
private:
queue<T>q1;
queue<T>q2;
};
template<typename T> void Cstack::appendTail(const &T node)
{
if (!q1.empty())
{
q1.push(node);
}
else
{
q2.push(node);
}
}
template<typename T> T Cstack::deleteHead()
{
int ret=0;
if (!q1.empty())
{
int num=q1.size();
while (num>1)
{
q2.push(q1.front());
q1.pop();
--num;
}
ret=q1.front();
q1.pop();
}
else
{
int num=q2.size();
while (num>1)
{
q1.push(q2.front());
q2.pop();
--num;
}
ret=q2.front();
q2.pop();
}
return ret;
}
算法
掌握:二分查找、归并排序和快速排序
小tips:
- 排序数组中查找一个数字或统计某个数字出现的次数,可以尝试用二分查找算法
- 哈希表和二叉树查找的重点为数据结构,哈希表的优点是能够在O(1)的时间查找某个元素,是效率最高的查找方式,但需要额外的空间实现哈希表
- 插入排序、冒泡排序、归并排序和快速排序等不同算法的优劣,能从额外空间消耗、平均时间复杂度和最差时间复杂度等方面比较优缺点
快速排序:先在一个数组中选择一个数字,接下来把数据中的数字分为2部分,小于选择数字移到数组左边,比选择大的数字移到数组右边