目录
1.剑指Offer
面试题12:矩阵中的路径
题目描述:
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串”bcced”的路径,但是矩阵中不包含”abcb”路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
思路:回溯法,将矩阵看作二维数组,字符串作为一维数组,对于矩阵中的每个点,遍历其左右上下四个方向上的路径,若找到则返回true,否则继续下一个节点,每次需要visited数组记录该节点是否被访问过,递归返回时需修改visited数组和已匹配的字符串长度。
代码:
class Solution {
public:
bool hasPath(char* matrix, int rows, int cols, char* str)
{
if(matrix==nullptr||rows<=0||cols<=0||str==nullptr){
return false;
}
bool *visited=new bool[rows*cols];
memset(visited,0,rows*cols);
int pathLength=0;
for(int i=0;i<rows;i++){
for(int j=0;j<cols;j++){
if(hasPathCore(matrix,rows,cols,i,j,str,visited,pathLength)){
return true;
}
}
}
delete[] visited;
return false;
}
bool hasPathCore(const char* matrix, int rows, int cols, int row, int col, const char* str, bool *visited, int& pathLength){
if(str[pathLength]=='\0'){
return true;
}
bool hasPath=false;
if(row>=0&&row<rows&&col>=0&&col<cols&&str[pathLength]==matrix[row*cols+col]&&!visited[row*cols+col]){
++pathLength;
visited[row*cols+col]=true;
hasPath=hasPathCore(matrix,rows,cols,row-1,col,str,visited,pathLength)||hasPathCore(matrix,rows,cols,row+1,col,str,visited,pathLength)||hasPathCore(matrix,rows,cols,row,col-1,str,visited,pathLength)||hasPathCore(matrix,rows,cols,row,col+1,str,visited,pathLength);
if(!hasPath){
--pathLength;
visited[row*cols+col]=false;
}
}
return hasPath;
}
};
面试题42:连续子数组的最大和
题目描述:
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
代码:
class Solution {
public:
int FindGreatestSumOfSubArray(vector<int> array) {
if(array.empty()||array.size()==0){
return false;
}
int cursum=0;
int greatestsum=0;
for(int i=0;i<array.size();i++){
if(cursum<=0){
cursum=array[i];
}
else{
cursum+=array[i];
}
if(cursum>greatestsum){
greatestsum=cursum;
}
}
return greatestsum;
}
};
结果:不通过
您的代码已保存
答案错误:您提交的程序没有通过所有的测试用例
case通过率为33.33%
错误原因:cursum和greatestsum初始值都设为0,但是当数组全为负数,则不成立,故需将它们的处置设置为数组的第一个值
修改为:
int cursum=array[0];
int greatestsum=array[0];
正确代码:动态规划
class Solution {
public:
int FindGreatestSumOfSubArray(vector<int> array) {
if(array.empty()) return 0;
int sum = array[0], tempsum = array[0]; //注意初始值 不能设为0 防止只有负数
for(int i = 1; i < array.size(); i++) //从1开始 因为0的情况在初始化时完成了
{
tempsum = (tempsum < 0) ? array[i] : tempsum + array[i];
sum = (tempsum > sum) ? tempsum : sum;
}
return sum;
}
};
2.Leetcode
例1:复制带有随机指针的链表
题目描述:
A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null.
Return a deep copy of the list.
思路:先拷贝新节点,插入到原节点的后边;然后再 拷贝随机指针;最后将新节点从原链表中分离出,注意要保证原链表正常。
代码:
class Solution {
public:
RandomListNode *copyRandomList(RandomListNode *head) {
if(!head) return NULL;
RandomListNode *copy,*p;
for(p=head;p;p=p->next){
copy=new RandomListNode(p->label);
copy->next=p->next; // insert new at old next
p=p->next=copy;
}
for(p=head;p;p=copy->next){
copy=p->next;
copy->random=(p->random?p->random->next:NULL); //copy random pointer
}
for(p=head,head=copy=p->next;p;){
p=p->next=copy->next; //split list
copy=copy->next=(p?p->next:NULL);
}
return head;
}
};
例2:n个数选择k个的所有组合
题目描述:
Given two integers n and k, return all possible combinations of knumbers out of 1 … n.
For example,
If n = 4 and k = 2, a solution is:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
思路:递归思想,n个数选k个,分为两部分,第一部分选择当前数再从剩下的n-1个数中选择k-1个,第二部分不选当前数,直接从剩下的n-1个中选择k个数。
代码:
class Solution {
public:
vector<vector<int> > combine(int n, int k) {
vector<vector<int>> res;
vector<int> ans;
combineCore(1,n,k,ans,res);
return res;
}
void combineCore(int num, int n, int k, vector<int> &ans, vector<vector<int>> &res){
if(k<=0){
res.push_back(ans);
return;
}
if(num>n) return;
ans.push_back(num); //将当前数num加入组合,再从n-1个数中选择k-1个构成组合
combineCore(num+1,n,k-1,ans,res);
ans.pop_back(); //不选择当前数num,直接从n-1个数中选择k个构成组合
combineCore(num+1,n,k,ans,res);
}
};
3.2018年校招编程题
例1:网易-彩色的砖块
题目描述:
小易有一些彩色的砖块。每种颜色由一个大写字母表示。各个颜色砖块看起来都完全一样。现在有一个给定的字符串s,s中每个字符代表小易的某个砖块的颜色。小易想把他所有的砖块排成一行。如果最多存在一对不同颜色的相邻砖块,那么这行砖块就很漂亮的。请你帮助小易计算有多少种方式将他所有砖块排成漂亮的一行。(如果两种方式所对应的砖块颜色序列是相同的,那么认为这两种方式是一样的。)
例如: s = “ABAB”,那么小易有六种排列的结果:
“AABB”,”ABAB”,”ABBA”,”BAAB”,”BABA”,”BBAA”
其中只有”AABB”和”BBAA”满足最多只有一对不同颜色的相邻砖块。
输入描述:
输入包括一个字符串s,字符串s的长度length(1 ≤ length ≤ 50),s中的每一个字符都为一个大写字母(A到Z)。
输出描述:
输出一个整数,表示小易可以有多少种方式。
示例1
输入
ABAB
输出
2
思路:对每种颜色砖块个数做统计:
- 如果出现了两种以上的颜色,肯定最少有两种相邻,直接输出0.
- 如果出现了一种,那就只有一种。
- 如果出现了两种,那就只有两种。
代码:
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main(){
string str;
int res=0;
cin>>str;
sort(str.begin(),str.end());
str.erase(unique(str.begin(),str.end()),str.end());
if(str.size()==1){
res=1;
}
else if(str.size()==2){
res=2;
}
cout<<res<<endl;
return 0;
}
例2:网易-最长连续交错01串的长度
题目描述:
如果一个01串任意两个相邻位置的字符都是不一样的,我们就叫这个01串为交错01串。例如: “1”,”10101″,”0101010″都是交错01串。
小易现在有一个01串s,小易想找出一个最长的连续子串,并且这个子串是一个交错01串。小易需要你帮帮忙求出最长的这样的子串的长度是多少。
输入描述:
输入包括字符串s,s的长度length(1 ≤ length ≤ 50),字符串中只包含'0'和'1'
输出描述:
输出一个整数,表示最长的满足要求的子串长度。
示例1
输入
111101111
输出
3
思路:遍历字符串,记录每个位置的最大01串长度。
代码:
错误解法:case通过率为80%
#include <iostream>
#include <string>
using namespace std;
int main(){
string str;
int maxlen=0;
cin>>str;
int i=1;
int start=0;
while(i<str.size()){
if(str[i]!=str[i-1]){
++i;
}
else{
int curlen=i-start;
if(curlen>maxlen){
maxlen=curlen;
}
start=i;
++i;
}
}
cout<<maxlen<<endl;
}
错误原因应该是else里面的问题。
正确代码:
#include <iostream>
#include <string>
using namespace std;
int main(){
string str;
int maxlen=0;
cin>>str;
for(int i=1;i<str.size();){
int k=i-1;
while(i<str.size()){
if(str[i]!=str[i-1]){
i++;
}
else{
break;
}
}
if(maxlen<i-k){
maxlen=i-k;
}
i++;
}
cout<<maxlen<<endl;
}
4.2017年阿里巴巴暑期实习试题-单选题
例1:问题描述:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
sizeof(bu)的值是(C)
A.20 B.21 C.22 D.23 E.24 F.非以上选项
解析:
#pragma pack(2)
class BU
{
int number; // 4
union UBffer
{
char buffer[13]; // 13
int number; // 4
}ubuf; // union的大小取决于它所有的成员中,占用空间最大的一个成员的大小,并且需要内存对齐,这里因为#pragma pack(2),所以union的大小为14,如果不写#pragma pack(2),那么union大小为16【因为与sizeof(int)=4对齐】
void foo(){} //0
typedef char*(*f)(void*); //0
enum{hdd,ssd,blueray}disk; // 4
}bu;
因此sizeof(union) = 4+14 +0 +0 +4 = 22
知识点补充:
1.#pragma pack(n)的意思是告诉编译器字节对齐方式为n字节对齐,n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。
2.枚举变量的取值为花括号内的任意一个值(有且只能有其中一个值),而这个值是int型的,在X86系统中,int型的数据占内存4个字节。所以sizeof(c) = 4,也就是枚举变量的值为4。多数编译器默认enum型长度等于int型
例2:同一个进程中的线程不共享的部分是(F)
A.信号 B.堆 C.文件描述符 D.进程组id E.代码段 F.栈空间
解析:
线程共享的环境包括:进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。
进程拥有这许多共性的同时,还拥有自己的个性。有了这些个性,线程才能实现并发性。这些个性包括:
1.线程ID
每个线程都有自己的线程ID,这个ID在本进程中是唯一的。进程用此来标识线程。
2.寄存器组的值
由于线程间是并发运行的,每个线程有自己不同的运行线索,当从一个线程切换到另一个线程上 时,必须将原有的线程的寄存器集合的状态保存,以便将来该线程在被重新切换到时能得以恢复。
3.线程的堆栈
堆栈是保证线程独立运行所必须的。线程函数可以调用函数,而被调用函数中又是可以层层嵌套的,所以线程必须拥有自己的函数堆栈, 使得函数调用可以正常执行,不受其他线程的影响。栈是线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread safe 的。
4.错误返回码
由于同一个进程中有很多个线程在同时运行,可能某个线程进行系统调用后设置了errno值,而在该 线程还没有处理这个错误,另外一个线程就在此时被调度器投入运行,这样错误值就有可能被修改。所以,不同的线程应该拥有自己的错误返回码变量。
5.线程的信号屏蔽码
由于每个线程所感兴趣的信号不同,所以线程的信号屏蔽码应该由线程自己管理。但所有的线程都共享同样的信号处理器。
6.线程的优先级
由于线程需要像进程那样能够被调度,那么就必须要有可供调度使用的参数,这个参数就是线程的优先级。
在一个进程的线程共享堆区,而进程中的线程各自维持自己堆栈。
在 windows 等平台上,不同线程缺省使用同一个堆,所以用 C 的 malloc (或者 windows 的 GlobalAlloc)分配内存的时候是使用了同步保护的。如果没有同步保护,在两个线程同时执行内存操作的时候会产生竞争条件,可能导致堆内内存管理混乱。比如两个线程分配了统一块内存地址,空闲链表指针错误等。
Symbian 的线程一般使用独立的堆空间。这样每个线程可以直接在自己的堆里分配和释放,可以减少同步所引入的开销。当线程退出的时候,系统直接回收线程的堆空间,线程内没有释放的内存空间也不会造成进程内的内存泄漏。
但是两个线程使用共用堆的时候,就必须用 critical section 或者 mutex 进行同步保护。否则程序崩溃时早晚的事。如果你的线程需要在共用堆上无规则的分配和释放任何数量和类型的对象,可以定制一个自己的 allcator,在 allocator 内部使用同步保护。线程直接使用这个 allocator 分配内存就可以了。这相当于实现自己的 malloc,free。但是更建议你重新审查一下自己的系统,因为这种情况大多数是不必要的。经过良好的设计,线程的本地堆应该能够满足大多数对象的需求。如果有某一类对象需要在共享堆上创建和共享,这种需求是比较合理的,可以在这个类的 new 和 delete 上实现共享保护。
例3:下面关于系统调用的描述中,错误的是(B)
A.系统调用把应用程序的请求传输给系统内核执行
B.系统调用中被调用的过程运行在”用户态”中 (调用程序是运行在用户态,而被调用的程序是运行在系统态)
C.利用系统调用能够得到操作系统提供的多种服务
D.是操作系统提供给编程人员的接口
E.系统调用给用户屏蔽了设备访问的细节
F.系统调用保护了一些只能在内核模式执行的操作指令
例4:在动态分区分配方案中,系统回收主存,合并空闲空间时需修改空闲区表,以下哪种情况空闲区会减1?(F)
正确答案:F.有上邻空闲区,也有下邻空闲区
解析:有四种:上邻,下邻,上下相邻,上下不相邻。
(1)回收分区的上邻分区是空闲的,需要将两个相邻的空闲区合并成一个更大的空闲区,然后修改空闲区表。
(2)回收分区的下邻分区是空闲的,需要将两个相邻的空闲区合并成一个更大的空闲区,然后修改空闲区表。
(3)回收分区的上、下邻分区都是空闲的(空闲区个数为2),需要将三个空闲区合并成一个更大的空闲区(空闲区个数为1 ),然后修改空闲区表、
(4)回收分区的上、下邻分区都不是空闲的,则直接将空闲区记录在空闲区表中。
例5:下面关于虚拟局域网VLAN的叙述错误的是(D)
解析:
VLAN(Virtual Local Area Network)的中文名为”虚拟局域网”。
虚拟局域网(VLAN)是一组逻辑上的设备和用户,这些设备和用户并不受物理位置的限制,可以根据功能、部门及应用等因素将它们组织起来,相互之间的通信就好像它们在同一个网段中一样,由此得名虚拟局域网。VLAN是一种比较新的技术,工作在OSI参考模型的第2层和第3层,一个VLAN就是一个广播域,VLAN之间的通信是通过第3层的路由器来完成的。与传统的局域网技术相比较,VLAN技术更加灵活,它具有以下优点: 网络设备的移动、添加和修改的管理开销减少;可以控制广播活动;可提高网络的安全性。
在计算机网络中,一个二层网络可以被划分为多个不同的广播域,一个广播域对应了一个特定的用户组,默认情况下这些不同的广播域是相互隔离的。不同的广播域之间想要通信,需要通过一个或多个路由器。这样的一个广播域就称为VLAN。
参考:
https://blog.csdn.net/Cassiel_Paris/article/details/80203633