代码随想录Day5总结

这几天把链接学习了一下,题目坚持学习完了,今天在这里做一个总结

先对链表的知识点作一个汇总

        链表与数组作为最常用的数据结构,他们最本质的区别就是,链表是不需要连续内存空间的,它是通过指针存储的内存地址去操作。而数组则必须是需要一段连续的内存空间

        链表常见的几个类型:单链表,双链表,循环链表。

        链表在插入和删除这块性能是很高,不像数组,只能通过更新,或者再复制出一段新的连续的内存空间。只需要考虑相邻的结点来操作,并且能做到时间复杂度是O(1).

        但是链表在查询这块就比较低效了,链表在代码中只能通过迭代去一个个遍历,数组只需要知道是哪个index,就可以直接通过内存地址查询到存储的内容了。所以,链表的随机访问并没有数组做的高效,时间复杂度为O(n)

        单链表:

链表1

双链表:

链表2

循环链表

链表4

 链表结点的代码定义:

type ListNode struct {
	next  *ListNode
	value interface{}
}

初始化结点:

newNode := &ListNode{nil, 0}

 链表的代码定义:

type LinkedList struct {
	head   *ListNode
	length uint
}

 链表vs数组

链表-链表与数据性能对比

 链表添加结点

链表-添加节点

链表删除结点

 链表-删除节点

 203.移除链表元素

力扣题目链接(opens new window)https://leetcode.cn/problems/remove-linked-list-elements/

题意:删除链表中等于给定值 val 的所有节点。

示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例 2:
输入:head = [], val = 1
输出:[]

示例 3:
输入:head = [7,7,7,7], val = 7
输出:[]

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */

func removeElements(head *ListNode, val int) *ListNode {
    node:=&ListNode{Next:head}
    for tmp:=node;tmp.Next!=nil; {
        if tmp.Next.Val == val {
            tmp.Next = tmp.Next.Next
        } else {
            tmp = tmp.Next
        }
    }
    return node.Next
}

核心就是找到值等于val然后删除。

设置虚拟头结点,即是哨兵结点

dummyNode:=&ListNode{Next:head}

该节点是指向head的 

链表结点删除:

p.Next = p.Next.Next

迭代则是

p = p.Next

最后返回:

return dummyNode.Next

dummyNode所指向的是head,这才是原来链表的头结点。

707.设计链表

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

哨兵结点+单链表的写法:

type MyLinkedList struct {
    head *ListNode
    size int
}

func Constructor() MyLinkedList {
    return MyLinkedList{&ListNode{},0}
}

//获取index节点的value
func (this *MyLinkedList) Get(index int) int {
    if index < 0 || index >= this.size {
        return -1
    }
    p:=this.head
    for i:=0;i<=index;i++{
        p=p.Next
    }
    return p.Val
}

func (this *MyLinkedList) AddAtHead(val int)  {
    this.AddAtIndex(0,val)
}


func (this *MyLinkedList) AddAtTail(val int)  {
    this.AddAtIndex(this.size,val)
}

//根据位置插入
func (this *MyLinkedList) AddAtIndex(index int, val int)  {
    if index > this.size {
        return
    }
    index = max(index, 0)
    p := this.head
    for i := 0; i < index&& p != nil; i++ {
        p = p.Next
    }
    toAdd := &ListNode{val, p.Next}
    p.Next = toAdd
    this.size++

}


func (this *MyLinkedList) DeleteAtIndex(index int)  {
    if index < 0 || index >= this.size {
        return
    }
    p:=this.head
    for i:=0;i<index&& p != nil;i++{
        p=p.Next
    }
    t := p.Next
	p.Next = t.Next
	//t.Next = nil
	this.size--
    
}

func max(a, b int) int {
    if b > a {
        return b
    }
    return a
}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * obj := Constructor();
 * param_1 := obj.Get(index);
 * obj.AddAtHead(val);
 * obj.AddAtTail(val);
 * obj.AddAtIndex(index,val);
 * obj.DeleteAtIndex(index);
 */

代码还是比较好理解的 

双链表的写法:

//循环双链表
type MyLinkedList struct {
	dummy *Node
}

type Node struct {
	Val  int
	Next *Node
	Pre  *Node
}

//仅保存哑节点,pre-> rear, next-> head
/** Initialize your data structure here. */
func Constructor() MyLinkedList {
	rear := &Node{
		Val:  -1,
		Next: nil,
		Pre:  nil,
	}
	rear.Next = rear
	rear.Pre = rear
	return MyLinkedList{rear}
}

/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
func (this *MyLinkedList) Get(index int) int {
	head := this.dummy.Next
	//head == this, 遍历完全
	for head != this.dummy && index > 0 {
		index--
		head = head.Next
	}
	//否则, head == this, 索引无效
	if 0 != index {
		return -1
	}
	return head.Val
}

/** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
func (this *MyLinkedList) AddAtHead(val int) {
	dummy := this.dummy
	node := &Node{
		Val: val,
		//head.Next指向原头节点
		Next: dummy.Next,
		//head.Pre 指向哑节点
		Pre: dummy,
	}

	//更新原头节点
	dummy.Next.Pre = node
	//更新哑节点
	dummy.Next = node
	//以上两步不能反
}

/** Append a node of value val to the last element of the linked list. */
func (this *MyLinkedList) AddAtTail(val int) {
	dummy := this.dummy
	rear := &Node{
		Val: val,
		//rear.Next = dummy(哑节点)
		Next: dummy,
		//rear.Pre = ori_rear
		Pre: dummy.Pre,
	}

	//ori_rear.Next = rear
	dummy.Pre.Next = rear
	//update dummy
	dummy.Pre = rear
	//以上两步不能反
}

/** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
func (this *MyLinkedList) AddAtIndex(index int, val int) {
	head := this.dummy.Next
	//head = MyLinkedList[index]
	for head != this.dummy && index > 0 {
		head = head.Next
		index--
	}
	if index > 0 {
		return
	}
	node := &Node{
		Val: val,
		//node.Next = MyLinkedList[index]
		Next: head,
		//node.Pre = MyLinkedList[index-1]
		Pre: head.Pre,
	}
	//MyLinkedList[index-1].Next = node
	head.Pre.Next = node
	//MyLinkedList[index].Pre = node
	head.Pre = node
	//以上两步不能反
}

/** Delete the index-th node in the linked list, if the index is valid. */
func (this *MyLinkedList) DeleteAtIndex(index int) {
	//链表为空
	if this.dummy.Next == this.dummy {
		return
	}
	head := this.dummy.Next
	//head = MyLinkedList[index]
	for head.Next != this.dummy && index > 0 {
		head = head.Next
		index--
	}
	//验证index有效
	if index == 0 {
		//MyLinkedList[index].Pre = index[index-2]
		head.Next.Pre = head.Pre
		//MyLinedList[index-2].Next = index[index]
		head.Pre.Next = head.Next
		//以上两步顺序无所谓
	}
}

206.反转链表

力扣题目链接(opens new window)

题意:反转一个单链表。

示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

#双指针

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func reverseList(head *ListNode) *ListNode {
    cur:=head
    var pre *ListNode

    for cur != nil {
        tmp:=cur.Next
        cur.Next=pre
        
        pre=cur        
        cur=tmp
    }
    return pre
}

利用dummyNode 还有tmp存储cur结点,cur赋给pre,然后tmp赋给cur

递归写法实际逻辑还是和双指针式意一样的

//递归
func reverseList(head *ListNode) *ListNode {
    return help(nil, head)
}

func help(pre, head *ListNode)*ListNode{
    if head == nil {
        return pre
    }
    next := head.Next
    head.Next = pre
    return help(head, next)
}

24. 两两交换链表中的节点

建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。(哨兵结点又来了)

初始化: 

dummyNode:=&ListNode{0,head}
cur:=dummyNode

开始操作结点

首先要确保cur.next 和cur.next.next不是nil

 然后开始相邻的结点交换

node1:=cur.next
node2:=cur.next.next

cur.next=node2

node1.next=node2.next
node2.next=node1
//完成交换,然后移动到node1,下个循环开始处理下个结点和下下个结点
cur = node1

最后还是要return dummyNode.Next

19.删除链表的倒数第N个节点 

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

 

方法一:

计算链表长度

func removeNthFromEnd(head *ListNode, n int) *ListNode {
        length := getLength(head)
        dummyNode := &ListNode{0,head}
        //length-n+1
        cur:=dummyNode
        index:=0
        for cur!=nil {
            index+=1
            if length-index+1 == n {
                cur.Next=cur.Next.Next
            }
            cur=cur.Next
        }
        return dummyNode.Next
        
}

func getLength(head *ListNode) (length int) {
    for ; head != nil; head = head.Next {
        length++
    }
    return
}

还是使用双指针

需要保持fast指针的位置比slow指针多n个结点,直到fast指向null,结点遍历完,这时候slow和fast依旧把持n个结点的距离,则可以删除slow的下一个结点

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func removeNthFromEnd(head *ListNode, n int) *ListNode {
        
        dummyNode := &ListNode{0,head}
        cur:=head
        pre:=dummyNode
        fast:=1
        for cur != nil {
            cur = cur.Next
            if fast > n{
                pre = pre.Next
            }
            fast++
        }
        pre.Next = pre.Next.Next
        return dummyNode.Next
        
}

#面试题 02.07. 链表相交

力扣题目链接(opens new window)

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

图示两个链表在节点 c1 开始相交:

此题我们可以先计算出nodeA和nodeB的距离,然后让两结点在同一位置起跑即可

需要算出step的数值以及谁是fast谁是slow

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func getIntersectionNode(headA, headB *ListNode) *ListNode {
    lenA,lenB := 0,0
    curA := headA
    curB := headB
    
    for curA != nil {
        curA = curA.Next
        lenA++
    }
    for curB != nil {
        curB = curB.Next
        lenB++
    }
    
    var step int
    var fast, slow *ListNode
    // 请求长度差,并且让更长的链表先走相差的长度
    if lenA > lenB {
        step = lenA - lenB
        fast, slow = headA, headB
    } else {
        step = lenB - lenA
        fast, slow = headB, headA
    }

    for i:=0; i < step; i++ {
        fast = fast.Next
    }

    for fast != slow {
        fast = fast.Next
        slow = slow.Next
    }

    return fast

}

双指针法(代码比较简短,但确实比较难理解):

 

如果链接相交 则a+c+b = b+c+a 就会有公共结点。

如果不相交的话,最终l1=l2=null 结束循环

func getIntersectionNode(headA, headB *ListNode) *ListNode {

    l1,l2:=headA,headB
    
    for l1 != l2 {
        if l1 != nil{
            l1=l1.Next
        } else {
            l1=headB
        }

        if l2 != nil {
            l2=l2.Next
        } else {
            l2 =headA
        }
    }

    return l1

}

142.环形链表II

题意: 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

主要考察两知识点:

  • 判断链表是否环
  • 如果有环,如何找到这个环的入口

 可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。

 数学推导公式:

slow:x+y  fast:x+y+n(y+z)

。。。。。

x=(n-1)(y+z)+z 当n等1的时候 x=z

这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点

也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。

让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。

 

那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。

其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。

func detectCycle(head *ListNode) *ListNode {
    slow, fast := head, head
    for fast != nil && fast.Next != nil {
        slow = slow.Next
        fast = fast.Next.Next
        if slow == fast {
            for slow != head {
                slow = slow.Next
                head = head.Next
            }
            return head
        }
    }
    return nil
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值