leetcode-链表

总结:

主要是迭代(遍历)和递归。迭代遍历的话一般可能用的多的是双指针,快慢指针,三指针(pre,cur,next)这些,最好画图理清链表节点next域的指向要如何修改,相交链表、环这种问题适合尝试双指针。因为链表的遍历查询只能从头节点一次向后遍历,所以检索的效率不高,这时时间复杂度为O(1)的哈希表可以发挥作用。递归的话则是将问题拆成小问题了,主要时考虑清楚每一个节点是如何处理的,最好也是画图分析比较清楚一点。

剑指 Offer 06. 从尾到头打印链表
链表的特点是只能从头节点开始向尾节点方向遍历,要实现这种逆序打印链表,得依靠类似栈这种先进后出的数据结构,或者是递归调用(递归也是栈的一种形式),或者是得先确定链表的长度然后以此创建数组,便可实现逆序的效果。

//go实现
func reversePrint(head *ListNode) []int {
	backup:=head
	//统计链表节点个数
	n:=0
	for head!=nil{
		n++
		head=head.Next
	}
	//创建对应大小的数组,重新遍历链表并从右到左放入数组中
	nums:=make([]int,n)
	for i:=n-1;i>=0;i--{
		nums[i]=backup.Val
		backup=backup.Next
	}
	return nums
}
//java实现
class Solution {
    public int[] reversePrint(ListNode head) {
    	//使用栈
        Stack<Integer> stack=new Stack<>();
        while(head!=null){
            stack.push(head.val);
            head=head.next;
        }
        int n=stack.size();
        int[] res=new int[n];
        for(int i=0;i<n;i++){
            res[i]=stack.pop();
        }
        return res;
    }
}

剑指 Offer 24. 反转链表

这种问题一般就递归或者迭代,迭代的话主要是需要另外声明两个指针来记录前后两个链表节点,从头节点开始不断修改链表节点的指向。最好画图,可以看清如何操作链表的指针。

//go实现
func reverseList(head *ListNode) *ListNode {
	//递归实现
	if head==nil || head.Next==nil{
		return head
	}
	nex:=head.Next
	newHead:=reverseList(nex)
	nex.Next=head
	head.Next=nil
	return newHead
}
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head==null || head.next==null) return head;
        ListNode nex=head.next;
        ListNode newHead=reverseList(nex);
        nex.next=head;
        head.next=null;
        return newHead;
    }
}

剑指 Offer 25. 合并两个排序的链表

双指针迭代或者递归

func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {
	if l1==nil{
		return l2
	}
	if l2==nil{
		return l1
	}
	if l1.Val<l2.Val{
		l1.Next=mergeTwoLists(l1.Next,l2)
		return l1
	}else{
		l2.Next=mergeTwoLists(l1,l2.Next)
		return l2
	}
}

160. 相交链表

方法一是使用哈希表,遍历一条链表的节点并将其加入哈希表中,然后遍历另一条链表查看是否有节点已经存在于哈希表中,如果有则是第一个相交的节点

方法二是双指针,想法比较好,见代码

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA==null || headB==null) return  null;
        ListNode pa=headA,pb=headB;
        while(pa!=pb){
            pa= pa==null? headB:pa.next;
            pb= pb==null? headA:pb.next;
        }
        return pa;
    }
}
func FindFirstCommonNode( pHead1 *ListNode ,  pHead2 *ListNode ) *ListNode {
    // write code here
    p1:=pHead1
	p2:=pHead2
	for p1!=p2{
		if p1!=nil{
			p1=p1.Next
		}else {
			p1=pHead2
		}
		if p2!=nil{
			p2=p2.Next
		}else{
			p2=pHead1
		}
	}
	return p1
}

剑指 Offer II 022. 链表中环的入口节点

方法一,哈希表

方法二,快慢指针,这个方法需要脑筋稍微转一下,理解一下为什么这样就是入口节点,可以参考题解剑指 Offer II 022. 链表中环的入口节点(双指针) - 链表中环的入口节点 - 力扣(LeetCode) (leetcode-cn.com)

func detectCycle(head *ListNode) *ListNode {
	//快慢指针解法
	slow:=head
	fast:=head
	for fast!=nil && fast.Next!=nil{
		fast=fast.Next.Next
		slow=slow.Next
		//快慢指针第一次相遇
		if fast==slow{
			slow=head
			for slow!=fast{
				slow=slow.Next
				fast=fast.Next
			}
			//当fast==slow时,此时正是入口处
			return fast
		}
	}
	return nil
}

剑指 Offer 22. 链表中倒数第k个节点

快慢指针

func getKthFromEnd(head *ListNode, k int) *ListNode {
    fast:=head
	slow:=head
	for fast!=nil && k>0{
		fast=fast.Next
		k--
	}
	if k>0{
		return nil
	}
	for fast!=nil{
		fast=fast.Next
		slow=slow.Next
	}
	return slow
}
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        //快慢指针
        ListNode fast=head,slow=head;
        while(k>0){
            if(fast==null) return null;
            fast=fast.next;
            k--;
        }         
        while(fast!=null){
            fast=fast.next;
            slow=slow.next;
        }   
        return slow;
    }
}

剑指 Offer 35. 复杂链表的复制

如果是复制普通链表的话,只需要遍历一次链表,以尾插法不断建立新节点即可。本题的难点在于在构建新链表的时候还需要构建各节点random域的引用指向,而构建的难点在于如何找到对应的新节点,而且可能这时对应的新节点还未构建。

难点在于如何找到random域对应的新节点,可以使用哈希表,记录原链表节点和新链表对应节点的键值对映射关系。第一次遍历创建新链表的时候random先指向原链表节点,第二次遍历新链表,从哈希表中查询,将random域的指向改为对应新节点的指向。

func copyRandomList(head *Node) *Node {
	originToCopyMap:=make(map[*Node]*Node)//原始节点到新节点的映射
	copyHead:=&Node{0,nil,nil}
	copyTail:=copyHead
	//第一次遍历原链表,复制新链表,并初始化Val和Next域的值,记录原始节点到新节点的映射关系至map中
	cur:=head
	for cur!=nil{
		node:=&Node{cur.Val,nil,nil}
		node.Random=cur.Random//先记录原始节点的
		copyTail.Next=node
		copyTail=copyTail.Next
		originToCopyMap[cur]=node
        cur=cur.Next
	}
	//第二次遍历新链表,从map中找到对应的Random域节点在新链表中对应的节点,进行替换
	cur=copyHead.Next
	for cur!=nil{
		if cur.Random!=nil{
			cur.Random=originToCopyMap[cur.Random]
		}
        cur=cur.Next
	}
	return copyHead.Next
}

83. 删除排序链表中的重复元素

因为链表已经排好序了,一次遍历迭代即可,每次比较cur与cur.next

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head==null || head.next==null) return head;
        ListNode pre=head;
        ListNode cur=pre.next;
        while(cur!=null){
            if(cur.val==pre.val){
                pre.next=cur.next;
                cur=pre.next;
            }else{
                pre=cur;
                cur=pre.next;
            }
        }
        return head;
    }
}

你可能感兴趣的:(leetcode,链表,leetcode,数据结构)