第13题:调整数组顺序使奇数位于偶数前面
题目
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
解析
如果这题没有加粗的这个条件,这是一个原版微软笔试题。设置
两个指针
head和tail,分别指向首尾,head向后移动,直到找到第一个偶数,tail向前移动,直到找到第一个奇数,交换这两个数,重复上述过程,直到head和tail相等为止;
时间复杂度: O ( n ) O(n) O(n);
空间复杂度: O ( 1 ) O(1) O(1)。
class Solution {
public:
void reArray(vector<int> &array) {
int head = 0, tail = array.size() - 1;
while (head < tail) {
// head指向偶数
for (; head < tail && array[head] % 2; head++);
// tail指向奇数
for (; head < tail && !(array[tail] % 2); tail--);
// swap交换两个两个元素内存地址
if (head < tail)
swap(array[head++], array[tail--]);
}
}
};
现在我们再来考虑加粗的条件,首先肯定是最直白的算法,统计一下奇数的数量,然后开一个
辅助数组
,扫描原数组,发现奇数就往辅助数组的前面放,发现偶数就往辅助数组“后面”放,最后另原数组等于辅助数组就行了。
时间复杂度: O ( n ) O(n) O(n)
class Solution {
public:
void reOrderArray(vector<int> &array) {
// cnt:奇数个数
int cnt = 0;
// 计算cnt
for (int i = 0; i < (int)array.size(); cnt += array[i++] % 2);
// 辅助数组
vector<int> arr(array);
// 根据奇偶,赋值
for (int i = 0, j = cnt, id = 0; id < (int)array.size(); id++)
array[id] % 2 ? arr[i++] = array[id] : arr[j++] = array[id];
array.swap(arr);
}
};
要解决这个特殊的排序算法就要用稳定排序算法,sort函数是不稳定排序,但是我们可以
把sort函数改装成稳定排序
,通过增加一个下标,然后利用结构体的二级排序可以搞定。
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( 2 n ) O(2n) O(2n)
class Solution {
public:
void reOrderArray(vector<int> &array) {
vector<pair<int, int> > arr;
for (int i = 0; i < (int)array.size(); i++)
arr.emplace_back(array[i], i);
sort(arr.begin(), arr.end(), cmp);
for (int i = 0; i < (int)array.size(); array[i] = arr[i].first, ++i);
}
static bool cmp(const pair<int, int> &A, const pair<int, int> &B)
{
// 如果A是奇数,B是偶数,返回True. (即奇数在前,偶数在后)
if (A.first % 2 && !(B.first % 2))
return true;
// 否则,保持A在前B在后的顺序
return A.second < B.second;
}
};
采用STL中内置了稳定排序算法
stable_sort
class Solution {
public:
void reOrderArray(vector<int> &array) {
stable_sort(array.begin(), array.end(), cmp);
}
static bool cmp(const int &A, const int &B)
{
return (A % 2 && !(B % 2));
}
};
备注
各种排序算法,稳定排序,sort, stable_sort函数
第14题:链表中倒数第k个结点
题目
输入一个链表,输出该链表中
倒数第k个
结点。
详解
前后指针法
,让第一个指针先向前走k-1步,然后第二个指针和第一个指针同时向后走,直到第一个指针后面没有结点,第二个指针指向的就是第k个结点。
不过要注意,如果链表的结点个数少于k,就要返回空指针。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
// pre为前,ret为后
auto pre = pListHead, ret = pre;
// pre向前k-1步,指向第k个节点
for (int i = 0; i < k - 1 && pre != nullptr; ++i, pre = pre->next);
// 若pre为空指针,则少于k个节点,返回空
if (pre == nullptr)
return nullptr;
// 前后指针同时后移
for (; pre->next != nullptr; ret = ret->next, pre = pre->next);
return ret;
}
};
第15题:反转链表
题目
输入一个链表,反转链表后,输出链表的所有元素。
解析
递归方法
,将第一个之后的链表翻转,再指向第一个
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
//如果只有一个或0个节点
if (pHead == nullptr || pHead->next == nullptr)
return pHead;
// 递归处理后面的链表
auto pre = ReverseList(pHead->next);
// 翻转的后面的再指向第一个
pHead->next->next = pHead;
//第一个指向空指针
pHead->next = nullptr;
return pre;
}
};
第二种做法就是非递归做法,利用链表的
头插法
会得到反序的链表这一性质来做。
具体做法是:提取头结点,然后遍历链表,取出的每一个结点,用头插法插入头结点。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
if (pHead == nullptr)
return nullptr;
auto ret = pHead, pre = ret->next;
ret->next = nullptr;
for (auto tmp = ret; pre != nullptr;
tmp = pre->next, pre->next = ret, ret = pre, pre = tmp);
return ret;
}
};
第16题
题目:合并两个排序的链表
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
解析
递归做法:两个链表中,令目标头结点指向头结点小的链表,然后合并剩下的链表,最后令目标头结点指向合并链表
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
// 若其中一个链表为空,返回另一个
if (pHead1 == nullptr)
return pHead2;
if (pHead2 == nullptr)
return pHead1;
// 递归合并
return pHead1->val > pHead2->val ? (pHead2->next = Merge(pHead1, pHead2->next), pHead2) :
(pHead1->next = Merge(pHead1->next, pHead2), pHead1);
}
};
第17题:树的子结构
题目
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)。
解析
递归法
。
首先,判断B是不是A的子结构,然后再判断B是不是A的左子树的子结构,B是不是A的右子树的子结构;
编写isTheSameTree
函数时要注意下面这两个判断条件:
pRoot2 == nullptr
pRoot1 == nullptr && pRoot2 != nullptr
。
两个条件都不满足保证了两个指针都不为空。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
{
// 若为空树,无子结构
if (pRoot1 == nullptr || pRoot2 == nullptr)
return false;
// 判断当前是否与pRoot2相同,或子树是否包含pRoot2
return isTheSameTree(pRoot1, pRoot2) ||
HasSubtree(pRoot1->left, pRoot2) || HasSubtree(pRoot1->right, pRoot2);
}
// 判断两个树是否相同
bool isTheSameTree(TreeNode* pRoot1, TreeNode* pRoot2)
{
// pRoot2为nullptr,pRoot1可以不为空
if (pRoot2 == nullptr)
return true;
if (pRoot1 == nullptr && pRoot2 != nullptr)
return false;
// 根节点相同,且左右子树相同
return pRoot1->val == pRoot2->val &&
isTheSameTree(pRoot1->left, pRoot2->left) && isTheSameTree(pRoot1->right, pRoot2->right);
}
};
备注
子树
的意思是只要包含了一个结点,就得包含这个结点下的所有节点.
子结构
的意思是包含了一个结点,可以只取左子树或者右子树,或者都不取。
简单而言,与子树不同的是,子结构可以是A树的任意一部分。
第18题:二叉树的镜像
题目
操作给定的二叉树,将其变换为源二叉树的镜像。
解析
还是
递归
求解,把根节点的左右子树都转化成镜像二叉树,然后再交换左右指针就行了。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
void Mirror(TreeNode *pRoot) {
// 空指针,返回
if (pRoot == nullptr)
return ;
// 左右子树分别镜像
Mirror(pRoot->left);
Mirror(pRoot->right);
//交换左右子树
TreeNode *tmp = pRoot->left;
pRoot->left = pRoot->right;
pRoot->right = tmp;
}
};