原题链接:
- 第一题:调整数组顺序使奇数位于偶数前面;
- 第二题:链表中倒数第k个结点;
- 第三题:反转链表;
- 第四题:合并两个排序的链表;
- 第五题:树的子结构;
- 第六题:二叉树的镜像;
第一题:调整数组顺序使奇数位于偶数前面
题目:
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
解析:
如果这题没有加粗的这个条件,这是一个原版微软笔试题,我也来说下做法,设置两个指针
head
和tail
,分别指向首尾,head
向后移动,直到找到第一个偶数,tail
向前移动,直到找到第一个奇数,交换这两个数,重复上述过程,直到head
和tail
相等为止;时间复杂度: O(n) ;
空间复杂度: O(1) 。
class Solution {
public:
void reArray(vector<int> &array) {
int head = 0, tail = array.size() - 1;
while (head < tail) {
for (; head < tail && array[head] % 2; head++);
for (; head < tail && !(array[tail] % 2); tail--);
if (head < tail)
swap(array[head++], array[tail--]);
}
}
};
现在我们再来考虑加粗的条件,首先肯定是最直白的算法,统计一下奇数的数量,然后开一个辅助数组,扫描原数组,发现奇数就往辅助数组的前面放,发现偶数就往辅助数组“后面”放,最后另原数组等于辅助数组就行了。
时间复杂度: O(n) ;
空间复杂度: O(n) 。
class Solution {
public:
void reOrderArray(vector<int> &array) {
int cnt = 0;
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(nlogn) ;
空间复杂度: 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)
{
if (A.first % 2 && !(B.first % 2))
return true;
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));
}
};
既然稳定排序可以解决这个问题,那么仿冒泡排序,和仿插入排序这两种排序算法也是可以搞定这个问题的。
时间复杂度: O(n2) ;
空间复杂度: O(1) 。下面依次是仿冒泡排序和仿插入排序的代码:
class Solution {
public:
void reOrderArray(vector<int> &array) {
for (int i = 0; i < (int)array.size(); i++)
for (int j = 0; j < (int)array.size() - i - 1; j++)
if (!(array[j] % 2) && array[j + 1] % 2)
swap(array[j], array[j + 1]);
}
};
class Solution {
public:
void reOrderArray(vector<int> &array) {
for (int i = 1; i < (int)array.size(); i++)
if (array[i] % 2)
for (int j = i - 1; j >= 0 && !(array[j] % 2);
swap(array[j], array[j + 1]), j--);
}
};
第二题:链表中倒数第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) {
auto pre = pListHead, ret = pre;
for (int i = 0; i < k - 1 && pre != nullptr; ++i, pre = pre->next);
if (pre == nullptr)
return nullptr;
for (; pre->next != nullptr; ret = ret->next, pre = pre->next);
return ret;
}
};
第三题:反转链表
题目:
输入一个链表,反转链表后,输出链表的所有元素。
解析:
有两种做法;
第一种做法是递归做法,递归做法代码都比较短,比较好理解;
想法就是把头结点后面的链表都反转了,然后把头结点接在最后就好了,递归做法就是你是怎么想的,代码就怎么写。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
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;
}
};
第四题:合并两个排序的链表
题目:
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
解析:
同上题一样,也是有递归做法和非递归做法。
递归做法:两个链表中,哪个头结点的值小就返回哪个头结点,然后合并剩下的链表,最后令头结点的值较小的那个头结点的
next
指向合并后的剩余链表,一样的,怎么想的,代码就怎么写。
/*
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);
}
};
第五题:树的子结构
题目:
输入两棵二叉树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;
return isTheSameTree(pRoot1, pRoot2) ||
HasSubtree(pRoot1->left, pRoot2) || HasSubtree(pRoot1->right, pRoot2);
}
bool isTheSameTree(TreeNode* pRoot1, TreeNode* pRoot2)
{
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);
}
};
第六题:二叉树的镜像
题目:
操作给定的二叉树,将其变换为源二叉树的镜像。
解析:
还是递归求解,把根节点的左右子树都转化成镜像二叉树,然后再交换左右指针就行了。
/*
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;
}
};