链表、字符串和数组一直是很多互联网公司笔试题、面试题中经常出现的,但是变化万众,看起来各大IT巨头乐此不疲。能够很好的、熟练而巧妙的操作链表、字符串和数组也是一个码农必备的技能之一。下面对最近这段时间中看到的通过巧妙使用指针来解决此类问题的题目进行总结,方便归纳记忆。
【链表:寻找链表的中点、或者寻找链表倒数第n的节点】
此类问题利用快慢指针(此处将起跳时间不同的指针也认为是快慢指针)。
演示效果图如下:
具体代码如下:
#include <stdio.h>
#include <iostream>
/*---------------------链表的操作----------------------*/
//也可以直接使用STL中的双向链表结构list。
typedef struct ListNode
{
int val;
ListNode* pNext;
} ListNode,*PListNode;
//快速寻找链表的中间节点
//【注】:对于寻找链表倒数第n个节点也是同样的流程
ListNode* FindMidNodeInList(PListNode ListHead)
{
if(ListHead==NULL)
return NULL;
if(ListHead->pNext==NULL)
return ListHead;
PListNode pFast=ListHead;
PListNode pSlow=ListHead;
while(pFast->pNext)
{
pSlow=pSlow->pNext;
pFast=pFast->pNext->pNext;
};
return pSlow;
}
int main()
{
//构造链表数据
printf("请输入链表长度:\n");
int len;
scanf("%d",&len);
PListNode ListHead=new ListNode;
PListNode CurNode=ListHead;
int i=1;
while(i<=len)
{
printf("请输入第%d个链表元素\n",i);
int val;
scanf("%d",&val);
CurNode->val=val;
if(i==len)
CurNode->pNext=NULL;
else
{
CurNode->pNext=new ListNode;
CurNode=CurNode->pNext;
}
++i;
}
CurNode->pNext=NULL;
PListNode pMid=FindMidNodeInList(ListHead);
return 0;
}
【字符串操作:63.在字符串中删除特定的字符(字符串)。
题目:输入两个字符串,从第一字符串中删除第二个字符串中所有的字符。
例如,输入”They are students.”和”aeiou”,
则删除之后的第一个字符串变成”Thy r stdnts.”。
分析:这是一道微软面试题。在微软的常见面试题中,与字符串相关的题目占了很大的一部分,
因为写程序操作字符串能很好的反映我们的编程基本功。】
此问题的直接思路就是逐个读取字符串中的每个字符然后判别是否是要删除的字符,此种方法在删除完每个字符后,需要将后续的字符前移,比较复杂。
下面展示一种通过巧妙利用快慢指针来实现字符串中删除特定字符的操作(此处的快慢指针是指两个指针跳动的次数不同),该方法不需要删除每个字母后移动后续字符串。
示意图如下:
具体代码如下:
#include <stdio.h>
#include <iostream>
#include <map>
using std::map;
/*-----------构建被删除字符串的哈希表---------------*/
//为了快速确定当前字符是否属于可删除字符,可通过构建
//可删除字符的哈希表
//此处利用了STL中的关联容器,map
map<char,int> DelCharMap;
void BuildDelCharMap(char* delchar)
{
char* pCur=delchar;
while(*pCur!='\0')
{
DelCharMap[*pCur]=1;
++pCur;
}
}
void DelCharsFromString(char* str,map<char,int>& DelCharMap)
{
char* pFirst=str;
char* pSec=str;
while(*pFirst!='\0')
{
if(DelCharMap[*pFirst])//发现要删除的字符
++pFirst;
else //未发现要删除的字符,将两个指针的数据交换
{
char temp=*pFirst;
*pFirst=*pSec;
*pSec=temp;
++pFirst;
++pSec;
}
}
*pSec='\0';
}
int main()
{
char str[]="They are students.";
char del[]="aeiou";
BuildDelCharMap(del);
DelCharsFromString(str,DelCharMap);
return 0;
}
【数组操作:
54.调整数组顺序使奇数位于偶数前面(数组)。
题目:输入一个整数数组,调整数组中数字的顺序,使得所有奇数位于数组的前半部分,
所有偶数位于数组的后半部分。要求时间复杂度为O(n)。】
利用首尾两个指针,首部指针逐个搜索直到偶数的出现,尾部指针反向搜索直到第一个奇数的出现,然后交换两个数,依次类推直至首尾指针相交
示意图如下:
具体代码如下:
#include <stdio.h>
#include <iostream>
/*-------------------数组中的奇偶数分开排列-------------------*/
void OddEvenClassify(int* data,int num)
{
int* pBegin=data;
int* pEnd=data+num-1;
while(pBegin<=pEnd)
{
//从左寻找第一个偶数
while(*pBegin%2)
++pBegin;
//从右寻找第一个奇数
while(!(*pEnd%2))
--pEnd;
//两者进行交换
if(pBegin>=pEnd)
break;
int temp=*pBegin;
*pBegin=*pEnd;
*pEnd=temp;
};
}
int main()
{
int test[]={1,5,9,6,4,2,1,3,7,1,0};
OddEvenClassify(test,11);
return 0;
}
上述三种巧思中的指针可以按照两个指针是否是同一侧出发(即移动方向是否相同)来分为:同向双指针和对向双指针两类。其实这种方式在某些情况下都可以解决问题,例如《算法导论》中的快排算法,在第七章书籍中作者给出的是“同向双指针”的方法
来进行快排中的分类,而课后思考题中又给出了原本的Hoare的“对向双指针”方法,这两类最终都可以实现快排。
示意图类似于数组操作中的结果图
【快排中“分类”代码的两种方式】
示例代码:
/*--------------------快排中的分组-------------------------*/
#include <stdio.h>
#include <iostream>
//算法导论中第七章原文中给出的方式,同侧的两个指针
void QuickSort1(int* data,int num,int r)
{
int val=data[r-1];
int* pFirst=data;
int* pSec=data;
while(pFirst<=data+r-1)
{
while(*pFirst<=val)
{
int temp=*pFirst;
*pFirst=*pSec;
*pSec=temp;
if(pFirst==data+r-1)
break;
++pFirst;
++pSec;
}
++pFirst;
}
}
//快排的初始HOARE版本,首尾的两个指针
void QuickSort2(int* data,int num,int r)
{
int val=data[r-1];
int* pBegin=data;
int* pEnd=data+r-1;
while(pBegin<=pEnd)
{
while(*pBegin<val)
++pBegin;
while(*pEnd>val)
--pEnd;
if(pBegin>=pEnd)
break;
int temp=*pBegin;
*pBegin=*pEnd;
*pEnd=temp;
}
}
int main()
{
int test[]={1,5,6,7,3,2,4};
QuickSort2(test,7,7);
return 0;
}
Author: ZSSURE
E-mail: zssure@163.com
Date: 2014-03-14