链表
1、给一个链表的头节点 head 和一个整数 val ,删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
eg:
public ListNode removeElements(ListNode head, int val) {
while (head != null && head.val == val){ //特殊情况 第一个头节点即为删除的
head = head.next;
}
ListNode prev = head;
if (prev != null){
while (prev.next != null) {
if (prev.next.val == val)
prev.next = prev.next.next;//1-2-3 删除2
else
prev = prev.next; //往后走
}
}
return head;
}
}
2、 合并两个有序的链表
eg:输入:L1 = [1,2,4], L2 = [1,3,4]
输出:[1,1,2,3,4,4]
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode l3=new ListNode();
ListNode head=l3;
while(l1!=null && l2 !=null ){
if(l1 .val >l2 .val ){
head .next =l2;
head =head .next ;
l2 =l2 .next ;
}else {
head .next =l1;
head =head .next ;
l1 =l1 .next ;
}
}
if(l1!=null ){
head .next =l1;
}else {
head .next =l2;
}
**return l3.next ;**
}
}
//递归
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1==null)return l2;
if(l2==null)return l1;
if(l1.val<l2.val){
l1.next=mergeTwoLists(l1.next,l2);
return l1;
}else{
l2.next=mergeTwoLists(l1,l2.next);
return l2;
}
3、 删除一个升序链表中的相同元素
public ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return null;
}
ListNode prev = head;
while (prev.next!= null) {
if (prev.val==prev.next.val ) {
prev.next = prev.next.next;
}else{
prev = prev.next;
}
}
return head ;
}
//递归
public ListNode deleteDuplicates(ListNode head) {
if(head==null||head.next==null){
return head;
}
head.next=deleteDuplicates(head.next);
if(head.val==head.next.val){
head=head.next;
}
return head;
}
4、判断两个链表是否相交
一、两个链表不含有环
1、暴力破解、逐个两个链表,判断第一个链表的每个结点是否在第二个链表中,时间复杂度为O(len1*len2),耗时很大。
2、hash计数法:若两个链表相交,则两个链表就会有共同的结点;而结点地址又是结点唯一标识。因而判断两个链表中是否存在地址一致的节点,就可以知道是否相交了。
3、第一个链表的终节点转到第二个链表的头节点、第二个链表的终节点转到第一个链表的头节点、转为判断是否有环
4、若两个链表相交、则各个链表的最后一个节点一定相同
public ListNode getNode(ListNode headA, ListNode headB) {
ListNode p1 = headA;
ListNode p2 = headB;
while (p2.next!= null) {
p2 = p2.next;
}
while (p1.next != null) {
p1 = p1.next;
}
if(p1==p2 ){
return p1 ;
} return headA ;
}
}
二、两个链表含有环
如果链表有环且相交,那么这两个链表都是有环的。
找到第一个链表的环点,然后将环断开(当然不要忘记了保存它的下一个节点),然后再来遍历第二个链表,如果发现第二个链表从有环变成了无环,那么他们就是相交的嘛,否则就是不相交的了。
(2)当一个链表中有环,一个链表中没有环时,两个链表必不相交。
5、判断两个链表是否相交 并返回相交节点
eg:listA = [0,9,1,2,4], listB = [3,2,4],
相交
解题思路
1、假设两个链表不相交、最终指向null
2、两个链表相交
先遍历第一个链表到他的尾部,然后将尾部的next指针指向第二个链表(尾部指针的next本来指向的是null)。这样两个链表就合成了一个链表,判断原来的两个链表是否相交也就转变成了判断新的链表是否有环的问题了:即判断单链表是否有环?
设交集链表长c,链表1除交集的长度为a,链表2除交集的长度为b,有
a + c + b = b + c + a
若无交集,则a + b = b + a
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode h1 = headA, h2 = headB;
while (h1 != h2) { //当h1与h2相交的时候,返回此节点
h1 = h1 == null ? headB : h1.next; //当h1与h2相交的时候,返回此节点
h2 = h2 == null ? headA : h2.next; //如果遍历到链表尾部,h2指向链表A的头节点。
}
return h1;
}
6、删除链表的倒数第 n 个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:使用一趟扫描实现
解法1:双指针
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode pre = head; //快指针
ListNode nt = head; //慢指针
for(int i = 0; i < n; i++){ //快指针比慢指针多走 n下
pre = pre.next;
}
if (pre == null){
return head.next;
}
while (pre.next != null){ //快指针走到最后、找到倒数第n个
nt = nt.next;
pre = pre.next;
}
nt.next = nt.next.next;
return head;
}
解法2:
public ListNode removeNthFromEnd(ListNode head, int n) {
if(head.next==null) return null;
ListNode list = head,list1 = head;
if(n==1){
while(list.next.next!=null){
list = list.next;
}
list.next = null;
}
else{
while(list!=null){
if(n>0) n--;
else list1 = list1.next;
list = list.next;
}
list1.val = list1.next.val;
list1.next = list1.next.next;
}
return head;
}
7、旋转链表
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
eg:输入head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]
在这里插入代码片
8、合并两个排序链表
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
在这里插入代码片
9、环形链表
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
思路:首先初始化快指针 fast = head.next.next 和 slow = head.next,
此时快指针走的路长为2, m慢指针走的路长为1,之后令快指针每次走两步,
慢指针每次走一步,这样快指针走的路长始终是慢指针走的路长的两倍,
若不存在环,直接返回None,
若存在环,则 fast 与 slow 肯定会在若干步之后相遇;
public boolean hasCycle(ListNode head) {
if (head == null) {
return false;
}
ListNode prev = head;
ListNode fast = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
prev = prev.next;
if (prev == fast) {
return true;
}
}
return false;
}
10、找出链表有环的入环点
解题思路:先判断链表是否有环、后找入环点
假如链表起点到入环的第一个节点A的长度为a【未知】,到快慢指针相遇的节点B的长度为(a + b)【这个长度是已知的】。
现在我们想知道a的值,注意到快指针fast始终是慢指针prev走过长度的2倍,所以慢指针prev从B继续走(a + b)又能回到B点,如果只走a个长度就能回到节点A。
起点到A的长度是a,用一个从起点开始的新指针q和从节点B开始的慢指针prev同步走,相遇的地方必然是入环的第一个节点A。
prev走a+b步 起始位置走a步 相遇便是入环点
public ListNode detectCycle(ListNode head) {
if(head==null){
return head;
}
boolean cycle=false;
ListNode prev=head;
ListNode fast=head;
//找链表是否存在环 链表快慢指针相遇即有环 相遇的节点肯定实在环内
while (fast.next != null&&fast.next.next != null) {
prev=prev.next;
fast =fast.next.next;
if(prev==fast){
cycle= true;
break;
}
}
//找入环的第一个节点
if(cycle){
ListNode pde=head;
while (prev != pde) {
prev = prev.next;
pde = pde.next;
}
return pde;
} else
return null;
}
11、链表反转
迭代
public static ListNode reverseListIterative(ListNode head) {
ListNode prev = null; //前指针节点
ListNode curr = head; //当前指针节点
//每次循环,都将当前节点指向它前面的节点,然后当前节点和前节点后移
while (curr != null) {
ListNode nextTemp = curr.next; //临时节点,暂存当前节点的下一节点,用于后移
curr.next = prev; //将当前节点指向它前面的节点
prev = curr; //前指针后移
curr = nextTemp; //当前指针后移
}
return prev;
}
递归
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode node = reverseList(head.next);
head.next.next = head;
head.next = null;
return node;
}
剑指offer
反转链表
1、 链表反转返回的是一个数组
不用递归、不用栈
public static int[] reversePrint(ListNode head) {
ListNode node = head;
int count = 0;
while (node != null) {
++count;
node = node.next;
}
int[] nums = new int[count];
node = head;
for (int i = count - 1; i >= 0; --i) {
nums[i] = node.val;
node = node.next;
}
return nums;
}
返回链表
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> temp = new ArrayList<>();
ArrayList<Integer> res = new ArrayList<>();
while(listNode!=null){
temp.add(listNode.val);
listNode = listNode.next;
}
// 反转赋值
for(int i=temp.size()-1;i>=0;--i){
res.add(temp.get(i));
}
return res;
}
使用递归的思想
class a{ //返回的时是数组
int [] are;
public int[] reversePrint(ListNode head) {
if(head == null){
return new int[0];
}
rPrint(head,1);
return are;
}
public void rPrint(ListNode head,int n) {
if(head==null||head.next==null){
are = new int[n];
are[are.length - n] = head.val;
return;
}
rPrint(head.next,n+1);
are[are.length - n] = head.val;
}
2、删除链表的节点
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点
public ListNode deleteNode(ListNode head, int val) {
if(head == null) return head;
ListNode cur = head;
ListNode pre = null;
if(cur.val == val) return head.next;
while(cur.val != val) {
pre = cur;
cur = cur.next;
}
pre.next = pre.next.next;
return head;
}
链表中倒数第k的节点
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode prv=head;
ListNode fast=head;
for(int i = 0; i < k; i++){
fast=fast.next;
}
if (fast == null){
return head;
}
while(fast.next!=null){
prv=prv.next;
fast=fast.next;
}
head=prv.next;
return head;
}
复杂链表的复制
1、哈希表,空间和时间都是O(n)
public Node copyRandomList(Node head) {
if (head == null) {
return head;
}
//map中存的是(原节点,拷贝节点)的一个映射
Map<Node, Node> map = new HashMap<>();
for (Node cur = head; cur != null; cur = cur.next) {
map.put(cur, new Node(cur.val));
}
//将拷贝的新的节点组织成一个链表
for (Node cur = head; cur != null; cur = cur.next) {
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
}
return map.get(head);
}
}
2、原地修改,空间复杂度为O(1)
public Node copyRandomList(Node head) {
if (head == null) {
return head;
}
//将拷贝节点放到原节点后面,例如1->2->3这样的链表就变成了这样1->1'->2->2'->3->3'
for (Node node = head, copy = null; node != null; node = node.next.next) {
copy = new Node(node.val);
copy.next = node.next;
node.next = copy;
}
//把拷贝节点的random指针安排上
for (Node node = head; node != null; node = node.next.next) {
if (node.random != null) {
node.next.random = node.random.next;
}
}
//分离拷贝节点和原节点,变成1->2->3和1'->2'->3'两个链表,后者就是答案
Node newHead = head.next;
for (Node node = head, temp = null; node != null && node.next != null;) {
temp = node.next;
node.next = temp.next;
node = temp; //把node 像后移动到temp位置上
}
return newHead;
}
两个链表的第一个公共节点
输入两个链表,找出它们的第一个公共节点。
设交集链表长c,链表1除交集的长度为a,链表2除交集的长度为b,有
a + c + b = b + c + a
若无交集,则a + b = b + a
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode h1 = headA, h2 = headB;
while (h1 != h2) { //当h1与h2相交的时候,返回此节点
h1 = h1 == null ? headB : h1.next; //当h1与h2相交的时候,返回此节点
h2 = h2 == null ? headA : h2.next; //如果遍历到链表尾部,h2指向链表A的头节点。
}
return h1;
}