在面对链表的题目时,大部分人(包括我自己)都是很怵的,因为这必将面临指针。
笔者今天实现了一些关于链表操作的题目。
首先,定义单链表中的节点:
struct ListNode
{
int val;
ListNode *next;
ListNode(int x):val(x),next(NULL){}
};
//求链表长度
int length(ListNode *head)
{
if(head == NULL) return 0;
int len = 0;
while(head)
{
++len;
head = head->next;
}
return len;
}
//创建单链表,使用的是函数模板,针对不同的输入
template
ListNode *buildList(T beg, T end)
{
if(beg == end) return NULL;
ListNode *head = NULL, *prev = NULL;
for(; beg != end ; ++beg)
{
ListNode *node = new ListNode(*beg);
if(head == NULL)
{
head = node;
prev = head;
}
else
{
prev->next = node;
prev = node;
}
}
return head;
}
//删除单链表,并且释放内存!!这是很重要的一步,否则将会导致内存泄露
void destoryList(ListNode *head)
{
if(head == NULL) return;
ListNode *next = head;
while(head)
{
next = head->next;
delete head;
head = next;
}
}
//打印单链表
void printList(ListNode *head)
{
if(head == NULL) cout << "EMPTY..." << endl;
while(head)
{
cout << head->val << " " ;
head = head->next;
}
cout << endl;
}
注:上述的删除链表函数是必须的,否则内存泄露将会是最大的问题。
[updated]:上述删除链表的操作是有问题的,传入的参数必须是指针的指针或者是引用。或者返回head。因为需要将head指针置为空。这利用传值调用是办不到的。
接下来,定义链表的一些操作:
1 返回倒数第K个节点:
//有待更新
2 逆置链表;依次取出原链表中的元素,将其插入到新链表的头部.
//reverse!!!
ListNode *reverse(ListNode *head)
{
if(head == NULL || head->next == NULL) return head;
//在此处我本想直接将new_head置为head,再循环head后面的元素,但是会出现死循环。
//在逆置过程中,因为后续的head的元素都是插入到new_head的最前面,也就意味着,第一个插入的元素的next指针是不会被更改的。
//因为在新的链表里面,该元素是最后一个元素,那么这样就会使得新的链表的new_head指针不指向空,直接导致死循环
ListNode *new_head = NULL;
ListNode *temp = NULL, *new_temp = NULL;
while(head)
{
temp = head->next;
new_temp = new_head;
new_head = head;
new_head->next = new_temp;
head = temp;
}
return new_head;
}
注意,在逆置单链表的过程中,我犯了一个错误,见上述代码注释:
3 判断链表是否是回文:
//reverse the last half and compare
//将链表的后半部分逆置,依次比较链表的元素。
bool isPalindrome(ListNode *head)
{
cout << "enter isPalindrome..." << endl;
printList(head);
if(head == NULL || head->next == NULL)
return true;
ListNode *fast = head->next;
ListNode *slow = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
}
ListNode *L = head;
ListNode *R = slow->next;
slow->next = NULL;
R = reverse(R);
cout << " L :" ;
printList(L);
cout << " R :" ;
printList(R);
while( L && R)
{
if(L->val != R->val)
{
return false;
}
L = L->next;
R = R->next;
}
//如果链表元素个数是偶数,那么直接返回true.
//如果链表元素个数是奇数,那么还需要判断是否在其中一个链表中只剩下一个元素即可。
if( ( L == NULL && R == NULL) || (L == NULL && R->next == NULL) || ( R == NULL && L->next == NULL))
return true;
return false;
}
当然,判断回文还有其他方法,不过我只想到这个,就直接实现了。
4 将链表的元素根据某个pivot进行partition,这可以用作链表快排的函数。
//注意一定要将less_head, grt_head链表的末尾置为空!!!
ListNode* Partition(ListNode *head, const int pivot)
{
if(head == NULL || head->next == NULL) return NULL;
ListNode *less_head = NULL , *less_temp = NULL;
ListNode *grt_head = NULL, *grt_temp = NULL;
ListNode *next = NULL;
while(head)
{
next = head->next;
if(head->val >= pivot)
{
if(grt_head == NULL)
{
grt_head = head;
grt_temp = head;
}
else
{
grt_temp->next = head;
grt_temp = head;
}
}
else
{
if(less_head == NULL)
{
less_head = head;
less_temp = head;
}
else
{
less_temp->next = head;
less_temp = head;
}
}
head = next;
}
if(less_head)
{
less_temp->next = grt_head;
head = less_head;
grt_temp->next = NULL;
}
else
{
head = grt_head;
grt_temp->next = NULL;
}
return head;
}
5 将两个链表中的元素相加,分为两个版本,第一个是:
高位在表头,那么先将链表长度补成一致,再进行递归调用即可。
//将链表补齐,在表头添加元素0
ListNode *paddle(ListNode *head, int len)
{
if(len == 0) return head;
ListNode *temp = NULL;
while(len-- > 0)
{
ListNode *node = new ListNode(0);
//head must not be empty
temp = head;
head = node;
head->next = temp;
}
return head;
}
//递归调用此函数
//carry应该是引用,因为在算当前位的时候,需要知道低位的进位,但是此时函数已经有了返回值,因此使用引用来传递carry这个返回值。
ListNode *addTwoEqualLenList(ListNode *lhs, ListNode *rhs, int &carry)
{
//必然同时为空
if(lhs == NULL || rhs == NULL) return NULL;
//先计算后面的位数,再计算当前位
ListNode *node = addTwoEqualLenList(lhs->next, rhs->next, carry);
int sum = lhs->val + rhs->val + carry;
carry = sum/10;
sum = sum%10;
ListNode *head = new ListNode(sum);
head->next = node;
return head;
}
//相加
ListNode *addTwoListReverse(ListNode *lhs, ListNode *rhs)
{
if(lhs == NULL && rhs == NULL) return NULL;
if(lhs == NULL) return rhs;
if(rhs == NULL) return lhs;
int len1 = length(lhs);
int len2 = length(rhs);
//补齐元素
if(len1 < len2)
{
lhs = paddle(lhs, len2-len1);
}
else
{
rhs = paddle(rhs, len1-len2);
}
cout << "after paddle..." << endl;
printList(lhs);
printList(rhs);
assert(length(lhs) == length(rhs));
int carry = 0;
ListNode *ret = addTwoEqualLenList(lhs, rhs, carry);
cout << "DEBUG : ";
printList(ret);
//处理最高位的进位
if(carry)
{
ListNode *node = new ListNode(1);
ListNode *temp = ret;
ret = node;
ret->next = temp;
}
destoryList(lhs);
destoryList(rhs);
return ret;
}
低位在表头。直接相加即可,只需要处理最后元素可能的进位!!
//add two list and return a new List
ListNode *addTwoList(ListNode *lhs, ListNode *rhs)
{
cout << "lhs : " ;printList(lhs);
cout << "rhs : " ;printList(rhs);
if(lhs == NULL && rhs == NULL) return NULL;
if(lhs == NULL) return rhs;
if(rhs == NULL) return lhs;
ListNode *ret = NULL;
ListNode *lhs_temp = NULL, *rhs_temp = NULL, *ret_temp = NULL;
int sum = 0 , carry = 0;
while(lhs && rhs)
{
sum = lhs->val + rhs->val + carry;
carry = sum/10;
sum = sum%10;
ListNode *node = new ListNode(sum);
if(ret == NULL)
{
ret = node;
ret_temp = node;
}
else
{
ret_temp->next = node;
ret_temp = node;
}
rhs = rhs->next;
lhs = lhs->next;
}
//which is empty
if(lhs != NULL) swap(lhs,rhs);
while(rhs)
{
sum = rhs->val + carry;
carry = sum/10;
sum = sum%10;
ListNode *node = new ListNode(sum);
//ret must not be empty
ret_temp->next = node;
ret_temp = ret_temp->next;
rhs = rhs->next;
}
if(carry)
{
ListNode *node = new ListNode(1);
ret_temp->next = node;
}
return ret;
}
6 判断一个链表中是否有环,如果有,返回环的起点,如果没有,直接返回,
首先,判断一个链表是否有环,典型的追及问题,一个快指针fast,每次走两步,慢指针slow,每次走一步。
如果存在环,则慢指针一定会在进入环的第一圈内就与快指针相遇。如果不是第一圈,也就意味着,慢指针在第一圈时,会被快指针追上
并超过,显然是不可能的。如果在点i处超越,那么此时慢指针在i处,快指针在i+1处,那么前一步,快指针在i-1处,慢指针也在i-1处,这
与“指针不相遇”假设矛盾。
ListNode* isCycle(ListNode *head)
{
if(head == NULL ) return NULL;
ListNode *fast = head->next;
ListNode *slow = head;
while(fast && fast->next)
{
if(fast == slow && fast != NULL)
{
return slow;
}
fast = fast->next->next;
slow = slow->next;
}
return NULL;
}
ListNode *findCycle(ListNode *head)
{
if(head == NULL) return NULL;
ListNode *temp = isCycle(head);
if(temp == NULL) return NULL;
//注意,在链表中的追及问题,fast指针首先应该指向slow的下一步。
//这样就能保证以后每一步,两者之间的差距均增加1
//也就是两者之间差距依次为,1,2,3,4,5...
ListNode *fast = temp->next;
ListNode *slow = head;
while(fast != slow)
{
fast = fast->next;
slow = slow->next;
}
return fast;
}
上述程序有些繁琐,特别是在处理 fast与slow的关系时,下面的代码比较简单也比较直观
ListNode* isCycle(ListNode *head)
{
if(head == NULL ) return NULL;
ListNode *fast = head;
ListNode *slow = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
//fast与slow是否相等的判断必须在移动之后,因为刚开始两者都是指向head的!!!
if(fast == slow)
break;
}
if(fast == NULL || fast->next == NULL)
return NULL;
return slow;
}
ListNode *findCycle(ListNode *head)
{
if(head == NULL) return NULL;
ListNode *temp = isCycle(head);
if(temp == NULL) return NULL;
ListNode *fast = temp;
ListNode *slow = head;
while(fast != slow)
{
fast = fast->next;
slow = slow->next;
}
return fast;
}
PS:所有代码的易错点均在代码注释中~~~~