99.Reorder List
题目描述
给定一个单链表L : L0 -> L1 -> L2 … -> Ln,将其重新排序,变成L0->Ln->L2->Ln-2…这样的形式。要求不能通过改变节点的值来实现。
思路
这是一道链表的综合性题目,考察了许多链表相关操作的小知识。为了实现题目的要求,我们按照如下3步来实现:
Step1.O(n)下找到链表的中点mid(奇数长度则为中间的那一个点,偶数长度则为第左边的那一点)
Step2.反转mid之后的链表。
Step3.将前面的链表和反转之后的链表用merge的方法拼接起来。
直接上代码,具体细节都写在代码里。
代码
void reorderList(ListNode * head) {
// write your code here
if (NULL == head || head->next == NULL) return;
if (head == NULL) return;
ListNode *L = new ListNode(-1);
L->next = head;
/*use two pointers to find the middle of the list.
**此处有一个关于slow指针和fast指针初始化的细节,我看九章上slow初始化为head,fast初始化为head,实际上和我的一样,那么循环结束后slow得到的就是(如果是偶数长度)左边的那个点。即L0,L1,..Ln-1,最终得到L(n/2)是终点。
*/
ListNode *slow, *fast;
slow = fast = L;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
//reverse the list start from slow->next to the end.
//反转链表的某一部分同样是面试时经常会被问到的一个知识点。根据前一篇的Blog,我掌握了两种反转链表的方法。这里采用的是第2种。
ListNode *curr = slow->next;
ListNode *p = curr->next;
while (curr->next) {
ListNode *p = curr->next;
curr->next = p->next;
p->next = slow->next;
slow->next = p;
}
ListNode *l = L->next, *r = slow->next;
slow->next = NULL;
p = L;
//最后采用merge two linkedlist的方法来拼接两个链表,注意到我之前令slow->next = NULL意指把该链表一切为二然后处理。
bool first = true;
while (l || r) {
if (first) {
p->next = l;
l = l->next;
} else {
p->next = r;
r = r->next;
}
first = !first;
p = p->next;
}
if (l) {
p->next = l;
}
}
总结
需要研究、掌握的一道题,融合了许多链表的重要操作。
98.Sort List
题目描述
使用Merge Sort和Quick Sort的思想实现链表的排序。
思路
好题!考察了排序思想在链表情况下的实现,对于加深对Merge Sort和Quick Sort的理解有很好的作用。
代码
Version I : Quick Sort.
ListNode * findMid(ListNode *head) {
ListNode *slow = head, *fast = head->next;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
ListNode *getTail(ListNode *head) {
if (head->next == NULL) return head;
return getTail(head->next);
}
ListNode * concat(ListNode *left, ListNode *middle, ListNode *right) {
ListNode *L = new ListNode(-1);
ListNode *tail = L;
tail->next = left;
if(left)
tail = getTail(left);
tail->next = middle;
tail = getTail(middle);
tail->next = right;
return L->next;
}
ListNode * quickSort(ListNode *head) {
if (head == NULL || head->next == NULL) return head;
ListNode *mid = findMid(head);
//选定链表的中间节点作为pivot,如何将链表划分为左边小于pivot,右边是大于pivot的呢?这里这个思路很好,就是分别用两个链表来代表小于pivot的节点和大于pivot的节点,然后遍历原链表一遍,采用尾插法分别将节点插入到两个链表其中之一。这个思想在某些排序题中我也用过,不过此处没有想到,需要注意一下。
ListNode *ldummy = new ListNode(0);
ListNode *mdummy = new ListNode(0);
ListNode *rdummy = new ListNode(0);
ListNode *ltail, *rtail, *mtail;
ltail = ldummy, rtail = rdummy, mtail = mdummy;
for (ListNode *i = head; i != NULL; i = i->next) {
if (i->val < mid->val) {
ltail->next = i;
ltail = i;
} else if (i->val > mid->val) {
rtail->next = i;
rtail = i;
} else {
mtail->next = i;
mtail = i;
}
}
ltail->next = NULL;
rtail->next = NULL;
mtail->next = NULL;
ListNode *left = quickSort(ldummy->next);
ListNode *right = quickSort(rdummy->next);
return concat(left, mdummy->next, right);
}
ListNode * sortList(ListNode * head) {
// write your code here;
return quickSort(head);
}
Version II: Merge Sort
ListNode * sortList(ListNode * head) {
// write your code here
ListNode *L = new ListNode(-1);
L->next = head;
return sortListRec(head, NULL);
}
ListNode * sortListRec(ListNode *left, ListNode *right) {
if (left == right) return left;
ListNode *mid = findMid(left, right);
ListNode *t = mid->next;
mid->next = NULL;
ListNode *l = sortListRec(left, mid);
ListNode *r = sortListRec(t, right);
return MergeTwoSortedList(l ,r);
}
ListNode * findMid(ListNode *left, ListNode *right) {
ListNode *dummy = new ListNode(-1);
dummy->next = left;
ListNode *slow, *fast;
slow = fast = dummy;
while (fast != right && fast->next != right) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
ListNode * MergeTwoSortedList(ListNode *head1, ListNode *head2) {
ListNode *L = new ListNode(-1);
ListNode *p = L;
while (head1 && head2) {
if (head1->val < head2->val) {
p->next = head1;
head1 = head1->next;
} else {
p->next = head2;
head2 = head2->next;
}
p = p->next;
}
while (head1) {
p->next = head1;
p = p->next;
head1 = head1->next;
}
while (head2) {
p->next = head2;
p = p->next;
head2 = head2->next;
}
return L->next;
}
223. Palindrome Linked List
题目描述
写一个函数,来判断一个链表是不是一个回文串链表。要求O(n)时间和O(1)空间。
思路
有了前几题的铺垫,这一题很快就想到了思路:首先找到链表的中点,将链表一分为二,然后反转后面的链表,然后在从头开始比较即可。
代码
bool isPalindrome(ListNode * head) {
// write your code here
if (head == NULL) return true;
if (head->next == NULL) return true;
//O(n) time
ListNode *slow = head, *fast = head->next;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
ListNode *L = slow, *pre = slow->next;
while (pre->next) {
ListNode *curr = pre->next;
pre->next = curr->next;
curr->next = L->next;
L->next = curr;
}
ListNode *l = head;
ListNode *r = slow->next;
slow->next = NULL;
while (l && r) {
if (l->val != r->val)
return false;
l = l->next;
r = r->next;
}
return true;
}
380. Intersection of Two Linked Lists
题目描述
找到两个单链表的最长公共后缀的第一个元素。
思路
这一题真是捉急了,竟然没想出来。其实可以从后面往前推论来得到正确的结果。定义两个指针,先让指向长度较长链表的指针往后移动(lenA-lenB)位,这样再和另一个指针同时向后比较,发现的第一个元素相等的元素就是链表的头结点。(其实也不一定)
代码
ListNode * getIntersectionNode(ListNode * headA, ListNode * headB) {
// write your code here
ListNode *pA = headA, *pB = headB;
int lenA = 0, lenB = 0;
while (pA) {
lenA++;
pA = pA->next;
}
while (pB) {
lenB++;
pB = pB->next;
}
pA = headA, pB = headB;
if (lenA > lenB) {
for (int i = 0; i < lenA-lenB; i++)
pA = pA->next;
} else {
for (int i = 0; i < lenB-lenA; i++)
pB = pB->next;
}
while (pA && pB) {
if (pA->val == pB->val)
break;
pA = pA->next;
pB = pB->next;
}
return pA;
}
165. Merge Two Sorted Lists
代码
不多说,直接上代码。
ListNode * mergeTwoLists(ListNode * l1, ListNode * l2) {
// write your code here
ListNode *L = new ListNode(-1);
ListNode *p = L;
while (l1 && l2) {
if (l1->val < l2->val) {
p->next = l1;
l1 = l1->next;
} else {
p->next = l2;
l2 = l2->next;
}
p = p->next;
}
while (l1) {
p->next = l1;
l1 = l1->next;
p = p->next;
}
while (l2) {
p->next = l2;
l2 = l2->next;
p = p->next;
}
return L->next;
}
112. Remove Duplicates from Sorted List
题目描述
删除有序链表中重复的元素。
思路
删除元素需要知道前驱节点,所以比较当前节点的值和前驱节点的值即可,如果相同,则删除掉当前节点,否则,前驱节点和当前节点同时向后移动一位。
注意free(p)的结果是:p正在指向的地址没变,但此时在该地址处的数据已经没有定义了。
代码
ListNode * deleteDuplicates(ListNode * head) {
// write your code here
ListNode *L = new ListNode(-1);
L->next = head;
ListNode *pre = L, *curr = head;
while (curr) {
ListNode *pnext = curr->next;
if (curr->val == pre->val) {
ListNode *q = curr;
pre->next = pnext;
free(q);
} else {
pre = curr;
}
curr = pnext;
}
return L->next;
}
113. Remove Duplicates from Sorted List II
题目描述
要求要把所有的重复元素节点都删掉。
思路
用三个指针before,pre,curr,curr表示当前遍历的节点,pre表示当前遍历的节点的前驱节点,如果curr->val == pre->val,则直接删除掉curr。当curr->val != pre->val时,还需要一个bool标志来标识当前这个pre节点是否是拥有重复元素的节点,如果是,则应删除掉pre节点,因此before节点代表的是pre节点的前驱节点。
代码
ListNode * deleteDuplicates(ListNode * head) {
// write your code here
if (NULL == head || NULL == head->next) return head;
ListNode *L = new ListNode(-1);
L->next = head;
bool hasDeleted = false;
ListNode *before = L, *pre = head, *curr = head->next;
while (curr) {
ListNode *pnext = curr->next;
if (curr->val == pre->val) {
free(curr);
pre->next = pnext;
curr = pnext;
hasDeleted = true;
} else {
if (hasDeleted) {
hasDeleted = false;
before->next = pre->next;
free(pre);
pre = curr;
curr = pnext;
} else {
before = pre;
pre = curr;
curr = pnext;
}
}
}
if (hasDeleted) {
before->next = NULL;
free(pre);
hasDeleted = false;
}
return L->next;
}
134. LRU Cache
题目描述
使用代码实现缓存的最远使用准则。要求O(1)时间内完成。
思路
很容易想到,这样一个队列,系统使用一个元素,如果该元素不在队列中,就将该元素插入到队列的尾部,如果在队列中,就需要把原来的位置删除掉,然后把该元素也插入到队尾中。当队列容量达到要求时,将队列头部的元素删除掉,因为此时队列头部的元素就代表我距离上次使用它已经有很长时间了。
那么用什么数据结构来实现这个队列呢?显然用queue是不行的,因为queue不支持随机访问。于是就想到了list,在C++ Primer Plus中,关于list有这样的一个描述:双向链表。只支持双向顺序访问。在list中任何位置进行插入和删除操作速度都很快。而经常使用的vector在尾部之外的位置插入或删除元素可能很慢。
代码
因为之前做过LFU Cache这道题,所以这道题写起来有一点轻车熟路的感觉。
#include <list>
class LRUCache {
public:
/*
* @param capacity: An intege
*/
int size;
list<int> process;
unordered_map<int, int> kv;
unordered_map<int, list<int>::iterator> iter;
LRUCache(int capacity) {
// do intialization if necessary
size = capacity;
}
/*
* @param key: An integer
* @return: An integer
*/
int get(int key) {
// write your code here
if (kv.find(key) == kv.end()) return -1;
if (iter.find(key) != iter.end()) {
auto old_posi = iter[key];
process.erase(old_posi);
}
process.push_back(key);
iter[key] = --process.end();
return kv[key];
}
/*
* @param key: An integer
* @param value: An integer
* @return: nothing
*/
void set(int key, int value) {
// write your code here
if (get(key) != -1) {
kv[key] = value;
return;
}
if (process.size() == size) {
int delete_key = *process.begin();
process.erase(process.begin());
kv.erase(delete_key);
iter.erase(delete_key);
}
kv[key] = value;
process.push_back(key);
iter[key] = --process.end();
}
};
总结
给自己挖一个坑,由intersection of two linked list我想到了一个问题,也是曾经在某Blog上看过说这是某一公司的面试题。即二叉树中给定两个节点的最小公共祖先问题。想了一下,又上网搜了一下,觉得这道题还是很有研究的价值,于是在此处挖个坑,以后能看到就开篇Blog写之。好了,我去健身了,明天还得去参加程序设计大赛(:D给大佬疯狂插气球)