面试题51:数组中的逆序对
在数组中的两个数字如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
分成长度1的子数组,在合并之前统计相邻子数组之间的逆序对个数,然后让它们升序合并,升序的最大的一定在最后面,然后再如此反复即可。具体图解见书上。
#include<bits/stdc++.h>
using namespace std;
// 参数:
// data: 一个整数数组
// copy: 复制数组
// start: 操作起点下标
// end: 操作终点下标
// 返回值:
// data数组中从start到end位置逆序对的总数
int InversePairsCore(int* data, int* copy, int start, int end) {
if(start == end) {//递归出口:子数组中仅有一个数字
copy[start] = data[start];//将其直接拷贝到copy数组
return 0;//只有一个数字,一定不包含逆序对
}
int length = (end - start) / 2;//二分数组,子数组的长度
//左侧部分/右侧部分分别递归计算:反过来调用则最终有序的在data数组里
int left = InversePairsCore(copy, data, start, start + length);
int right = InversePairsCore(copy, data, start + length + 1, end);
//i初始化为前半段最后一个数字的下标(前半段最大数)
int i = start + length;
//j初始化为后半段最后一个数字的下标(后半段最大数)
int j = end;
int indexCopy = end;//要复制到的copy数组的位置下标
int count = 0;//比较过程中发现的逆序对数目
//两个子数组中都有数字未复制完
while(i >= start && j >= start + length + 1) {
if(data[i] > data[j]) {//左侧数组中的数更大
copy[indexCopy--] = data[i--];//复制到copy数组中
//右侧子数组中剩余的所有数字都和左侧数组中的这个数字构成逆序对
count += j - start - length;//注意start+length才是分割点的位置下标
} else {//右侧数组中的数更大
copy[indexCopy--] = data[j--];
}
}
//上面的&&条件有一边不满足就终止,这时左右有一个数组还没复制完
for(; i >= start; --i)//左侧数组剩余复制完
copy[indexCopy--] = data[i];
for(; j >= start + length + 1; --j)//右侧数组剩余复制完
copy[indexCopy--] = data[j];
//左侧逆序对数+右侧逆序对数+本批计算得逆序对数
return left + right + count;
}
// 参数:
// data: 一个整数数组
// length: 数组的长度
// 返回值:
// data数组中逆序对的总数
int InversePairs(int* data, int length) {
//空数组或者长度不合法,这里应用<=0而不是<0
if(data == nullptr || length <= 0)
return 0;
//深拷贝data数组到copy数组中
int* copy = new int[length];
for(int i = 0; i < length; ++i)
copy[i] = data[i];
//调用统计逆序对数目的递归函数
int count = InversePairsCore(data, copy, 0, length - 1);
delete[] copy;
return count;
}
int main() {
int data[] = { 1, 2, 3, 4, 7, 6, 5 };
cout<<InversePairs(data,7)<<endl;//3
return 0;
}
面试题52:两个链表的第一个公共结点
输入两个链表,找出它们的第一个公共结点。
书上三种方法:
①在第一个链表上每遍历到一个点就在第二个链表上遍历一遍。
②用两个栈,两个链表上边遍历边压栈,然后同步出栈。
③先各自遍历一遍,然后就知道两个链表长度和长度差。在长的链表上先遍历这么多步,然后同步遍历直到指针重合。
#include<bits/stdc++.h>
#include"../Utilities/List.h"
using namespace std;
//遍历获得链表长度
unsigned int GetListLength(ListNode* pHead) {
unsigned int nLength = 0;
ListNode* pNode = pHead;
while(pNode != nullptr) {
++nLength;
pNode = pNode->m_pNext;
}
return nLength;
}
//寻找两个链表的第一个公共结点
ListNode* FindFirstCommonNode(ListNode *pHead1, ListNode *pHead2) {
//得到两个链表的长度
unsigned int nLength1 = GetListLength(pHead1);
unsigned int nLength2 = GetListLength(pHead2);
int nLengthDif = nLength1 - nLength2;//长度差,即先行步数
//默认链1长,链2短
ListNode* pListHeadLong = pHead1;
ListNode* pListHeadShort = pHead2;
//如果链2长
if(nLength2 > nLength1) {
//反过来
pListHeadLong = pHead2;
pListHeadShort = pHead1;
nLengthDif = nLength2 - nLength1;
}
//先在长链表上走nLengthDif步
for(int i = 0; i < nLengthDif; ++i)
pListHeadLong = pListHeadLong->m_pNext;
//然后同时在两个链表上遍历:只要长短链都没遍历完,且还没找到公共结点
while((pListHeadLong != nullptr) &&
(pListHeadShort != nullptr) &&
(pListHeadLong != pListHeadShort)) {
//同步指针向下走
pListHeadLong = pListHeadLong->m_pNext;
pListHeadShort = pListHeadShort->m_pNext;
}
//返回得到的第一个公共结点,如果没有自然返回nullptr也没问题
ListNode* pFisrtCommonNode = pListHeadLong;
return pFisrtCommonNode;
}
// 1 - 2 - 3 \
// 6 - 7
// 4 - 5 /
int main() {
ListNode* pNode1 = CreateListNode(1);
ListNode* pNode2 = CreateListNode(2);
ListNode* pNode3 = CreateListNode(3);
ListNode* pNode4 = CreateListNode(4);
ListNode* pNode5 = CreateListNode(5);
ListNode* pNode6 = CreateListNode(6);
ListNode* pNode7 = CreateListNode(7);
ConnectListNodes(pNode1, pNode2);
ConnectListNodes(pNode2, pNode3);
ConnectListNodes(pNode3, pNode6);
ConnectListNodes(pNode4, pNode5);
ConnectListNodes(pNode5, pNode6);
ConnectListNodes(pNode6, pNode7);
cout<<FindFirstCommonNode(pNode1,pNode4)->m_nValue<<endl;//6
DestroyList(pNode1);
DestroyList(pNode4);
return 0;
}