这周做了一些牛客网上剑指offer的题.选取几道个人觉得比较典型,有意思的题拿出来进行分析.
1.链表中环的入口节点
一个链表中包含一个环,请找出这个环的入口节点.
分析:一般答案不那么显然的链表题,大多都可以用这种方法解决:派出两个指针干活.这道题也是如此,具体是派出快慢2个指针同时出发,快指针一次走两步,慢指针一次走一步,找出他们第一次相遇的位置,然后就比较容易操作了,之后就是这是一个小学数学的行程问题了,这里就不多赘诉,指针的题需要注意下临界条件,这道题还好.
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* head){
if(head == NULL || head->next ==NULL || head->next->next == NULL)
return NULL;
//派出快慢两个指针,进行第一次相遇
ListNode *slow = head->next;
ListNode *fast = head->next->next;
while(slow != fast){
if (fast->next->next != NULL){
slow = slow->next;
fast = fast->next->next;
}//判断是否是环,其实如果题目出的正确的话可以不用这个条件判断
else
return NULL;
}
//指针相遇后重新派出两个速度一样的指针再度相遇,相遇后的位置便是环的入口
ListNode *p1 = head;
ListNode *p2 = slow;
while (p1 != p2){
p1 = p1->next;
p2 = p2->next;
}
return p1;
}
};
2.删除链表中重复的节点
题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,结果返回链表头指针。
分析:乍看之下是一道思路非常明确很温柔的题,实际上这道题坑了我很久才弄出来,说不定有更简单的思路(不包括重新建一个链表哈,只能在原始链表上操作),不过我暂时懒得去看答案了,心累…具体思路也是需要排出两个指针,前锋负责探路找出重复的节点,殿后的就负责把删除节点后的链表重新拼接起来,同时还需要设定一个判断是否是重复节点bool值…还是直接上程序吧,这道题的细节操作和临界处理上都花了我不少时间,很难语言描述清楚.
class Solution {
public:
ListNode* deleteDuplication(ListNode* head){
if (head == NULL|| head->next == NULL)
return head;
//在链表表头加了一个新的head,为了处理临界情况
ListNode* headNew = new ListNode(INT_MIN);
headNew->next = head;
ListNode* p1 = headNew;
ListNode* p2 = headNew->next;
bool isSame = false;
//核心操作步奏
while(p2->next != NULL){
if((p2->val != p2->next->val) && (isSame == false)){
p1 = p1->next;
p2 = p2->next;
}
else if (p2->val == p2->next->val){
p2 = p2->next;
isSame = true;
}
else{
p2 = p2->next;
p1->next = p2;
isSame = false;
}
}
//收尾的临界情况,漏了也会报错
if (isSame == true)
p1->next = NULL;
return headNew->next;
}
};
补充.后来看了下人家的代码,用的是递归,递归就简单多了,不管是思想上还是代码上都更简洁,下面给出递归的代码:
class Solution {
public:
ListNode* deleteDuplication(ListNode* head){
if(head == NULL || head->next == NULL)
return head;
ListNode *p = head;
if(p->next->val == p->val){
p = p->next->next;
while(p && p->val == head->val)
p = p->next;
return deleteDuplication(p);
}
head->next = deleteDuplication(p->next);
return head;
}
};
3.二叉树的下一个节点
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
分析: 1.若该节点包含右子树,那么它的下一个节点必然是这个右子树中的最左边的节点.2.若该节点无右子树,那么它的下一个节点只能是它的父节点或往上的节点.这时候又要分类:如果它正好是它父节点的左子节点,那么它的父节点就是它的下一个节点,如果不是,那么只能继续向上回溯.
class Solution {
public:
TreeLinkNode* GetleftNode(TreeLinkNode* Node){
if (Node->left == NULL)
return Node;
else
return GetleftNode(Node->left);
}
TreeLinkNode* GetRightDadNode(TreeLinkNode* Node){
if (Node->next == NULL)
return NULL;
if (Node == Node->next->left)
return Node->next;
else
return GetRightDadNode(Node->next);
}
TreeLinkNode* GetNext(TreeLinkNode* pNode)
{
if(pNode == NULL)
return pNode;
if (pNode->right != NULL){
return GetleftNode(pNode->right);
}
else{
return GetRightDadNode(pNode);
}
}
};
4.序列化二叉树
请实现两个函数,分别用来序列化和反序列化二叉树
分析:这题采用的是前序遍历,对于空节点处用0xFFFFFFFF进行补足,思想是非常简单的递归,但是反序列化的具体操作却不是那么容易,个人认为是二叉树的递归思想中难度最高的题之一了.这道题是看的答案,这里盗一波人家的代码.
class Solution {
public:
vector<int> buf;
void dfs1(TreeNode *root) {
if(!root) buf.push_back(0xFFFFFFFF);
else {
buf.push_back(root->val);
dfs1(root->left);
dfs1(root->right);
}
}
TreeNode* dfs2(int* &p) {
if(*p==0xFFFFFFFF) {
p++;
return NULL;
}
TreeNode* res =new TreeNode(*p);
p++;
res->left=dfs2(p);
res->right=dfs2(p);
return res;
}
char* Serialize(TreeNode *root) {
buf.clear();
dfs1(root);
int bufSize=buf.size();
int *res=new int[bufSize];
for(int i=0;i<bufSize;i++) res[i]=buf[i];
return (char*)res;
}
TreeNode* Deserialize(char *str) {
int *p=(int*)str;
return dfs2(p);
}
};
5.数据流中的中位数
如何得到一个数据流中的中位数?
分析:输入一个数据流,实时的计算出数据流的中位数.可以分别建立一个大顶堆和小顶堆,使得二者各存一半的数据,且大顶堆中的最大值小于等于小顶堆中的最小值,这样的话中位数始终都是从大顶堆和小顶堆中的top处诞生的,如数据流[1,3,2,4],输入后在大顶堆中就是[2,1],在小顶堆中就是[3,4],所以中位数就是(2+3)/2=2.5.具体操作见程序.为什么要用大顶堆和小顶堆?因为在给定的排序数组中插入数字可以用顶堆的思想达到O(logn)的复杂度.(我怎么感觉二分法也是logn的复杂度,估计是理解不够深刻…)
class Solution {
public:
priority_queue<int, vector<int>, less<int> > l;
priority_queue<int, vector<int>, greater<int> > g;
int k = 0;
void Insert(int num)
{
if (k%2 == 0)
l.push(num);
else{
if(num > l.top())
g.push(num);
else{
int temp = l.top();
l.pop();
l.push(num);
g.push(temp);
}
}
k++;
}
double GetMedian()
{
if (k==0) return 0;
if (k == 1) return l.top();
if (k%2 == 1){
if (l.top() <= g.top())
return l.top();
else
return g.top();
}
else
return (float(l.top() + g.top())/2);
}
};
6.机器人的运动范围
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
分析:简单来说就是从起点出发,每次递归地向四个方向前进,如果在某个方向遇到停止条件,那么便在这个方向上便停止,直到所有前进的路劲都达到停止条件…
class Solution {
public:
int bitSum (int qqq){
int t = 0;
while(qqq != 0){
t = t + qqq%10;
qqq = qqq/10;
}
return t;
}
int count(int thr, int i, int j, int rows, int cols,vector<vector<int>>& vis){
if(i<0 || j<0 || i>=rows || j >= cols || vis[i][j] || (bitSum(i)+bitSum(j)> thr))
return 0;
else{
vis[i][j] = 1;
return(1+count(thr, i+1, j, rows, cols, vis)+
count(thr, i, j+1, rows, cols, vis)+
count(thr, i-1, j, rows, cols, vis)+
count(thr, i, j-1, rows, cols, vis));
}
}
int movingCount(int threshold, int rows, int cols){
vector<vector<int>> vis(rows, vector<int>(cols, 0));
int res = count(threshold, 0, 0, rows, cols, vis);
return res;
}
};