二十一、调整数组顺序使奇数位于偶数前面

题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

测试用例:

  • 功能测试:输入数组中的奇数、偶数交替出现;输入的数组中所有偶数都出现在奇数的前面;输入的数组中所有奇数都出现在偶数的前面。
  • 特殊输入测试:输入nullptr指针;输入的数组只包含一个数字。

只完成功能的解法:

void record_odd_before_even(int *pData, int length)
{
if(pData == nullptr || length == 0) return;
int *pBegin = pData;
int *pEnd = pData + length - 1;
while(pBegin < pEnd) {
if(pBegin < pEnd && !is_even(*pBegin))
pBegin++;
if(pBegin < pEnd && is_even(*pEnd))
pEnd--;
if(pBegin < pEnd) {
int temp = *pBegin;
*pBegin = *pEnd;
*pEnd = temp;
}
}
} /* 判断n是否为偶数 */
bool is_even(int n)
{
bool isEven = ((n & 0x1) == 0);
return isEven;
}

分析:上段代码在扫描传入的数组时,如果发现有偶数出现在奇数的前面,则交换它们的顺序。所以,它维护两个指针,pBegin初始化时指向数组的第一个数字,它只向后移动;第二个指针初始化时指向数组的最后一个数字,它只向前移动。

二十二、链表中倒数第K个节点

题目:输入一个链表,输出该链表中倒数第K个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。

分析:为了得到倒数第K个节点,很自然地想到先遍历链表,从而得到链表的节点数n,于是获悉倒数第K个节点就是从头节点开始的第n-k+1个节点。

遍历链表两次的解法:

ListNode* get_kth_from_tail(ListNode *pHead, unsigned int k)
{
// 鲁棒性测试点1
if(pHead == nullptr || k == 0) return nullptr;
int cnt = 0;
ListNode *pNode = pHead;
while(pNode != nullptr) { // 第一次遍历链表
pNode = pNode->next;
++cnt;
}
// 鲁棒性测试点2
if(k > cnt) return nullptr;
pNode = pHead;
while(cnt != k) { // 第二次遍历链表
pNode = pNode->next;
--cnt;
}
return pNode;
}

分析:如果面试官告诉我们只需遍历链表一次,我们应该想点办法了。可以定义两个指针,第一个指针pAhead从链表的头指针开始遍历向前走K-1步,第二个指针pBehind保持不动;从第K步开始,第二个指针pBehind也开始从链表的头指针开始遍历。由于两个指针的距离保持在k-1,当pAhead指针到达链表的尾节点时,pBehind指针正好指向倒数第K个节点。

遍历链表一次的解法:

ListNode* get_kth_from_tail(ListNode *pHead, unsigned int k)
{
if(pHead == nullptr || k == 0) return nullptr;
ListNode *pAhead = pHead;
ListNode *pAhead = pHead;
for(int i = 0; i < k - 1; ++i) { // 向前走k-1步
if(pAhead->next != nullptr)
pAhead = pAhead->next;
else // 链表节点数小于k
return nullptr;
}
while(pAhead->next != nullptr) {
pAhead = pAhead->next;
pBehind = pBehind->next;
}
return pBehind;
}

二十三、链表中环的入口节点

题目:如果一个链表中包含环,如何找出环的入口节点?

分析:①判断一个链表中是否包含环;②如果有环,就要找到环的入口。

解法:

/* 返回环的入口节点 */
ListNode* entry_node_of_loop(ListNode *pHead)
{
if(pHead == nullptr) return nullptr;
// 判断是否有环,如果有,则返回一个环中的节点
ListNode *meetNode = meet_node(pHead);
if(meetNode == nullptr) return nullptr;
// 计算环中的节点数
int nodeNumOfLoop = 1;
ListNode *pCurrent = meetNode;
while(pCurrent->next != meetNode){
pCurrent = pCurrent->next;
nodeNumOfLoop++;
}
// 找出环的入口
ListNode *pAhead = pHead;
ListNode *pBehind = pHead;
for(int i = 0; i < nodeNumOfCode; ++i) {
pAhead = pAhead->next;
}
while(pAhead != pBehind) {
pAhead = pAhead->next;
pBehind = pBehind->next;
}
return pAhead;
} /* 判断链表中是否包含环 */
/* 如果有环,则返回两个指针相遇的节点 */
ListNode meet_node(ListNode *pHead)
{
ListNode *pSlow = pHead->next; // 一次走一步
if (pSlow == nullptr) return nullptr;
ListNode *pFast = pSlow->next; // 一次走两步
while(pFast != nullptr) {
if(pSlow == pFast) // 如果相遇即有环
return pFast; // 相遇的节点一定在环中
pSlow = pSlow->next;
pFast = pFast->next;
if(pFast != nullptr)
pFast = pFast->next;
}
return nullptr;
}

小结:利用两个指针pFast和pSlow来判断链表中是否包含环,如果有,就返回它们相遇的节点,显然这个节点是环中的一个节点。根据环中的一个节点,我们就可以遍历该环以得到环中的节点数。根据节点数,结合面试题22,可以利用两个指针来得到环的入口节点。

二十四、反转链表

题目:定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

测试用例:

  • 输入的链表头指针是nullptr。
  • 输入的链表只有一个节点。
  • 输入的链表有多个节点。

解法:

ListNode* reverse_list(ListNode *pHead)
{
if(pHead == nullptr) return nullptr;
ListNode *pRevHead = nullptr; // 反转后的链表的头节点
ListNode *pPre = nullptr;
ListNode *pCur = pHead;
while(pCur != nullptr) {
ListNode *pNext = pCur->next;   // 在反转后当前节点和之后的节点会断开,故把后面的节点保存下来
if(pNext == nullptr)
pRevHead = pCur;
pCur->next = pPre;
pPre = pCur;
pCur = pNext;
}
return pRevHead;
}

分析:反转一个链表,需要调整链表中指针的方向,显然需要3个指针。其中,最重要的是保存当前要调整指针方向的节点的下一个节点pNext,即pCur->next。

拓展:用递归实现同样的反转链表的功能。  

二十五、合并两个排序的链表

题目:输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

测试用例:

  • 功能测试:输入的两个链表有多个节点;节点的值互不相同或者存在值相等的多个节点。
  • 特殊输入测试:两个链表的一个或者两个头节点为nullptr指针;两个链表中只有一个节点。

递归解法:

ListNode* Merge(ListNode *pHead1, ListNode *pHead2)
{
if(pHead1 == nullptr) return pHead2;
if(pHead2 == nullptr) return pHead1;
ListNode *pMergedHead = nullptr;
if(pHead1->value < pHead2->value) {
pMergedHead = pHead1;
pMergedHead->next = Merge(pHead1->next, pHead2);
}
else {
pMergedHead = pHead2;
pMergedHead->next = Merge(pHead1, pHead2->next);
}
return pMergedHead;
}

分析:每次比较两个链表的头节点,较小的被加入到合并的链表中,并更新原来链表的头节点,然后再次比较两个链表的头节点。本题需要注意的是如果传入的是空链表时,我们应该如何处理。

二十六、树的子结构

题目:输入两棵二叉树A和B,判断B是不是A的子结构。

分析:要查找树A中是否存在和树B一样的子树,需要①在树A中找到和树B的根节点的值一样的节点R,②判断树A中以R为根节点的子树是不是包含和树B一样的结构。

测试用例:

  • 功能测试:树B是或者不是树A的子结构。
  • 特殊输入测试:两棵二叉树的一个或者两个根节点为nullptr指针;二叉树的所有节点都没有左子树或者右子树。

二十七、二叉树的镜像

题目:请完成一个函数,输入一棵二叉树,该函数输出它的镜像。

题意:求树的镜像的过程,其实就是在遍历树的同时交换非叶节点的左、右子节点。具体来说,就是先前序遍历这棵树的每个节点,如果遍历到的节点有子节点,就交换它的两个子节点。

测试用例:

  • 功能测试:普通的二叉树;二叉树的所有节点都没有左子树或者右子树;只有一个节点的二叉树。
  • 特殊输入测试:二叉树的根节点为nullptr指针。

解法:

void mirror_recursive(BinaryTreeNode *pRoot)
{
if(pRoot == nullptr) return;
if(pRoot->lChild == nullptr && pRoot->rChild == nullptr) return;
BinaryTreeNode *pTemp = pRoot->lChild;
pRoot->lChild = pRoot->rChild;
pRoot->rChild = pTemp;
if(pRoot->lChild) mirror_recursive(pRoot->lChild);
if(pRoot->rChild) mirror_recursive(pRoot->rChild);
}

扩展:上面的代码是用递归实现的。如果要求用循环,则该如何实现?

  

二十八、对称的二叉树

题目:请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。

分析:可以先得到该二叉树的镜像,然后来比较该镜像和原二叉树是否相同,但上题只是完成二叉树的镜像,因为我还不会如何去复制一个二叉树,并且这个方法的效率也不高。

前序遍历和自定义的对称前序遍历解法:

bool is_symmetrical(BinaryTreeNode *pRoot)
{
return is_symmetrical(pRoot, pRoot);
} bool is_symmetrical(BinaryTreeNode *pRoot1, BinaryTreeNode *pRoot2)
{
if(pRoot1 == nullptr && pRoot2 == nullptr) return true;
if(pRoot1 == nullptr || pRoot2 == nullptr) return false;
if(pRoot1->value != pRoot2->value) return false;
return is_symmetrical(pRoot1->lChild, pRoot2->rChild) && is_symmetrical(pRoot1->rChild, pRoot2->lChild);
}

小结:针对前序遍历定义一种对称的遍历算法,即先遍历父节点,再遍历它的右子节点,最后遍历它的左子节点。我们可以通过比较二叉树的前序遍历序列和对称前序遍历序列来判断二叉树是不是对称的。  

二十九、顺时针打印矩阵

题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

分析:此题可借助图形来帮助思考,我们可以把矩阵想象成若干个圈。我们可以用一个循环来打印矩阵,每次打印矩阵中的一个圈。要注意的是,我们应理清循环继续的条件以及打印时每一步的前提条件。

解法:

void PrintMatrixClockwisely(int** numbers, int columns, int rows)
{
if(numbers == nullptr || columns <= 0 || rows <= 0)
return; int start = 0; while(columns > start * 2 && rows > start * 2)
{
PrintMatrixInCircle(numbers, columns, rows, start); ++start;
}
} void PrintMatrixInCircle(int** numbers, int columns, int rows, int start)
{
int endX = columns - 1 - start;
int endY = rows - 1 - start; // 从左到右打印一行
for(int i = start; i <= endX; ++i)
{
int number = numbers[start][i];
printNumber(number);
} // 从上到下打印一列
if(start < endY)
{
for(int i = start + 1; i <= endY; ++i)
{
int number = numbers[i][endX];
printNumber(number);
}
} // 从右到左打印一行
if(start < endX && start < endY)
{
for(int i = endX - 1; i >= start; --i)
{
int number = numbers[endY][i];
printNumber(number);
}
} // 从下到上打印一行
if(start < endX && start < endY - 1)
{
for(int i = endY - 1; i >= start + 1; --i)
{
int number = numbers[i][start];
printNumber(number);
}
}
} void printNumber(int number)
{
printf("%d\t", number);
}

三十、包含min函数的栈  

题目:定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的min函数。在该栈中,调用min、push及pop的时间复杂度都是O(1)。

分析:初看本题时,会发现无从下手。既要push/pop的时间为O(1),又要min的时间为O(1),看上去像是不可能的事。但可以借助两个栈来实现本题的要求,其中数据栈实现push/pop的功能,而辅助栈实现min的功能,而关键之处是维持辅助栈的内容。

解法:

stack<int> dataSck;			// 数据栈
stack<int> minSck; // 辅助栈 void push(int val)
{
dataSck.push(val);
if(!minSck.empty()) {
if(val < minSck.top()) minSck.push(val);
else minSck.push(minSck.top());
}
else {
minSck.push(val);
}
} void pop()
{
assert(dataSck.size() > 0 && minSck.size() > 0);
dataSck.pop();
minSck.pop();
} void min()
{
assert(dataSck.size() > 0 && minSck.size() > 0);
return minSck.top();
}

小结:①把每次的最小元素(之前的最小元素和新压入栈的元素两者的较小值)都保存起来放到辅助栈中,要保证数据栈和辅助栈的元素个数一致;②保证辅助栈的栈顶一直都是最小元素;③要想到这个思路,其实只要举个例子,多做几次入栈、出栈的操作就能看出问题,并想到也要把最小元素用另外的辅助栈保存。

  

《剑指Offer》题二十一~题三十的更多相关文章

  1. 剑指Offer(二十一):栈的压入、弹出序列

    剑指Offer(二十一):栈的压入.弹出序列 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.net/b ...

  2. 《剑指offer》第十一题(旋转数组的最小数字)

    // 面试题:旋转数组的最小数字 // 题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转. // 输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素.例如数组 // {3, ...

  3. 《剑指offer》第二十一题(调整数组顺序使奇数位于偶数前面)

    // 面试题21:调整数组顺序使奇数位于偶数前面 // 题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有 // 奇数位于数组的前半部分,所有偶数位于数组的后半部分. #inclu ...

  4. 刷题-力扣-剑指 Offer 15. 二进制中1的个数

    剑指 Offer 15. 二进制中1的个数 题目链接 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de- ...

  5. 剑指offer:二维数组中的查找

    目录 题目 解题思路 具体代码 题目 题目链接 剑指offer:二维数组中的查找 题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺 ...

  6. 剑指Offer(二十二):从上往下打印二叉树

    剑指Offer(二十二):从上往下打印二叉树 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.net/b ...

  7. 剑指Offer(二十五):复杂链表的复制

    剑指Offer(二十五):复杂链表的复制 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.net/bai ...

  8. 【剑指Offer】二叉搜索树的第k个结点 解题报告(Python)

    [剑指Offer]二叉搜索树的第k个结点 解题报告(Python) 标签(空格分隔): 剑指Offer 题目地址:https://www.nowcoder.com/ta/coding-intervie ...

  9. 剑指Offer(二十六):二叉搜索树与双向链表

    剑指Offer(二十六):二叉搜索树与双向链表 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.net/ ...

  10. 剑指Offer(二十八):数组中出现次数超过一半的数字

    剑指Offer(二十八):数组中出现次数超过一半的数字 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn. ...

随机推荐

  1. URL参数获取/转码

    JS中对URL进行转码与解码 1.escape 和 unescape escape()不能直接用于URL编码,它的真正作用是返回一个字符的Unicode编码值. 采用unicode字符集对指定的字符串 ...

  2. js的单例

     对于 JS 来说,巨大的灵活性使得其可以有多种方式实现单例模式,使用闭包方式来模拟私有数据,按照其思路可得: var single = (function(){ var unique; functi ...

  3. 网页股票期货历史数据(API)

    //[和讯数据] //大商所DCE.郑商所CZCE.上期所SHFE3.中金所CFFEX //期货1分钟线http://webftcn.hermes.hexun.com/ ... I1709&d ...

  4. Elasticsearch 索引操作

    一.创建 语法: PUT /索引库名称 { "settings": { "number_of_shards": 分片数量, "number_of_re ...

  5. 月薪30-50K的大数据工程师们,他们背后是如何学习的

    ​ 这两天小编去了解了下大数据开发相关职位的薪资,主要有hadoop工程师,数据挖掘工程师.大数据算法工程师等,从平均薪资来看,目前大数据相关岗位的月薪均在2万以上,随着项目经验的增长工资会越来越高. ...

  6. WIN10远程连接winserver2012 r2,连接失败

    背景:2012开启远程的时候,默认是勾选“仅允许运行使用网络级别身份验证的远程桌面的计算机连接”,这个选项据说比较安全,但是用win10远程的时候就报错,函数不受支持,最后通过修改win10的配置得以 ...

  7. Learning notes | Data Analysis: 1.2 data wrangling

    | Data Wrangling | # Sort all the data into one file files = ['BeijingPM20100101_20151231.csv','Chen ...

  8. Grep/find查找文件

    1. 查找secret 函数所在的文件位置grep -rn secret * grep -rn "secret" * 2. find 查找当前目录下,比while2 时间新并且名字 ...

  9. 【8086汇编-Day7】关于多个段的程序的实验

    实验一 实验二 实验三 实验四 实验五 实验六 总结 在集成环境下,内存从0770段开始按照段的先后顺序和内容多少分配,并且分配的都是16的倍数 关于实际占用的空间公式的话其实极容易想到(假设有N个字 ...

  10. 如何使用GeoServer发布地图

    本文所采用的系统为Windows 10 64bit操作系统,使用FireFox浏览器 一.安装配置Java的SDK 1. 安装JavaDevelopment Kit (JDK) 8,java开发环境, ...