1. 单链表的插入,删除,搜索
2. 反转链表
实现思路:
- 表头指向原链表的尾结点
- 创建一个空的prev指针,指向当前结点,也就是记录下一个结点的上一个结点
- 修改结点的next指针,指向prev指向的结点,注:头结点的next指向空结点
- 依次从头结点进行遍历,即可
3. 合并两个递增排序的链表
实现思路:
假设上面虚线内的两个链表,上面的链表为链表1,下面的链表为链表2。
- 如果链表1的头结点小于链表2的头结点的值,把链表1的头结点作为合并后链表的头结点;
- 在剩余结点中,链表2的头结点小于链表1的头结点的值,把链表2的头结点链接在之前已经合并好的链表的头结点之后。
- 依次递归完成合并链表。
4. 遍历一次,找到倒数第k个结点
实现思路:
正常找到倒数第K个结点,可以先反转链表,然后遍历链表到第K个结点即可。不过需要两次遍历。
- 创建两个指针,第一个指针先从0遍历到第k - 1个结点,然后第二个指针从头结点开始与第一个指针(当前位置)一起遍历。
- 当第一个指针遍历到尾结点时,第二个指针指向的结点就是第K个结点。
5. 给定头指针和一个结点指针,在O(1)时间内删除该结点
实现思路:
一般删除结点,都要遍历找到要删除的结点,时间复杂度为O(n)
- 把删除结点的下一个结点内容覆盖当前删除结点的内容
- 删除结点指向删除结点的下下个结点,也就是跳过了删除结点的下一个结点
- 这里需要注意的是:头结点和尾结点的处理。
- 如果头结点是唯一的结点,删除头结点后,需要把头结点设置为null。
- 如果删除结点是尾结点,尾结点没有下一个结点,这时就需要从头开始遍历,删除尾结点。
6. 输入两个链表,找到它们的第一个公共结点。
实现思路:
首先,要明确一点,从第一个公共结点之后,所有的结点都是重合的,不可能再分叉。也就是向左旋转的Y的形状。所以,如果有公共结点,一定不会在长链表比短链表多出的前半部分。
- 首先,遍历得到两个链表的长度,并得到他们长度的差值k。
- 长的链表先从0开始遍历到第k-1个结点,然后,短链表从头开始和长链表(当前位置)一起遍历。
7. 判断链表是否有环
实现思路:快慢指针
首先创建两个指针1和2(在C#里就是两个对象引用),同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针1每次向下移动一个节点,让指针2每次向下移动两个节点,然后比较两个指针指向的节点是否相同。如果相同,则判断出链表有环,如果不同,则继续下一次循环。
此方法也可以用一个更生动的例子来形容:在一个环形跑道上,两个运动员在同一地点起跑,一个运动员速度快,一个运动员速度慢。当两人跑了一段时间,速度快的运动员必然会从速度慢的运动员身后再次追上并超过,原因很简单,因为跑道是环形的。
假设链表的环长是S。那么循环会进行S次,可以简单理解为O(N)。除了两个指针以外,没有使用任何额外存储空间,所以空间复杂度是O(1)。
8. 如何找出有环链表的入环点?
实现思路:首先判断是否有环,然后找到入环点。如何找到入环点,这里需要一点推导,
- 假设链表环的长度为r,慢指针走了s步,快指针就走了2s步。当快慢指针相遇时,2s = s + r,得到s = r。
- 假设链表的总长度为L,环入口与相遇点距离为x,表头到环入口点的距离为a,得出a + x = s,根据上面的结论,得到a + x = r,交换一下得到a = r - x。
- 同时,L - a = r,也就是链表总长度减去表头到环入口点的距离等于环的长度,这个直接可以得到的结论。
- 因此,a = L - a - x。(L - a - x)就是相遇点到环入口的距离。
最终得到结论:表头到环入口点的距离等于相遇点到环入口的距离。
根据上面的结论,我们可以从链表头结点、相遇点分别设置一个指针,每次走一步,两个指针必然相遇,而且相遇第一点就是环入口点。
using System;
using System.Collections;
using System.Collections.Generic;
namespace StructScript
{
public class SequentialLinkedList<T>
{
// 表头结点
private Node mHead;
private int mCount;
// 比较器,用于合并两个链表时,比较
private Comparer<T> mComparer;
public SequentialLinkedList()
{
mHead = null;
mCount = 0;
mComparer = Comparer<T>.Default;
}
// 添加了一个circleNode参数,可以人为设置有环链表,一般不需要设置
// 为了设置有环链表,同时还增加了返回值,返回当前插入的结点
public Node Add(T value, Node circleNode = null)
{
if (value == null)
{
throw new ArgumentNullException();
}
if (mHead == null)
{
mHead = new Node(value, null);
mCount++;
return mHead;
}
else
{
if (!Contains(value))
{
//这里每添加一个新的项,可以往前依次添加,新增项作为新的表头
//mFirst = new Node(value, mFirst);
//往后添加的,需要遍历所有节点,在最后的位置添加新的项
Node node = mHead;
while (node.next != null)
{
node = node.next;
}
node.next = new Node(value, circleNode);
mCount++;
return node.next;
}
}
return null;
}
public bool Remove(T value)
{
if (value == null)
{
throw new ArgumentNullException();
}
if (mHead == null)
{
return false;
}
// 如果是表头
if (value.Equals(mHead.value))
{
mHead = mHead.next;
mCount--;
return true;
}
// 不是表头
Node node = mHead;
while (node.next != null)
{
if (value.Equals(node.next.value))
{
node.next = node.next.next;
mCount--;
return true;
}
node = node.next;
}
return false;
}
// 判断链表是否有环
public bool HasCircle()
{
Node slowNode = mHead;
Node fastNode = mHead;
// 使用快慢指针,慢指针每走一步,快指针走两步
while(fastNode != null && fastNode.next != null)
{
slowNode = slowNode.next;
fastNode = fastNode.next.next;
// 如果快慢指针相遇了则有环
if(slowNode.value.Equals(fastNode.value))
{
return true;
}
}
return false;
}
// 找到环链表的环入口点
public Node FindEnterCircleNode()
{
bool hasCircle = false;
Node slowNode = mHead;
Node fastNode = mHead;
// 使用快慢指针,慢指针每走一步,快指针走两步
while (fastNode != null && fastNode.next != null)
{
slowNode = slowNode.next;
fastNode = fastNode.next.next;
// 如果快慢指针相遇了则有环
if (slowNode.value.Equals(fastNode.value))
{
hasCircle = true;
break;
}
}
// 如果是环链表
if (hasCircle)
{
// 一个指针从相遇点开始、一个从表头结点开始遍历
// 每次走一步,相遇的第一个结点就是环入口点
fastNode = mHead;
while(slowNode != null && slowNode.next != null)
{
// 这里一定要先进行比较,再往下走
// 如果链表整个都是环,当环的长度为奇数时,相遇点就是头结点
if (slowNode.value.Equals(fastNode.value))
{
return fastNode;
}
slowNode = slowNode.next;
fastNode = fastNode.next;
}
}
return null;
}
// 给定两个链表,返回第一个公共结点
public Node GetPublicNode(SequentialLinkedList<T> st1, SequentialLinkedList<T> st2)
{
int st1Length = st1.Count;
int st2Length = st2.Count;
if(st1Length == 0 || st2Length == 0)
{
return null;
}
int diff = 0;
Node node1;
Node node2;
if(st1Length > st2Length)
{
diff = st1Length - st2Length;
node1 = st1.mHead;
node2 = st2.mHead;
}
else
{
diff = st2Length - st1Length;
node1 = st2.mHead;
node2 = st1.mHead;
}
// 先在长链表上执行diff步,然后同时在两链表上遍历
for (int i = 0; i < diff; i++)
{
node1 = node1.next;
}
// 因为在第一个公共结点之后,两链表的所有结点都是重合的,相同的
// 所以,公共结点不可能在长链表比短链表多出来的前半部分,直接比较剩下的部分即可
while(node1 != null && node2 != null)
{
if(node2.value.Equals(node1.value))
{
return node1;
}
node1 = node1.next;
node2 = node2.next;
}
return null;
}
// 给定头指针和一个结点指针,在O(1)时间内删除该结点
public void RemoveNode(Node node)
{
if (node != null)
{
// 删除结点是头结点
if (node.value.Equals(mHead.value))
{
mHead = mHead.next;
mCount--;
return;
}
// 删除结点是尾结点
else if (node.next == null)
{
Node node1 = mHead;
while (node1.next != null)
{
if (node.value.Equals(node1.next.value))
{
node1.next = node1.next.next;
mCount--;
return;
}
node1 = node1.next;
}
}
else
{
// 用下一个指针的内容覆盖当前结点的内容
node.value = node.next.value;
// 当前结点指向下下个结点
node.next = node.next.next;
mCount--;
}
}
}
// 反转链表
public void Reverse()
{
Node node = mHead;
Node prev = null;
while (node != null)
{
// 保存原结点的next指向的结点,因为下面会修改结点next指向的结点
Node next = node.next;
// 表头指向原链表的尾结点
if (node.next == null)
{
mHead = node;
}
node.next = prev;
// 保存当前节点作为下一个节点的next指向的节点
prev = node;
// node指向下一个节点,继续遍历
node = next;
}
}
// 合并两个增量排序的链表
public SequentialLinkedList<T> Merge(SequentialLinkedList<T> st1, SequentialLinkedList<T> st2)
{
st1.mHead = Merge(st1.mHead, st2.mHead);
return st1;
}
public Node Merge(Node head1, Node head2)
{
if (head1 == null)
{
return head2;
}
else if (head2 == null)
{
return head1;
}
Node mergeHead = new Node(default(T), null);
// 如果链表1的头结点小于链表2的头结点,把链表1的头结点作为合并链表的头结点
// 剩余结点,继续比较头结点大小,并把较小的头结点放在合并链表的头结点之后。
if (mComparer.Compare(head1.value, head2.value) < 0)
{
mergeHead = head1;
mergeHead.next = Merge(head1.next, head2);
}
else
{
mergeHead = head2;
mergeHead.next = Merge(head1, head2.next);
}
return mergeHead;
}
public void Print()
{
for (Node current = mHead; current != null; current = current.next)
{
Console.Write(current.value + " ");
}
}
// 找到链表中倒数第k个结点,在一次遍历中
public Node FindKFromTail(SequentialLinkedList<T> st1, int k)
{
if (st1.mHead == null)
{
return null;
}
// 创建两个指针p1,p2,
// p1先遍历到k-1的位置,然后p2开始遍历
// 当p1遍历到链表尾部时,p2的位置就是倒数第k个结点
Node p1 = st1.mHead;
for (int i = 0; i < k - 1; i++)
{
// 防止k小于链表长度
if (p1.next != null)
{
p1 = p1.next;
}
else
{
return null;
}
}
Node p2 = st1.mHead;
while (p1.next != null)
{
p1 = p1.next;
p2 = p2.next;
}
return p2;
}
public void Clear()
{
for (Node current = mHead; current != null; current = current.next)
{
Node tempNode = current;
tempNode.next = null;
tempNode.value = default(T);
}
mHead = null;
mCount = 0;
}
public bool Contains(T value)
{
if (value == null)
{
throw new ArgumentNullException();
}
for (Node current = mHead; current != null; current = current.next)
{
if (value.Equals(current.value))
{
return true;
}
}
return false;
}
public int Count
{
get
{
return mCount;
}
}
public class Node
{
public T value;
public Node next;
public Node(T value, Node next)
{
this.value = value;
this.next = next;
}
}
}
}
附上,测试用例:
using System;
namespace StructScript
{
public class TestSequentialLinkedList
{
static void Main(string[] args)
{
// 创建一个有环链表
SequentialLinkedList<int> st11 = new SequentialLinkedList<int>();
st11.Add(1);
st11.Add(2);
SequentialLinkedList<int>.Node newNode = st11.Add(3);
st11.Add(4);
st11.Add(5);
st11.Add(6);
st11.Add(7, newNode);
Console.WriteLine("当前链表是否有环:" + st11.HasCircle());
Console.WriteLine("当前链表的环入口点是:" + st11.FindEnterCircleNode().value);
SequentialLinkedList<int> st = new SequentialLinkedList<int>();
st.Add(1);
st.Add(3);
st.Add(5);
st.Add(7);
SequentialLinkedList<int> st1 = new SequentialLinkedList<int>();
st1.Add(2);
st1.Add(4);
st1.Add(6);
st1.Add(8);
//if (st.Contains(1))
//{
// Console.WriteLine("包含1");
//}
//st.Remove(1);
//得到第一个公共结点
SequentialLinkedList<int>.Node node = st.GetPublicNode(st, st1);
if (node != null)
{
Console.WriteLine("第一个公共结点是:" + node.value);
}
else
{
Console.WriteLine("没有公共结点");
}
// 合并两个链表
st = st.Merge(st, st1);
st.Print();
Console.WriteLine();
int value = st.FindKFromTail(st, 4).value;
Console.WriteLine("倒数第4个的值:" + value);
Console.WriteLine("反转链表:");
st.Reverse();
st.Print();
Console.ReadLine();
}
}
}