链表汇总(未完待续)

题解

双指针(快慢指针)

环形链表

力扣传送门

思路一:快慢指针
假如两个人绕操场跑步,跑的快的人必定追得上慢的人。这就是快慢指针的思想,所以通过设置步长为2和1的两个指针,从链表都出发,不断往后移,如果存在环形链表,两指针必定会相遇。

public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while(slow != null && fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
            if(fast == slow){
                return true;
            }
        }
        return false;
    }
}

思路二:HashSet集合,具体见下一题

环形链表 II

力扣传送门

思路一:借助Set集合的唯一性,往Set添加节点,若链表有循环,说明节点会重复遇到。

public class Solution {
    public ListNode detectCycle(ListNode head) {
        Set<ListNode> set = new HashSet<>();
        while(head != null) {
            if(set.contains(head)) {
                return head;
            }
            set.add(head);
            head = head.next;
        }
        return null;
    }
}

!思路二:双指针,节省空间
由于快慢指针只是判断链表中有环,并不能得出相遇点就是交叉点。所以需要了解其中的一点规律才能做出:快慢指针相遇时,慢指针走到交叉节点的路径等于从首节点走到交叉节点的路径
具体证明见力扣
简单理解如下图:-4到2等于3到2的距离,-4才是相遇点

public class Solution {    
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        ListNode tmp = null;
        while(fast != null && fast.next != null && slow != null) {            
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow) {
                tmp = head;
                while(tmp != null && slow != null && tmp != slow) {
                    tmp = tmp.next;
                    slow = slow.next;
                }
                break;
            }
        }        
        return tmp;
    }
}
链表中倒数第k个节点

力扣传送门

思路一:list集合(代码略)
用list集合存放所有节点,然后通过get(size-k)即可返回结果

代码略

思路二:快慢指针
让b指针先走k步,然后a指针和b指针同时开始往后走,待b走完时,a所到的位置就是倒数第k个节点,保持因为a和b的距离一直保持是k

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {   
        ListNode tmp = head;//b指针
        while(k-- > 0) {//先走k步
            tmp = tmp.next;
        }
        while(tmp != null) {//同时走
            tmp = tmp.next;
            head = head.next;
        }
        return head;//a指针
    }
}

思路三:二次遍历
第一次先求长度len,第二遍历到len-k的位置即可

代码略

链表相交

力扣传送门

思路一:HashMap
对每个节点进行计数(不是节点值,是节点的引用),若存在计数为2的,说明该节点为交点。

代码略

思路二:交换遍历
难以确定交点的原因在于两路径到达交点的长度不一致,于是我们可以通过交换路径实现长度一致。例如a路径长度是4,b路径长度是5,a遍历完就跳去遍历b,b遍历完就去遍历a,那么两者的长度就能得到互补,也就是9。

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode a = headA;
        ListNode b = headB;
        while(a!=b) {
            a = (a == null) ? headB : a.next;
            b = (b == null) ? headA : b.next;
        }
        return a;//b也可
    }
}

链表骚操作

删除链表中的节点

力扣传送门

思路:节点删除
a->b->c->d,删除b,就是把a指向b的引用指向c即可。但是我们并不知道a指向b的引用,只知道有个引用指向b。
a指向b这种属于引用删除,还有一种删除就是替换,既然删不了b,那就用c替换b,即b.val = b.next.val。对于c后面的节点就没必要用替换了,而是只需要删除c就可以,因为我们知道b指向c的引用,所以可以删除,即b.next = b.next.next

class Solution {
    public void deleteNode(ListNode node) {    	
        if(node == null) return;
        if(node.next!=null) {
            node.val = node.next.val; //替换
            node.next = node.next.next; //删除
        }        
    }
}
移除链表元素

力扣链接:https://leetcode-cn.com/problems/remove-linked-list-elements/

思路:递归或迭代

class Solution {
    public ListNode removeElements(ListNode head, int val) {        

        //递归
        if(head == null) return head;//边界
        //判断当前节点,相等就移除,引用往后移,只考虑此刻,后面的节点由递归搞定。
        if(head.val == val)  {
            head= head.next;
            head = removeElements(head,val);
        } else {
            head.next = removeElements(head.next,val);
        }
        return head;//返回当前符合节点的引用
        

        //简化递归
        if(head == null) return head;//边界
        head.next = removeElements(head.next,val);
        //返回当前符合节点的引用,要么当前,要么后一个,只考虑此刻,后面的节点由递归搞定。
        return head.val == val ? head.next : head;


        //迭代--可用假节点或者替换删除的方式减少代码
        if(head == null) return head;        
        //固定头节点
        while(head !=null && head.val == val) {
            head = head.next;
        }
        //移除
        ListNode tmp = head;
        while(tmp != null && tmp.next != null) {
            if(tmp.next.val == val) {
                tmp.next = tmp.next.next;
            } else {
                tmp = tmp.next;
            }
        }
        return head;
    }
}
移除重复节点

思路:HashSet
根据Set集合唯一性,如果遇到重复,就进行链表元素移除即可

class Solution {
    public ListNode removeDuplicateNodes(ListNode head) {
        HashSet<Integer> set = new HashSet();
        ListNode front = head;
        ListNode current = head;
        
        while(current != null) {
            if(set.contains(current.val)) { //存在就移除
                front.next = current.next;
            } else {
                set.add(current.val);
                front = current;     //记录上一个节点,用于删除
            }        
            current = current.next;  //遍历一个
        }
        return head;
    }
}

改进:元素有范围,可使用数组代替Set

思路二:暴力双层for循环

class Solution {
    public ListNode removeDuplicateNodes(ListNode head) {
        ListNode first = head;        
        while(first != null) {
            ListNode front = first; 
            ListNode current = first.next;               
            while(current != null) {
                if(current.val == first.val) {
                    front.next = current.next;
                } else {
                    front = current;
                }
                current = current.next;                    
            }
            first = first.next;
        }
        return head;
    }
}
旋转链表

力扣门:https://leetcode.cn/problems/rotate-list/

思路:简单点说就是将后半部分与前半部分进行拼接

class Solution {
    public ListNode rotateRight(ListNode head, int k) {        
        if(head == null || head.next == null) return head; //特判
		//记录链表的长度,用于取模
        int l = 0;
        //记录最后一个节点,用于拼接
        ListNode last = null;
        ListNode tmp = head;
        while(tmp != null) {
            last = tmp;
            tmp = tmp.next;
            l++;
        }
        k = k % l;
        if(k == 0) return head; //特判
        //获取后半部分的第一个节点,用于返回结果
        ListNode prev = head;
        int lk = l - k;
        while(lk-- > 1) {        
            prev = prev.next;
        }        
        ListNode res = prev.next;   //头部     
        prev.next = null;    //末尾断开连接,否则会变成循环链表
        last.next = head;    //拼接
        return res;
    }
}

数学

二进制链表转整数

力扣门

思路:二进制与十进制的转化
101 = ((((0 x 2)+1) x 2)+0) x 2)+1=5
其实就是1 x 22 + 0 x 21 + 1 x 20 ,上式就是高中的秦九韶算法

class Solution {
    public int getDecimalValue(ListNode head) {
        int res = 0;
        while(head != null) {
            res = res * 2 + head.val;
            head = head.next;
        }
        return res;
    }
}
两数相加

力扣门

思路:不难,就是加法运算,不过每次需要记录进位。其中如果选择新建节点能省去不少代码,如果把相加后的结果赋值给其中一个链表,那就会多出一些比较繁琐的判断。

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode res = l1;
        ListNode prev = null; //保留前一个节点
        int jw = 0;  //进位
        while(l1 != null && l2 != null) {
            prev = l1;
            //直接存到l1,不新建节点
            l1.val = l1.val + l2.val + jw;
            if(l1.val > 9) {
                l1.val = l1.val - 10;
                jw = 1;
            } else {
                jw = 0;
            }            
            l1 = l1.next;
            l2 = l2.next;
        }
        //判断
        if(l2 != null) {
            //如果是l2不为空,也就是比l1长,需要prev来拼接l2了
            prev.next = l2;    
            while(l2 != null) {
                prev = l2;           
                l2.val = l2.val + jw;
                if(l2.val > 9) {
                    l2.val = l2.val - 10;
                    jw = 1;
                } else {
                    jw = 0;
                    break;
                }
                l2 = l2.next;  
            }     
            //最后一位
            if(jw == 1) prev.next = new ListNode(1);
        } else {            
            while(l1 != null) {            
                prev = l1;
                l1.val = l1.val + jw;
                if(l1.val > 9) {                
                    l1.val = l1.val - 10;
                    jw = 1;
                } else {
                    jw = 0;
                    break;
                }
                l1 = l1.next;             
            }         
            //最后一位
            if(jw == 1) prev.next = new ListNode(1);
        }  
        return res;              
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值