声明
![](/icons/93824dou.gif)
本文所有13道算法题目
![](/icons/93824dou.gif)
覆盖了基本上所有常见
![](/icons/93824de.gif)
单链表问题
![](/icons/93824dou.gif)
全都用C#实现
![](/icons/93824dou.gif)
并测试通过
![](/icons/93824dou.gif)
代码下载:TestLink.zip
1.单链表反转
2.找出单链表
![](/icons/93824de.gif)
倒数第4个元素
3.找出单链表
![](/icons/93824de.gif)
中间元素
4.删除无头单链表
![](/icons/93824de.gif)
![](/icons/93824yi.gif)
个节点
5.两个不交叉
![](/icons/93824de.gif)
有序链表
![](/icons/93824de.gif)
合并
6.有个 2级单链表
![](/icons/93824dou.gif)
其中每个元素都含有
![](/icons/93824yi.gif)
个指向
![](/icons/93824yi.gif)
个单链表
![](/icons/93824de.gif)
指针
![](/icons/93824dou2.gif)
写
![](/icons/93824chengxu.gif)
把这个 2级链表称
![](/icons/93824yi.gif)
级单链表
![](/icons/93824dou2.gif)
7.单链表交换任意两个元素(不包括表头)
8.判断单链表是否有环?如何找到环
![](/icons/93824de.gif)
“起始”点?如何知道环
![](/icons/93824de.gif)
长度?
9.判断两个单链表是否相交
10.两个单链表相交
![](/icons/93824dou.gif)
计算相交点
11.用链表模拟大整数加法运算
12.单链表排序
13.删除单链表中重复
![](/icons/93824de.gif)
元素
首先写
![](/icons/93824yi.gif)
个单链表
![](/icons/93824de.gif)
C#实现
![](/icons/93824dou.gif)
这是我们
![](/icons/93824de.gif)
基石:
public
Link
{
public Link Next;
public
Data;
public Link(
Link next,
data)
{
this.Next = next;
this.Data = data;
}
}
其中
![](/icons/93824dou.gif)
我们需要人为地在单链表前面加
![](/icons/93824yi.gif)
个空节点
![](/icons/93824dou.gif)
称其为head
![](/icons/93824dou2.gif)
例如
![](/icons/93824dou.gif)
![](/icons/93824yi.gif)
个单链表是1->2->5
![](/icons/93824dou.gif)
如图所示:
对
![](/icons/93824yi.gif)
个单链表
![](/icons/93824de.gif)
遍历如下所示:
void Main(
![](/icons/93824string.gif)
![](/icons/93824zhk2.gif)
args)
{
Link head = GenerateLink
![](/icons/93824kh.gif)
;
Link curr = head;
while (curr !=
null)
{
Console.WriteLine(curr.Data);
curr = curr.Next;
}
}
1.单链表反转
这道题目有两种算法
![](/icons/93824dou.gif)
既然是要反转
![](/icons/93824dou.gif)
那么肯定是要破坏原有
![](/icons/93824de.gif)
数据结构
![](/icons/93824de.gif)
:
算法1:我们需要额外
![](/icons/93824de.gif)
两个变量来存储当前节点curr
![](/icons/93824de.gif)
下
![](/icons/93824yi.gif)
个节点next、再下
![](/icons/93824yi.gif)
个节点nextnext:
public
Link ReverseLink1(
Link head)
{
Link curr = head.Next;
Link next =
null;
Link nextnext =
null;
//
no elements or _disibledevent=>
(curr
null || curr.Next
null)
{
head;
}
//
more than _disibledevent=>while (curr.Next !=
null)
{
next = curr.Next;
//1
nextnext = next.Next;
//2
next.Next = head.Next;
//3
head.Next = next;
//4
curr.Next = nextnext;
//5
}
head;
}
算法
![](/icons/93824de.gif)
核心是while循环中
![](/icons/93824de.gif)
5句话
![](/icons/93824dou.gif)
画
![](/icons/93824yi.gif)
个图来表示这5个步骤:
我们发现
![](/icons/93824dou.gif)
curr始终指向第1个元素
![](/icons/93824dou2.gif)
此外
![](/icons/93824dou.gif)
出于编程
![](/icons/93824de.gif)
严谨性
![](/icons/93824dou.gif)
还要考虑2种极特殊
![](/icons/93824de.gif)
情况:没有元素
![](/icons/93824de.gif)
单链表
![](/icons/93824dou.gif)
以及只有
![](/icons/93824yi.gif)
个元素
![](/icons/93824de.gif)
单链表
![](/icons/93824dou.gif)
都是不需要反转
![](/icons/93824de.gif)
算法2:自然是递归
如果题目简化为逆序输出这个单链表
![](/icons/93824dou.gif)
那么递归是很简单
![](/icons/93824de.gif)
![](/icons/93824dou.gif)
在递归
![](/icons/93824hanshu.gif)
的后输出当前元素
![](/icons/93824dou.gif)
这样能确保输出第N个元素语句永远在第N+1个递归
![](/icons/93824hanshu.gif)
的后执行
![](/icons/93824dou.gif)
也就是说第N个元素永远在第N+1个元素的后输出
![](/icons/93824dou.gif)
最终我们先输出最后
![](/icons/93824yi.gif)
个元素
![](/icons/93824dou.gif)
然后是倒数第2个、倒数第3个
![](/icons/93824dou.gif)
直到输出第1个:
public
void ReverseLink2(
Link head)
{
(head.Next !=
null)
{
ReverseLink2(head.Next);
Console.WriteLine(head.Next.Data);
}
}
但是
![](/icons/93824dou.gif)
现实应用中往往不是要求我们逆序输出(不损坏原有
![](/icons/93824de.gif)
单链表)
![](/icons/93824dou.gif)
而是把这个单链表逆序(破坏型)
![](/icons/93824dou2.gif)
这就要求我们在递归
![](/icons/93824de.gif)
时候
![](/icons/93824dou.gif)
还要处理递归后
![](/icons/93824de.gif)
逻辑
![](/icons/93824dou2.gif)
首先
![](/icons/93824dou.gif)
要把判断单链表有0或1个元素这部分逻辑独立出来
![](/icons/93824dou.gif)
而不需要在递归中每次都比较
![](/icons/93824yi.gif)
次:
public
Link ReverseLink3(
Link head)
{
//
no elements or _disibledevent=>
(head.Next
null || head.Next.Next
null)
head;
head.Next = ReverseLink(head.Next);
head;
}
我们观测到:
head.Next = ReverseLink(head.Next);
这句话
![](/icons/93824de.gif)
意思是为ReverseLink思路方法生成
![](/icons/93824de.gif)
逆序链表添加
![](/icons/93824yi.gif)
个空表头
![](/icons/93824dou2.gif)
接下来就是递归
![](/icons/93824de.gif)
核心算法ReverseLink了:
Link ReverseLink(
Link head)
{
(head.Next
null)
head;
Link rHead = ReverseLink(head.Next);
head.Next.Next = head;
head.Next =
null;
rHead;
}
算法
![](/icons/93824de.gif)
关键就在于递归后
![](/icons/93824de.gif)
两条语句:
head.Next.Next = head;
//1
head.Next =
null;
//2
啥意思呢?画个图表示就是:
这样
![](/icons/93824dou.gif)
就得到了
![](/icons/93824yi.gif)
个逆序
![](/icons/93824de.gif)
单链表
![](/icons/93824dou.gif)
我们只用到了1个额外
![](/icons/93824de.gif)
变量rHead
2.找出单链表
![](/icons/93824de.gif)
倒数第4个元素
这道题目有两种算法
![](/icons/93824dou.gif)
但无论哪种算法
![](/icons/93824dou.gif)
都要考虑单链表少于4个元素
![](/icons/93824de.gif)
情况:
第1种算法
![](/icons/93824dou.gif)
建立两个指针
![](/icons/93824dou.gif)
第
![](/icons/93824yi.gif)
个先走4步
![](/icons/93824dou.gif)
然后第2个指针也开始走
![](/icons/93824dou.gif)
两个指针步伐(前进速度)
![](/icons/93824yi.gif)
致
Link GetLast4thOne(
Link head)
{
Link first = head;
Link second = head;
for (
i = 0; i < 4; i
![](/icons/93824jiajia.gif)
)
{
(first.Next
null)
throw
Exception(
"Less than 4 elements");
first = first.Next;
}
while (first !=
null)
{
first = first.Next;
second = second.Next;
}
second;
}
第2种算法
![](/icons/93824dou.gif)
做
![](/icons/93824yi.gif)
个
![](/icons/93824shuzu.gif)
arr[4]
![](/icons/93824dou.gif)
让我们遍历单链表
![](/icons/93824dou.gif)
把第0个、第4个、第8个……第4N个扔到arr[0]
![](/icons/93824dou.gif)
把第1个、第5个、第9个……第4N+1个扔到arr[1]
![](/icons/93824dou.gif)
把第2个、第6个、第10个……第4N+2个扔到arr[2]
![](/icons/93824dou.gif)
把第3个、第7个、第11个……第4N+3个扔到arr[3]
![](/icons/93824dou.gif)
这样随着单链表
![](/icons/93824de.gif)
遍历结束
![](/icons/93824dou.gif)
arr中存储
![](/icons/93824de.gif)
就是单链表
![](/icons/93824de.gif)
最后4个元素
![](/icons/93824dou.gif)
找到最后
![](/icons/93824yi.gif)
个元素对应
![](/icons/93824de.gif)
arr[i]
![](/icons/93824dou.gif)
让k=(i+1)%4
![](/icons/93824dou.gif)
则arr[k]就是倒数第4个元素
Link GetLast4thOneByArray(
Link head)
{
Link curr = head;
i = 0;
Link![](/icons/93824zhk2.gif)
arr =
Link[4];
while (curr.Next !=
null)
{
arr[i] = curr.Next;
curr = curr.Next;
i = (i + 1) % 4;
}
(arr[i]
null)
throw
Exception(
"Less than 4 elements");
arr[i];
}
本题目源代码下载:
推而广的
![](/icons/93824dou.gif)
对倒数第K个元素
![](/icons/93824dou.gif)
都能用以上2种算法找出来
3.找出单链表
![](/icons/93824de.gif)
中间元素
算法思想:类似于上题
![](/icons/93824dou.gif)
还是使用两个指针first和second
![](/icons/93824dou.gif)
只是first每次走
![](/icons/93824yi.gif)
步
![](/icons/93824dou.gif)
second每次走两步:
Link GetMiddleOne(
Link head)
{
Link first = head;
Link second = head;
while (first !=
null && first.Next !=
null)
{
first = first.Next.Next;
second = second.Next;
}
second;
}
但是
![](/icons/93824dou.gif)
这道题目有个地方需要注意
![](/icons/93824dou.gif)
就是对于链表元素个数为奇数
![](/icons/93824dou.gif)
以上算法成立
![](/icons/93824dou2.gif)
如果链表元素个数为偶数
![](/icons/93824dou.gif)
那么在返回second
![](/icons/93824de.gif)
同时
![](/icons/93824dou.gif)
还要返回second.Next也就是下
![](/icons/93824yi.gif)
个元素
![](/icons/93824dou.gif)
它俩都算是单链表
![](/icons/93824de.gif)
中间元素
![](/icons/93824dou2.gif)
下面是加强版
![](/icons/93824de.gif)
算法
![](/icons/93824dou.gif)
无论奇数偶数
![](/icons/93824dou.gif)
![](/icons/93824yi.gif)
概通杀:
void Main(
![](/icons/93824string.gif)
![](/icons/93824zhk2.gif)
args)
{
Link head = GenerateLink
![](/icons/93824kh.gif)
;
bool isOdd =
true;
Link middle = GetMiddleOne(head,
ref isOdd);
(isOdd)
{
Console.WriteLine(middle.Data);
}
{
Console.WriteLine(middle.Data);
Console.WriteLine(middle.Next.Data);
}
Console.Read
![](/icons/93824kh.gif)
;
}
Link GetMiddleOne(
Link head,
ref bool isOdd)
{
Link first = head;
Link second = head;
while (first !=
null && first.Next !=
null)
{
first = first.Next.Next;
second = second.Next;
}
(first !=
null)
isOdd =
false;
second;
}
4.
![](/icons/93824yi.gif)
个单链表
![](/icons/93824dou.gif)
很长
![](/icons/93824dou.gif)
遍历
![](/icons/93824yi.gif)
遍很慢
![](/icons/93824dou.gif)
我们仅知道
![](/icons/93824yi.gif)
个指向某节点
![](/icons/93824de.gif)
指针curr
![](/icons/93824dou.gif)
而我们又想删除这个节点
![](/icons/93824dou2.gif)
这道题目是典型
![](/icons/93824de.gif)
“狸猫换太子”
![](/icons/93824dou.gif)
如下图所示:
如果不考虑任何特殊情况
![](/icons/93824dou.gif)
代码就2行:
curr.Data = curr.Next.Data;
curr.Next = curr.Next.Next;
上述代码由
![](/icons/93824yi.gif)
个地方需要注意
![](/icons/93824dou.gif)
就是如果要删除
![](/icons/93824de.gif)
是最后
![](/icons/93824yi.gif)
个元素呢?那就只能从头遍历
![](/icons/93824yi.gif)
次找到倒数第 2个节点了
此外
![](/icons/93824dou.gif)
这道题目
![](/icons/93824de.gif)
![](/icons/93824yi.gif)
个变身就是将
![](/icons/93824yi.gif)
个环状单链表拆开(即删除其中
![](/icons/93824yi.gif)
个元素)
![](/icons/93824dou.gif)
此时
![](/icons/93824dou.gif)
只要使用上面那两行代码就可以了
![](/icons/93824dou.gif)
不需要考虑表尾
![](/icons/93824dou2.gif)
相关问题:只给定单链表中某个结点p(非空结点)
![](/icons/93824dou.gif)
在p前面插入
![](/icons/93824yi.gif)
个结点q
话说
![](/icons/93824dou.gif)
交换单链表任意两个节点
![](/icons/93824dou.gif)
也可以用交换值
![](/icons/93824de.gif)
思路方法
![](/icons/93824dou2.gif)
但这样就没意思了
![](/icons/93824dou.gif)
所以
![](/icons/93824dou.gif)
才会有第7题霸王硬上工
![](/icons/93824de.gif)
做法
5.两个不交叉
![](/icons/93824de.gif)
有序链表
![](/icons/93824de.gif)
合并
有两个有序链表
![](/icons/93824dou.gif)
各自内部是有序
![](/icons/93824de.gif)
![](/icons/93824dou.gif)
但是两个链表的间是无序
![](/icons/93824de.gif)
![](/icons/93824dou2.gif)
算法思路:当然是循环逐项比较两个链表了
![](/icons/93824dou.gif)
如果
![](/icons/93824yi.gif)
个到了头
![](/icons/93824dou.gif)
就不比较了
![](/icons/93824dou.gif)
直接加上去
![](/icons/93824dou2.gif)
注意
![](/icons/93824dou.gif)
对于2个元素
![](/icons/93824de.gif)
Data相等(仅仅是Data相等哦
![](/icons/93824dou.gif)
而不是相同
![](/icons/93824de.gif)
引用)
![](/icons/93824dou.gif)
我们可以把它视作前面
![](/icons/93824de.gif)
Data大于后面
![](/icons/93824de.gif)
Data
![](/icons/93824dou.gif)
从而节省了算法逻辑
Link MergeTwoLink(
Link head1,
Link head2)
{
Link head =
Link(
null,
Int16.MinValue);
Link pre = head;
Link curr = head.Next;
Link curr1 = head1;
Link curr2 = head2;
//compare until _disibledevent=>while (curr1.Next !=
null && curr2.Next !=
null)
{
(curr1.Next.Data < curr2.Next.Data)
{
curr =
Link(
null, curr1.Next.Data);
curr1 = curr1.Next;
}
{
curr =
Link(
null, curr2.Next.Data);
curr2 = curr2.Next;
}
pre.Next = curr;
pre = pre.Next;
}
//
head1 run to the end
while (curr1.Next !=
null)
{
curr =
Link(
null, curr1.Next.Data);
curr1 = curr1.Next;
pre.Next = curr;
pre = pre.Next;
}
//
head2 run to the end
while (curr2.Next !=
null)
{
curr =
Link(
null, curr2.Next.Data);
curr2 = curr2.Next;
pre.Next = curr;
pre = pre.Next;
}
head;
}
如果这两个有序链表交叉组成了Y型呢
![](/icons/93824dou.gif)
比如说:
这时我们需要先找出这个交叉点(图中是11)
![](/icons/93824dou.gif)
这个算法参见第9题
![](/icons/93824dou.gif)
我们这里直接使用第10道题目中
![](/icons/93824de.gif)
思路方法GetIntersect
![](/icons/93824dou2.gif)
然后局部修改上面
![](/icons/93824de.gif)
算法
![](/icons/93824dou.gif)
只要其中
![](/icons/93824yi.gif)
个链表到达了交叉点
![](/icons/93824dou.gif)
就直接把另
![](/icons/93824yi.gif)
个链表
![](/icons/93824de.gif)
剩余元素都加上去
![](/icons/93824dou2.gif)
如下所示:
Link MergeTwoLink2(
Link head1,
Link head2)
{
Link head =
Link(
null,
Int16.MinValue);
Link pre = head;
Link curr = head.Next;
Link ![](/icons/93824int.gif)
ersect = GetIntersect(head1, head2);
Link curr1 = head1;
Link curr2 = head2;
//compare until _disibledevent=>while (curr1.Next !=
![](/icons/93824int.gif)
ersect && curr2.Next !=
![](/icons/93824int.gif)
ersect)
{
(curr1.Next.Data < curr2.Next.Data)
{
curr =
Link(
null, curr1.Next.Data);
curr1 = curr1.Next;
}
{
curr =
Link(
null, curr2.Next.Data);
curr2 = curr2.Next;
}
pre.Next = curr;
pre = pre.Next;
}
//
head1 run to the
ersect
(curr1.Next
![](/icons/93824int.gif)
ersect)
{
while (curr2.Next !=
null)
{
curr =
Link(
null, curr2.Next.Data);
curr2 = curr2.Next;
pre.Next = curr;
pre = pre.Next;
}
}
//
head2 run to the
ersect
(curr2.Next
![](/icons/93824int.gif)
ersect)
{
while (curr1.Next !=
null)
{
curr =
Link(
null, curr1.Next.Data);
curr1 = curr1.Next;
pre.Next = curr;
pre = pre.Next;
}
}
head;
}
6.有个 2级单链表
![](/icons/93824dou.gif)
其中每个元素都含有
![](/icons/93824yi.gif)
个指向
![](/icons/93824yi.gif)
个单链表
![](/icons/93824de.gif)
指针
![](/icons/93824dou2.gif)
写
![](/icons/93824chengxu.gif)
把这个 2级链表展开称
![](/icons/93824yi.gif)
级单链表
![](/icons/93824dou2.gif)
这个简单
![](/icons/93824dou.gif)
就是说
![](/icons/93824dou.gif)
这个 2级单链表只包括
![](/icons/93824yi.gif)
些head:
public
Link
{
public Link Next;
public
Data;
public Link(
Link next,
data)
{
this.Next = next;
this.Data = data;
}
}
public
CascadeLink
{
public Link Next;
public CascadeLink NextHead;
public CascadeLink(
CascadeLink nextHead,
Link next)
{
this.Next = next;
this.NextHead = nextHead;
}
}
下面做
![](/icons/93824yi.gif)
个 2级单链表
![](/icons/93824dou.gif)
GenerateLink1和GenerateLink2思路方法在前面都已经介绍过了:
public
CascadeLink GenerateCascadeLink
![](/icons/93824kh.gif)
{
Link head1 = GenerateLink1
![](/icons/93824kh.gif)
;
Link head2 = GenerateLink2
![](/icons/93824kh.gif)
;
Link head3 = GenerateLink1
![](/icons/93824kh.gif)
;
CascadeLink element3 =
CascadeLink(
null, head3);
CascadeLink element2 =
CascadeLink(element3, head2);
CascadeLink element1 =
CascadeLink(element2, head1);
CascadeLink head =
CascadeLink(element1,
null);
head;
}
就是说
![](/icons/93824dou.gif)
这些单链表
![](/icons/93824de.gif)
表头head1、head2、head3、head4……
![](/icons/93824dou.gif)
它们组成了
![](/icons/93824yi.gif)
个 2级单链表head:null –> head1 –> head2 –> head3 –> head4 –>
我们
![](/icons/93824de.gif)
算法思想是: 进行两次遍历
![](/icons/93824dou.gif)
在外层用curr1遍历 2级单链表head
![](/icons/93824dou.gif)
在内层用curr2遍历每个单链表:
public
Link GenerateNewLink(
CascadeLink head)
{
CascadeLink curr1 = head.NextHead;
Link ![](/icons/93824new.gif)
Head = curr1.Next;
Link curr2 =
![](/icons/93824new.gif)
Head;
while (curr1 !=
null)
{
curr2.Next = curr1.Next.Next;
while (curr2.Next !=
null)
{
curr2 = curr2.Next;
}
curr1 = curr1.NextHead;
}
![](/icons/93824new.gif)
Head;
}
其中
![](/icons/93824dou.gif)
curr2.Next = curr1.Next.Next; 这句话是关键
![](/icons/93824dou.gif)
它负责把上
![](/icons/93824yi.gif)
个单链表
![](/icons/93824de.gif)
表尾和下
![](/icons/93824yi.gif)
个单链表
![](/icons/93824de.gif)
非空表头连接起来
7.单链表交换任意两个元素(不包括表头)
先
![](/icons/93824yi.gif)
次遍历找到这两个元素curr1和curr2
![](/icons/93824dou.gif)
同时存储这两个元素
![](/icons/93824de.gif)
前驱元素pre1和pre2
![](/icons/93824dou2.gif)
然后大换血
public
Link SwitchPo
![](/icons/93824int.gif)
s(
Link head,
Link p,
Link q)
{
(p
![](/icons/93824dd.gif)
head || q
![](/icons/93824dd.gif)
head)
throw
Exception(
"No exchange with head");
(p
![](/icons/93824dd.gif)
q)
head;
//find p and q in the link
Link curr = head;
Link curr1 = p;
Link curr2 = q;
Link pre1 =
null;
Link pre2 =
null;
count = 0;
while (curr !=
null)
{
(curr.Next
![](/icons/93824dd.gif)
p)
{
pre1 = curr;
count
![](/icons/93824jiajia.gif)
;
(count
![](/icons/93824dd.gif)
2)
![](/icons/93824break.gif)
;
}
(curr.Next
![](/icons/93824dd.gif)
q)
{
pre2 = curr;
count
![](/icons/93824jiajia.gif)
;
(count
![](/icons/93824dd.gif)
2)
![](/icons/93824break.gif)
;
}
curr = curr.Next;
}
curr = curr1.Next;
pre1.Next = curr2;
curr1.Next = curr2.Next;
pre2.Next = curr1;
curr2.Next = curr;
head;
}
注意特例
![](/icons/93824dou.gif)
如果相同元素
![](/icons/93824dou.gif)
就没有必要交换;如果有
![](/icons/93824yi.gif)
个是表头
![](/icons/93824dou.gif)
就不交换
8.判断单链表是否有环?如何找到环
![](/icons/93824de.gif)
“起始”点?如何知道环
![](/icons/93824de.gif)
长度?
算法思想:
先分析是否有环
![](/icons/93824dou2.gif)
为此我们建立两个指针
![](/icons/93824dou.gif)
从header
![](/icons/93824yi.gif)
起向前跑
![](/icons/93824dou.gif)
![](/icons/93824yi.gif)
个步长为1
![](/icons/93824dou.gif)
![](/icons/93824yi.gif)
个步长为2
![](/icons/93824dou.gif)
用while(直到步长2
![](/icons/93824de.gif)
跑到结尾)检查两个指针是否相等
![](/icons/93824dou.gif)
直到找到为止
bool JudgeCircleExists(
Link head)
{
Link first = head;
//1 step each time
Link second = head;
//2 steps each time
while (second.Next !=
null && second.Next.Next !=
null)
{
second = second.Next.Next;
first = first.Next;
(second
![](/icons/93824dd.gif)
first)
true;
}
false;
}
那又如何知道环
![](/icons/93824de.gif)
长度呢?
根据上面
![](/icons/93824de.gif)
算法
![](/icons/93824dou.gif)
在返回true
![](/icons/93824de.gif)
地方
![](/icons/93824dou.gif)
也就是2个指针相遇处
![](/icons/93824dou.gif)
这个位置
![](/icons/93824de.gif)
节点P肯定位于环上
![](/icons/93824dou2.gif)
我们从这个节点开始先前走
![](/icons/93824dou.gif)
转了
![](/icons/93824yi.gif)
圈肯定能回来:
GetCircleLength(
Link po
![](/icons/93824int.gif)
)
{
length = 1;
Link curr = po
![](/icons/93824int.gif)
;
while (curr.Next != po
![](/icons/93824int.gif)
)
{
length
![](/icons/93824jiajia.gif)
;
curr = curr.Next;
}
length;
}
继续我们
![](/icons/93824de.gif)
讨论
![](/icons/93824dou.gif)
如何找到环
![](/icons/93824de.gif)
“起始”点呢?
延续上面
![](/icons/93824de.gif)
思路
![](/icons/93824dou.gif)
我们仍然在返回true
![](/icons/93824de.gif)
地方P
![](/icons/93824dou.gif)
计算
![](/icons/93824yi.gif)
下从有环单链表
![](/icons/93824de.gif)
表头head到P点
![](/icons/93824de.gif)
距离
GetLengthFromHeadToPo
![](/icons/93824int.gif)
(
Link head,
Link po
![](/icons/93824int.gif)
)
{
length = 1;
Link curr = head;
while (curr != po
![](/icons/93824int.gif)
)
{
length
![](/icons/93824jiajia.gif)
;
curr = curr.Next;
}
length;
}
如果我们把环从P点“切开”(当然并不是真
![](/icons/93824de.gif)
切
![](/icons/93824dou.gif)
那就破坏原来
![](/icons/93824de.gif)
数据结构了)
![](/icons/93824dou.gif)
那么问题就转化为计算两个相交“单链表”
![](/icons/93824de.gif)
交点(第10题):
![](/icons/93824yi.gif)
个单链表是从P点出发
![](/icons/93824dou.gif)
到达P(
![](/icons/93824yi.gif)
个回圈)
![](/icons/93824dou.gif)
距离M;另
![](/icons/93824yi.gif)
个单链表从有环单链表
![](/icons/93824de.gif)
表头head出发
![](/icons/93824dou.gif)
到达P
![](/icons/93824dou.gif)
距离N
![](/icons/93824dou2.gif)
我们可以参考第10题
![](/icons/93824de.gif)
GetIntersect思路方法并稍作修改
private
Link FindIntersect(
Link head)
{
Link p =
null;
//get the po
in the circle
bool result = JudgeCircleExists(head,
ref p);
(!result)
null;
Link curr1 = head.Next;
Link curr2 = p.Next;
//length from head to p
M = 1;
while (curr1 != p)
{
M
![](/icons/93824jiajia.gif)
;
curr1 = curr1.Next;
}
//circle length
N = 1;
while (curr2 != p)
{
N
![](/icons/93824jiajia.gif)
;
curr2 = curr2.Next;
}
//recover curr1 & curr2
curr1 = head.Next;
curr2 = p.Next;
//make 2 links have the same distance to the
ersect
(M > N)
{
for (
i = 0; i < M - N; i
![](/icons/93824jiajia.gif)
)
curr1 = curr1.Next;
}
(M < N)
{
for (
i = 0; i < N - M; i
![](/icons/93824jiajia.gif)
)
curr2 = curr2.Next;
}
//goto the
ersect
while (curr1 != p)
{
(curr1
![](/icons/93824dd.gif)
curr2)
{
curr1;
}
curr1 = curr1.Next;
curr2 = curr2.Next;
}
null;
}
9.判断两个单链表是否相交
这道题有多种算法
![](/icons/93824dou2.gif)
算法1:把第
![](/icons/93824yi.gif)
个链表逐项存在hashtable中
![](/icons/93824dou.gif)
遍历第2个链表
![](/icons/93824de.gif)
每
![](/icons/93824yi.gif)
项
![](/icons/93824dou.gif)
如果能在第
![](/icons/93824yi.gif)
个链表中找到
![](/icons/93824dou.gif)
则必然相交
bool JudgeIntersectLink1(
Link head1,
Link head2)
{
Hashtable ht =
Hashtable![](/icons/93824kh.gif)
;
Link curr1 = head1;
Link curr2 = head2;
//store all the elements of link1
while (curr1.Next !=
null)
{
ht[curr1.Next] =
![](/icons/93824string.gif)
.Empty;
curr1 = curr1.Next;
}
//check all the elements in link2
exists in Hashtable or not
while (curr2.Next !=
null)
{
//
exists
(ht[curr2.Next] !=
null)
{
true;
}
curr2 = curr2.Next;
}
false;
}
算法2:把
![](/icons/93824yi.gif)
个链表A接在另
![](/icons/93824yi.gif)
个链表B
![](/icons/93824de.gif)
末尾
![](/icons/93824dou.gif)
如果有环
![](/icons/93824dou.gif)
则必然相交
![](/icons/93824dou2.gif)
如何判断有环呢?从A开始遍历
![](/icons/93824dou.gif)
如果能回到A
![](/icons/93824de.gif)
表头
![](/icons/93824dou.gif)
则肯定有环
![](/icons/93824dou2.gif)
注意
![](/icons/93824dou.gif)
在返回结果的前
![](/icons/93824dou.gif)
要把刚才连接上
![](/icons/93824de.gif)
两个链表断开
![](/icons/93824dou.gif)
恢复原状
bool JudgeIntersectLink2(
Link head1,
Link head2)
{
bool exists =
false;
Link curr1 = head1;
Link curr2 = head2;
//goto the end of the link1
while (curr1.Next !=
null)
{
curr1 = curr1.Next;
}
//join these two links
curr1.Next = head2;
//iterate link2
while (curr2.Next !=
null)
{
(curr2.Next
![](/icons/93824dd.gif)
head2)
{
exists =
true;
![](/icons/93824break.gif)
;
}
curr2 = curr2.Next;
}
//recover original status, whether exists or not
curr1.Next =
null;
exists;
}
算法3:如果两个链表
![](/icons/93824de.gif)
末尾元素相同
![](/icons/93824dou.gif)
则必相交
bool JudgeIntersectLink3(
Link head1,
Link head2)
{
Link curr1 = head1;
Link curr2 = head2;
//goto the end of the link1
while (curr1.Next !=
null)
{
curr1 = curr1.Next;
}
//goto the end of the link2
while (curr2.Next !=
null)
{
curr2 = curr2.Next;
}
(curr1 != curr2)
false;
true;
}
10.两个单链表相交
![](/icons/93824dou.gif)
计算相交点
分别遍历两个单链表
![](/icons/93824dou.gif)
计算出它们
![](/icons/93824de.gif)
长度M和N
![](/icons/93824dou.gif)
假设M比N大
![](/icons/93824dou.gif)
则长度M
![](/icons/93824de.gif)
链表先前进M-N
![](/icons/93824dou.gif)
然后两个链表同时以步长1前进
![](/icons/93824dou.gif)
前进
![](/icons/93824de.gif)
同时比较当前
![](/icons/93824de.gif)
元素
![](/icons/93824dou.gif)
如果相同
![](/icons/93824dou.gif)
则必是交点
public
Link GetIntersect(
Link head1,
Link head2)
{
Link curr1 = head1;
Link curr2 = head2;
M = 0, N = 0;
//goto the end of the link1
while (curr1.Next !=
null)
{
curr1 = curr1.Next;
M
![](/icons/93824jiajia.gif)
;
}
//goto the end of the link2
while (curr2.Next !=
null)
{
curr2 = curr2.Next;
N
![](/icons/93824jiajia.gif)
;
}
//
to the begining of the link
curr1 = head1;
curr2 = head2;
(M > N)
{
for (
i = 0; i < M - N; i
![](/icons/93824jiajia.gif)
)
curr1 = curr1.Next;
}
(M < N)
{
for (
i = 0; i < N - M; i
![](/icons/93824jiajia.gif)
)
curr2 = curr2.Next;
}
while (curr1.Next !=
null)
{
(curr1
![](/icons/93824dd.gif)
curr2)
{
curr1;
}
curr1 = curr1.Next;
curr2 = curr2.Next;
}
null;
}
11.用链表模拟大整数加法运算
例如:9>9>9>NULL + 1>NULL => 1>0>0>0>NULL
肯定是使用递归啦
![](/icons/93824dou.gif)
不然没办法解决进位+1问题
![](/icons/93824dou.gif)
![](/icons/93824yinwei.gif)
这时候要让前面
![](/icons/93824de.gif)
节点加1
![](/icons/93824dou.gif)
而我们
![](/icons/93824de.gif)
单链表是永远指向前
![](/icons/93824de.gif)
![](/icons/93824dou2.gif)
此外对于999+1=1000
![](/icons/93824dou.gif)
新得到
![](/icons/93824de.gif)
值
![](/icons/93824de.gif)
位数(4位)比原来
![](/icons/93824de.gif)
两个值(1个1位
![](/icons/93824dou.gif)
1个3位)都多
![](/icons/93824dou.gif)
所以我们将表头
![](/icons/93824de.gif)
值设置为0
![](/icons/93824dou.gif)
如果多出
![](/icons/93824yi.gif)
位来
![](/icons/93824dou.gif)
就暂时存放到表头
![](/icons/93824dou2.gif)
递归结束后
![](/icons/93824dou.gif)
如果表头为1
![](/icons/93824dou.gif)
就在新
![](/icons/93824de.gif)
链表外再加
![](/icons/93824yi.gif)
个新
![](/icons/93824de.gif)
表头
//head1 length > head2, so M > N
public
Add(
Link head1,
Link head2,
ref Link ![](/icons/93824new.gif)
Head,
M,
N)
{
// goto the end
(head1
null)
0;
temp = 0;
result = 0;
![](/icons/93824new.gif)
Head =
Link(
null, 0);
(M > N)
{
result = Add(head1.Next, head2,
ref ![](/icons/93824new.gif)
Head.Next, M - 1, N);
temp = head1.Data + result;
![](/icons/93824new.gif)
Head.Data = temp % 10;
temp >= 10 ? 1 : 0;
}
// M
N
{
result = Add(head1.Next, head2.Next,
ref ![](/icons/93824new.gif)
Head.Next, M - 1, N - 1);
temp = head1.Data + head2.Data + +result;
![](/icons/93824new.gif)
Head.Data = temp % 10;
temp >= 10 ? 1 : 0;
}
}
这里假设head1比head2长
![](/icons/93824dou.gif)
而且M、N分别是head1和head2
![](/icons/93824de.gif)
长度
12.单链表排序
无外乎是冒泡、选择、插入等排序思路方法
![](/icons/93824dou2.gif)
关键是交换算法
![](/icons/93824dou.gif)
需要额外考虑
![](/icons/93824dou2.gif)
第7题我编写了
![](/icons/93824yi.gif)
个交换算法
![](/icons/93824dou.gif)
在本题
![](/icons/93824de.gif)
排序过程中
![](/icons/93824dou.gif)
我们可以在外层和内层循环里面
![](/icons/93824dou.gif)
捕捉到pre1和pre2
![](/icons/93824dou.gif)
然后进行交换
![](/icons/93824dou.gif)
而无需每次交换又要遍历
![](/icons/93824yi.gif)
次单链表
![](/icons/93824dou2.gif)
在实战中
![](/icons/93824dou.gif)
我发现冒泡排序和选择排序都要求内层循环从链表
![](/icons/93824de.gif)
末尾向前走
![](/icons/93824dou.gif)
这明显是不合时宜
![](/icons/93824de.gif)
![](/icons/93824dou2.gif)
所以我最终选择了插入排序算法
![](/icons/93824dou.gif)
如下所示:
先给出基于
![](/icons/93824shuzu.gif)
![](/icons/93824de.gif)
算法:
public
![](/icons/93824int.gif)
![](/icons/93824zhk2.gif)
SortLink(
![](/icons/93824int.gif)
![](/icons/93824<img src=)
.gif' />)
{
for (
i = 0; i <
![](/icons/93824<img src=)
.gif' />.Length; i
![](/icons/93824jiajia.gif)
)
{
for (
j = i; j > 0; j--)
{
(
![](/icons/93824<img src=)
.gif' />[j] <
![](/icons/93824<img src=)
.gif' />[j - 1])
{
//swap arr[i] and arr[lowindex]
temp =
![](/icons/93824<img src=)
.gif' />[j];
![](/icons/93824<img src=)
.gif' />[j] =
![](/icons/93824<img src=)
.gif' />[j - 1];
![](/icons/93824<img src=)
.gif' />[j - 1] = temp;
}
}
}
![](/icons/93824<img src=)
.gif' />;
}
仿照上面
![](/icons/93824de.gif)
思想
![](/icons/93824dou.gif)
我们来编写基于Link
![](/icons/93824de.gif)
算法:
public
Link SortLink(
Link head)
{
Link pre1 = head;
Link pre2 = head.Next;
Link min =
null;
for (
Link curr1 = head.Next; curr1 !=
null; curr1 = min.Next)
{
(curr1.Next
null)
![](/icons/93824break.gif)
;
min = curr1;
for (
Link curr2 = curr1.Next; curr2 !=
null; curr2 = curr2.Next)
{
//swap curr1 and curr2
(curr2.Data < curr1.Data)
{
min = curr2;
curr2 = curr1;
curr1 = min;
pre1.Next = curr1;
curr2.Next = curr1.Next;
curr1.Next = pre2;
//
exchange element n-1 and n, no need to add reference from pre2 to curr2, because they are the same _disibledevent=>
(pre2 != curr2)
pre2.Next = curr2;
}
pre2 = curr2;
}
pre1 = min;
pre2 = min.Next;
}
head;
}
值得注意
![](/icons/93824de.gif)
是
![](/icons/93824dou.gif)
很多人
![](/icons/93824de.gif)
算法不能交换相邻两个元素
![](/icons/93824dou.gif)
这是
![](/icons/93824yinwei.gif)
pre2和curr2是相等
![](/icons/93824de.gif)
![](/icons/93824dou.gif)
如果此时还执行pre2.Next = curr2; 会造成
![](/icons/93824yi.gif)
个自己引用自己
![](/icons/93824de.gif)
环
交换指针很是麻烦
![](/icons/93824dou.gif)
而且效率也不高
![](/icons/93824dou.gif)
需要经常排序
![](/icons/93824de.gif)
东西最好不要用链表来实现
![](/icons/93824dou.gif)
还是
![](/icons/93824shuzu.gif)
好
![](/icons/93824yi.gif)
些
13.删除单链表中重复
![](/icons/93824de.gif)
元素
用Hashtable辅助
![](/icons/93824dou.gif)
遍历
![](/icons/93824yi.gif)
遍单链表就能搞定
![](/icons/93824dou2.gif)
实战中发现
![](/icons/93824dou.gif)
curr从表头开始
![](/icons/93824dou.gif)
每次判断下
![](/icons/93824yi.gif)
个元素curr.Netx是否重复
![](/icons/93824dou.gif)
如果重复直接使用curr.Next = curr.Next.Next; 就可以删除重复元素——这是最好
![](/icons/93824de.gif)
算法
![](/icons/93824dou2.gif)
唯
![](/icons/93824yi.gif)
![](/icons/93824de.gif)
例外就是表尾
![](/icons/93824dou.gif)
所以到达表尾
![](/icons/93824dou.gif)
就
![](/icons/93824break.gif)
跳出while循环
public
Link DeleteDuplexElements(
Link head)
{
Hashtable ht =
Hashtable![](/icons/93824kh.gif)
;
Link curr = head;
while (curr !=
null)
{
(curr.Next
null)
{
![](/icons/93824break.gif)
;
}
(ht[curr.Next.Data] !=
null)
{
curr.Next = curr.Next.Next;
}
{
ht[curr.Next.Data] =
"";
}
curr = curr.Next;
}
head;
}
结语:
单链表只有
![](/icons/93824yi.gif)
个向前指针Next
![](/icons/93824dou.gif)
所以要使用1-2个额外变量来存储当前元素
![](/icons/93824de.gif)
前
![](/icons/93824yi.gif)
个或后
![](/icons/93824yi.gif)
个指针
![](/icons/93824dou2.gif)
尽量用while循环而不要用for循环
![](/icons/93824dou.gif)
来进行遍历
![](/icons/93824dou2.gif)
哇塞
![](/icons/93824dou.gif)
我就是不用指针
![](/icons/93824dou.gif)
照样能“修改地址”
![](/icons/93824dou.gif)
达到和C
![](/icons/93824jiajia.gif)
同样
![](/icons/93824de.gif)
效果
![](/icons/93824dou.gif)
虽然很烦~
遍历
![](/icons/93824de.gif)
时候
![](/icons/93824dou.gif)
不要在while循环中head=head.Next;这样会改变原先
![](/icons/93824de.gif)
数据结构
![](/icons/93824dou2.gif)
我们要这么写:Link curr=head;然后curr=curr.Next;
有时我们需要临时把环切开
![](/icons/93824dou.gif)
有时我们需要临时把单链表首尾相连成
![](/icons/93824yi.gif)
个环
![](/icons/93824dou2.gif)
究竟是玩curr还是curr.Next
![](/icons/93824dou.gif)
根据区别题目而各有用武的地
![](/icons/93824dou.gif)
没有定论
![](/icons/93824dou.gif)
不必强求