swift实现链表及链表的相关特性,最后附上两道算法题
一、链表的基本操作实现
1、定义链表节点
class ListNode{
var val : Int!
var next : ListNode!
init(_ val : Int) {
self.val = val
self.next = nil
}
}
2、链表的基本操作
class List{
var head : ListNode?
var tail : ListNode?
//尾插法
func appendToEnd(_ val : Int){
if tail == nil {
tail = ListNode(val)
head = tail
}else{
tail?.next = ListNode(val)
head = tail?.next
}
}
//头插法
func appendToHead(_ val : Int){
if head == nil{
head = ListNode(val)
tail = head
}else{
let tmp = ListNode(val)
tail?.next = head
head?.next = tmp
}
}
}
以上就是链表的基本操作,那么我们如何利用链表来解决问题呢?请看下面的题目
二、算法
1、Dummy节点和尾插法
什么是Dummy节点?
答: dummy节点的作用就是一个虚拟的头前节点,由于我们不知道需要返回的新链表的头节点是哪一个,可能是原链表的第一个/中间/最后一个/不存在nil,而dummy涵盖了所有的情况,所以可以用dummy.next 方便返回最终需要的头节点
题目描述:给出一个链表和一个值x,要求将链表中所有小于x的值放到左边,所有大于x的值放到右边,并且原链表的节点顺序不能变
例如:1->5->3->2->4->2,给定 x=3 ,则返回为 1->2->2->5->3->4
思路:对于这道题目,我们需要先处理左边 即小于x的节点, 再处理右边 即大于x的节点,最后再将左右两边连起来
那么问题来了,我们如何处理左边?如何处理右边?处理完成后如何链接?
1)首先,将题目简化:
给出一个链表和一个值x,要求只保留链表中所有小于x的值,原链表的节点顺序不能变
例如:1->5->3->2->4->2,给定 x=3 ,则返回为 1->2->2
2)题目1)的实现:采用 尾插法 ,遍历链表,将小于x的节点接入新的链表即可
func getLeftList(_ head : ListNode?, _ x : Int)->ListNode? {
let dummy : ListNode = ListNode(0)
var pre = dummy, node = head
while node != nil {
if node!.val < x {
pre.next = node
pre = node!
}
node = node!.next
}
//防止构成环
pre.next = nil
return dummy.next
}
3)右边用同样的方法处理,最后只需要让左边的的尾节点 指向 右边的头节点即可
具体实现
func partition(_ head : ListNode?, _ x : Int)->ListNode? {
let prevDummy : ListNode = ListNode(0), postDummy = ListNode(0)
var prev = prevDummy, post = postDummy
var node = head
while node != nil {
if node!.val < x {
prev.next = node
prev = node!
}else{
post.next = node
post = node!
}
node = node!.next
}
//防止构成环
post.next = nil
//拼接左右链表
prev.next = postDummy.next
return prevDummy.next
}
4)验证
let one = ListNode(1)
let two = ListNode(5)
let three = ListNode(3)
let four = ListNode(2)
let five = ListNode(4)
let six = ListNode(2)
one.next = two
two.next = three
three.next = four
four.next = five
five.next = six
let result = self.partition(one, 3)
var dummy = result
while dummy != nil {
print("node val: \(dummy?.val)")
dummy = dummy?.next
}
结果为:
2、快行指针
什么是快行指针?
答:就是两个指针访问链表,一个在前,一个在后,或者一个移动块,一个移动慢
题目一描述:如何检测一个链表中是否有环?
例如:
思路:用两个指针同时访问链表,其中一个指针的速度是另一个指针的2倍,如果她们变成相等的了,那么该链表就有环
func hasCycle(_ head : ListNode?)->Bool{
var slow = head, fast = head
while fast != nil && fast?.next != nil {
slow = slow?.next
fast = fast?.next.next
//判断节点是否一致时,需要节点遵守Equatable协议
if slow==fast {
return true
}
}
return false
}
extension ListNode : Equatable{
static func == (lhs: ListNode, rhs: ListNode) -> Bool {
//判断链那个节点的值以及节点的下一个节点值是否一致,一致则节点相等,反之不相等
let result = (lhs.val==rhs.val)&&(lhs.next.val==rhs.next.val)
return result
}
}
验证:
let one = ListNode(1)
let two = ListNode(5)
let three = ListNode(3)
let four = ListNode(2)
let five = ListNode(4)
let six = ListNode(2)
one.next = two
two.next = three
three.next = four
four.next = five
five.next = six
let result = self.hasCycle(one)
print("result : \(result)")
result结果为: false
在上面的链表基础上加上,构成环
six.next = three
result结果为: true
题目二描述:删除链表中倒数第n个节点(快行指针一前一后的例子)
例如:1->2->3->4->5,n=2,返回为 1->2->3->5
思路:两个指针的移动速度相同,但在一开始,第一个指针(在指向头节点之前)就落后第二个指针n个节点,接着,两个指针同时移动,当第二个指针移动到尾节点时,第一个节点的下一个节点就是我们要删除的节点
具体实现:
func removeNthFromEnd(_ head : ListNode?, _ n : Int)->ListNode?{
guard let head = head else {
return nil
}
let dummy = ListNode(0)
dummy.next = head
var prev : ListNode? = dummy
var post : ListNode? = dummy
//设置后一个节点的初始位置,与前一个节点相差n个节点
for _ in 0..<n {
if post == nil {
break
}
post = post?.next
}
//同时移动前后节点
while post != nil && post?.next != nil {
prev = prev?.next
post = post?.next
}
//删除节点
prev?.next = prev?.next.next
//返回删除节点后链表头节点
return dummy.next
}
验证
let one = ListNode(1)
let two = ListNode(2)
let three = ListNode(3)
let four = ListNode(4)
let five = ListNode(5)
one.next = two
two.next = three
three.next = four
four.next = five
let result = self.removeNthFromEnd(one, 2)
var dummy = result
while dummy != nil {
print("node val : \(dummy?.val)")
dummy = dummy?.next
}
用swift处理链表问题需要注意的细节:
1)一定要注意头节点可能就是nil,所以给定链表后,要看清楚head是不是 可选类型 optional,然后判断是不是要处理这种边界条件
2)注意每个节点的next可能是nil,如果不为nil,则用“!”修饰变量,在赋值的时候,也要注意“!”将optional 节点传给非optional 节点的情况