目录
offer25合并两个排序的链表
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode dummyHead=new ListNode(-1); ListNode tail=dummyHead; while (l1!=null&&l2!=null){ if (l1.val>l2.val){ tail.next=l2; l2=l2.next; tail=tail.next; }else { tail.next=l1; l1=l1.next; tail=tail.next; } } if (l1==null){ tail.next=l2; } if (l2==null){ tail.next=l1; } return dummyHead.next; } }
23合并K个升序列表
- 这里我们就是想找到n个链表中最小的一个数,加入到里面,一直到加完,我们可以利用优先级队列,Java提供了最小堆,我们把每个链表的头节点放进去,则堆头的数就是链表所有链表中最小的那个数,然后在将这个链表的下一个数加入,一直取到这个优先级队列为空
- 优先级队列要求我们的元素必须是有排序规则的,所以我们传入一个comparator
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode mergeKLists(ListNode[] lists) { if (lists.length==0) return null; ListNode dummyHead=new ListNode(-1); ListNode tail=dummyHead; PriorityQueue<ListNode> minHeap=new PriorityQueue<>(lists.length, new Comparator<ListNode>() { @Override public int compare(ListNode o1, ListNode o2) { return o1.val- o2.val; } }); for (ListNode head:lists) { if (head!=null){ minHeap.add(head); } } while (!minHeap.isEmpty()){ ListNode node=minHeap.poll(); tail.next=node; tail=tail.next; if (node.next!=null){ minHeap.add(node.next); } } return dummyHead.next; } }
Offer22 链表中倒数第k个节点
- 利用快慢指针,让快指针先走k步,然后让两个指针一起走,等快指针走完,慢指针就刚刚走到倒数第k个结点
public class Offer22 { /** * 利用快慢指针 * @param head * @param k * @return */ public ListNode getKthFromEnd(ListNode head, int k) { if (head==null){ return null; } ListNode dummyHead=new ListNode(-1); dummyHead.next=head; ListNode node=dummyHead.next; ListNode node1=dummyHead.next; int i=0; while (i<k){ node=node.next; i++; } while (node!=null){ node1=node1.next; node=node.next; } return node1; } }
19删除链表的倒数第N个节点
![]()
- 思路,先用快慢指针来定位要删除的指针,再来一个指针来指向删除结点的前驱节点
- 就是offer22题加一个前驱结点的结合
class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode dummyHead=new ListNode(-1); dummyHead.next=head; ListNode prev=dummyHead; ListNode low=dummyHead.next; ListNode fast=dummyHead.next; int i=0; while (i<n){ fast=fast.next; i++; } while (fast!=null){ fast=fast.next; low=low.next; prev=prev.next; } prev.next=low.next; low=low.next=null; return dummyHead.next; } }
Offer06从尾到头打印链表
- 利用递归先走到链表的结尾,然后再开始添加元素,也就是在所谓的后序位置操作,来添加元素,如果想按顺序添加,就在前序位置去进行添加元素的操作
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { int []arr=null; int index=0; public int[] reversePrint(ListNode head) { helper(head,0); return arr; } public void helper(ListNode head,int length){ if (head==null){ arr=new int[length]; return; } helper(head.next,length+1); arr[index++]=head.val; } }
Offer18删除链表的结点
- 最重要的就是要找到删除节点的前驱节点,如果没有虚拟头节点,那么需要去区分头节点和其他节点,如果有虚拟头节点就不需要
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode deleteNode(ListNode head, int val) { ListNode dummyHead=new ListNode(-1); dummyHead.next=head; ListNode prev=dummyHead;//前驱结点 while (prev.next!=null){ ListNode node=prev.next; if (node.val==val){ prev.next=node.next; node=node.next=null; break; }else { prev=prev.next; } } return dummyHead.next; } }
Offer24反转链表
利用迭代实现(递归实现)
ListNode dummyHead=new ListNode(-1); ListNode tail=dummyHead; public ListNode reverseList(ListNode head) { helper(head); return dummyHead.next; } private void helper(ListNode head) { if (head==null){ return; } helper(head.next); //后序位置 tail.next=head; tail=tail.next; //从后面断链接,不会影响前面的链表 head.next=null; }
利用递归语义解决问题
//利用递归函数将一个链表反转 public ListNode reverseList(ListNode head) { if (head==null||head.next==null){ return head; //终止条件,如果只剩一个结点,不需要处理 } ListNode next=head.next;//头节点的后面一个结点,也是反转后的最后一个结点 ListNode reverseHead=reverseList(head.next);//将后面的链表反转,返回反转后的头节点 next.next=head; head.next=null; return reverseHead; }
25K个一组翻转链表
- 这道题就是多个子链表反转,然后将多个子链表链接在一起,有一个细节就是当子链表的长度小于k的时候,这个链表就不需要除了、
- 其实链表是天然的递归和迭代的数据结构,所以用递归和迭代的都能解决
- 先完成一个功能,就是将链表反转,然后返回反转链表后的头节点,因为是区间链表,我们使用迭代比较简单,但是递归也能做
private ListNode reveser(ListNode left, ListNode right) { ListNode prev=null; ListNode cur=left; ListNode next=left; while (cur!=right){ next=cur.next; cur.next=prev; prev=cur; cur=next; } return prev; }
- 然后实现怎么讲链表变成一个个区间链表,然后链接起来
public ListNode reverseKGroup(ListNode head, int k) { if (head==null){ return null; } ListNode left=head; ListNode right=head; //区间是左闭右开, for (int i = 0; i < k; i++) { if (right==null){ return left; } right=right.next; } ListNode node= reveser(left,right);//返回反转链表的头节点,也就是最后一个结点 ListNode nextHead= reverseKGroup(right,k); left.next=nextHead;//最开始的点被反转到最后面 return node; }
Offer35复杂链表的复制
- 这道题的难点就是在于,我们不能确定他的random到底对应哪个结点,它不像是我们的next,知道next的指向就是链表的下一个元素,random的指向可能是在这个结点前面,或者在后面,或者是null,还可能是本身
- 对于这种,我们合理利用Map这种结构,Map这中key-value,是一对一的结构,我们可以让新构造的结点跟对应的结点对应,然后遍历一遍Map就可以
class Solution { public Node copyRandomList(Node head) { if (head==null){ return null; } HashMap<Node,Node> map=new HashMap<>(); for (Node node=head;node!=null;node=node.next){ Node newNode=new Node(node.val); map.put(node,newNode); } for (Node node=head;node!=null;node=node.next) { map.get(node).next=map.get(node.next); map.get(node).random=map.get(node.random); } return map.get(head); } }
![]()
- 让A走完去走B,B走完去走A ,逻辑上就相当于走了同一条的链表
class Solution { ListNode getIntersectionNode(ListNode headA, ListNode headB) { ListNode node1=headA; ListNode node2=headB; if (headA==null||headB==null){ return null; } while (node1!=node2){ if (node1==null){ node1=headB; }else { node1=node1.next; } if (node2==null){ node2=headA; }else{ node2=node2.next; } } if (node1==null){ return null; }else { return node1; } } }
2两数相加
class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { int carry=0; ListNode dummyHead=new ListNode(-1); ListNode tail=dummyHead; while (l1!=null||l2!=null||carry!=0){ int val1=l1==null?0: l1.val; int val2=l2==null?0: l2.val; int sum=val1+val2+carry; carry=sum/10; sum=sum%10; ListNode node=new ListNode(sum); tail.next=node; tail=tail.next; if (l1!=null){ l1=l1.next; } if (l2!=null){ l2=l2.next; } } return dummyHead.next; } }
141环形链表
public class Solution { public boolean hasCycle(ListNode head) { // 快慢指针初始化指向 head ListNode slow = head, fast = head; // 快指针走到末尾时停止 while (fast != null && fast.next != null) { // 慢指针走一步,快指针走两步 slow = slow.next; fast = fast.next.next; // 快慢指针相遇,说明含有环 if (slow == fast) { return true; } } // 不包含环 return false; } }
142环形链表2
- 首先思路肯定是用快慢指针去遍历,如果它有环的话,必定是在环的范围里面相遇,我们假设快指针的速度是慢指针的速度的2倍,那么快指针走了2k,慢指针走了k相遇
fast
一定比slow
多走了k
步,这多走的k
步其实就是fast
指针在环里转圈圈,所以k
的值就是环长度的「整数倍」。- 相遇点到环的起点就是k-m,如果我们从相遇点继续走k-m步,就凑齐k步,也就肯定能到环起点
public class Solution { public ListNode detectCycle(ListNode head) { ListNode low=head; ListNode fast=head; boolean flag=false; while (fast!=null&&fast.next!=null){ low=low.next; fast=fast.next.next; if (low==fast){ flag=true; break; } } // 上面的代码类似 hasCycle 函数 if (fast == null || fast.next == null) { // fast 遇到空指针说明没有环 return null; } // 重新指向头结点 low = head; // 快慢指针同步前进,相交点就是环起点 while (low != fast) { fast = fast.next; low = low.next; } return low; } }
148排序链表
- 这道题就是归并排序的链表实现,归并排序每次都要找到中间结点,作为左右区间的分割(对于链表找中间结点,用快慢指针),然后就是两个有序列表的合并(offer25),有一个细节,就是对于数组,我们提供索引来区分区间,但是链表我们是通过next来连接,所以我们需要将左右两个区间的连接断了
- 归并思想,就是将一个区间先分成更小的区间(中止条件,区间只有一个元素或者0个肯定是有序的),然后将一个个有序的小区间变成有序的大区间,直到整个区间有序
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode sortList(ListNode head) { //利用归并排序的思想 if (head==null||head.next==null){ return head; } ListNode mid=midNode(head); ListNode x=head; while (x.next!=mid){ x=x.next; } x.next=null;//将左右区间断开 ListNode left= sortList(head);//排序左区间 ListNode right= sortList(mid);//排序右区间 return merge(left,right);//因为两个子链表断了,肯定需要重新链接起来 如果想提供效率, // 来比较左区间的最大值小于由区间的最小值,就直接链接就行 } private ListNode merge(ListNode left, ListNode right) { ListNode dummyHead=new ListNode(-1); ListNode tail=dummyHead; while (left!=null&&right!=null){ if (left.val<=right.val){ tail.next=left; left=left.next; tail=tail.next; }else { tail.next=right; right=right.next; tail=tail.next; } } if (left==null){ tail.next=right; } if (right==null){ tail.next=left; } return dummyHead.next; } private ListNode midNode(ListNode head) { ListNode fast=head; ListNode slow=head; while (fast!=null&&fast.next!=null){ fast=fast.next.next; slow=slow.next; } return slow; } }
OfferII27回文链表
- 思路:我们就是要比较123和321的逆序是不是相同
- 通过快慢指针找到中间结点(12 3 2 1),我们比较的是12 和21,所以当我们遇到奇数个节点,就让slow前进一步,去掉这个多余的中间节点(判断条件:当fast不为null的时候,说明有奇数个节点)
- 然后讲后面的链表反转,比较这两个链表是否每个值都相同
class Solution { public boolean isPalindrome(ListNode head) { //首先通过快慢指针来获得链表的中间结点 // 有奇数和偶数之分 1 2 3 2 1 为奇数个,通过快慢指针获得的中间结点是3,我们应该比较的是12 21 // 1 2 3 3 2 1 获得的就是是左边的三 我们应该比较的是123 321 ListNode mid=midNode(head); ListNode right=head; ListNode left=reverse(mid); while (left!=null){ if (left.val!=right.val){ return false; } left=left.next; right=right.next; } return true; } private ListNode reverse(ListNode mid) { ListNode prev=null; ListNode cur=mid; ListNode next=mid; while (cur!=null){ next=cur.next; cur.next=prev; prev=cur; cur=next; } return prev; } private ListNode midNode(ListNode head) { ListNode fast=head; ListNode slow=head; while (fast!=null&&fast.next!=null){ fast=fast.next.next; slow=slow.next; } if (fast!=null){ //对应奇数个结点,我们让slow多走一步 slow=slow.next; } return slow; } }
对比数组类型的归并排序
class Solution { public int[] sortArray(int[] nums) { //采用归并排序 sortHelperBymerge(nums,0,nums.length-1); return nums; } private void sortHelperBymerge(int[] nums, int left, int right) { //left-right是我们需要排序的区间 if (left>=right){ //说明区间为空,或者区间就一个元素,不需要排序 return; } int mid=left+(right-left)/2; //开始归 sortHelperBymerge(nums,left,mid); sortHelperBymerge(nums,mid+1,right); //开始并 if (nums[mid]>nums[mid+1]){ //说明这两个子区间 直接合起来不是有序的,需要我们自己去合并 //这里的思想就是合并两个有序的数组 merge(nums,left,mid,right); } } /** * 合并两个有序区间为一个更大的有序区间 * [left,mid] [mid+1,right] * [0,1],[2,3] * @param nums * @param left * @param mid * @param right */ private void merge(int[] nums, int left, int mid, int right) { int []arr=new int[right-left+1]; int index=0; for (int i = left; i <=right ; i++) { arr[index++]=nums[i]; //将这两个区间的值都放在一个数组中 } int l1=left;//用来指向当前左区间的指针 int l2=mid+1;//用来指向当前右区间的指针 int i=left; while (i<=right){ if (l1>mid){ //说明当前左区间已经遍历完,只能去右区间的值 nums[i]=arr[l2-left]; l2++; i++; }else if(l2>right){ //说明当前右区间已经遍历完,只能走左区间 nums[i]=arr[l1-left]; l1++; i++; }else if (arr[l1-left]>=arr[l2-left]){ //当前左区间的指针的值大于等于有区间的值 nums[i]=arr[l2-left]; l2++; i++; }else { nums[i]=arr[l1-left]; l1++; i++; } } } }
86分割链表
![]()
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode partition(ListNode head, int x) { ListNode dummyHead1=new ListNode(-1); ListNode tail1=dummyHead1; ListNode dummyHead2=new ListNode(-1); ListNode tail2=dummyHead2; while (head!=null){ if (head.val<x){ tail1.next=head; ListNode node=head; head=head.next; node=node.next=null; tail1=tail1.next; }else { tail2.next=head; ListNode node=head; head=head.next; node=node.next=null; tail2=tail2.next; } } tail1.next=dummyHead2.next; return dummyHead1.next; } }