- 首先推荐一个GitHub项目:labuladong。本文是我在做labuladong算法小抄中链表题目的相关源码。
- 处理链表问题通常可以有迭代法和递归法两种。迭代法思路比较简单,但是写法通常比较复杂;而递归法的思路相对较难,但是写法通常比较简单。在直接做LeetCode之前,先实现几个简单问题的迭代法和递归法。
问题1:翻转整条单链表
方法一:迭代法
public ListNode reverse(ListNode head) {//翻转整条单链表
ListNode pre, cur, nxt;
pre = null; cur = head; nxt = head;
while (cur != null) {
nxt = cur.next;
// 逐个结点反转
cur.next = pre;
// 更新指针位置
pre = cur;
cur = nxt;
}
// 返回反转后的头结点
return pre;
}
方法二:递归法
public ListNode reverse(ListNode head) {//翻转整条单链表
if (head.next == null) return head;
ListNode last = reverse(head.next);
head.next.next = head;
head.next = null;
return last;
}
问题2:翻转单链表中的前n个结点
方法一:迭代法
public ListNode reverseN(ListNode head,int n){//翻转链表中的前n个节点
ListNode last = head;//last存储第n+1个节点
for (int i = 0; i < n; i++) {
if(last == null) return head;//不足n个节点就不需要翻转
last = last.next;
}
ListNode pre = last,cur = head,nxt;//pre先初始化为last
for (int i = 0; i < n; i++) {
nxt = cur.next;
//逐个节点翻转
cur.next = pre;
//更新节点
pre = cur;
cur = nxt;
}
return pre;
}
方法二:递归法
ListNode successor = null;//后驱节点
public ListNode reverseN(ListNode head,int n){//翻转单链表中的前n个节点
if(n == 1){
successor = head.next;//successor记录第n+1个节点
return head;
}
ListNode last = reverseN(head.next,n-1);//翻转以head.next为起点的前n-1个节点
head.next.next = head;
head.next = successor;//让翻转之后的head节点与后面的节点相连接
return last;
}
92. 翻转链表 II
问题描述:翻转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
方法一:迭代法
public ListNode reverseBetween(ListNode head, int m, int n) {//翻转单链表中的区间[m,n]中的节点
ListNode cur = head,pre_cur = null,last = head;
for (int i = 0; i < m-1; i++) {
pre_cur = cur;//pre_cur是cur的前一个节点。如果cur=head,则pre_cur为null
cur = cur.next;//cur是第m个节点
}
for (int i = 0; i < n; i++) {
last = last.next;//last存储第n+1个节点
}
ListNode pre = last,nxt;//pre初始化为last
for (int i = 0; i < n-m+1; i++) {//翻转n-m+1次
nxt = cur.next;
//逐个节点翻转
cur.next = pre;
//更新节点
pre = cur;
cur = nxt;
}
if(pre_cur ==null){//即m=1,即从第一个节点开始翻转
return pre;
}
else{//翻转后的节点与前半部分链连接
pre_cur.next = pre;
}
return head;
}
方法二:递归法:
public ListNode reverseBetween(ListNode head, int m, int n){//翻转单链表中的区间[m,n]中的节点
if(m==1){//此时,问题转化为翻转单链表中的前n个节点
return reverseN(head,n);
}
head.next = reverseBetween(head.next,m-1,n-1);//递归head.next
return head;
}
25. K个一组翻转链表
public ListNode reverseN(ListNode head,int n){//递归法:翻转链表中的前n个节点
ListNode last = head;//last存储第n+1个节点
for (int i = 0; i < n; i++) {
last = last.next;
}
ListNode pre = last,cur = head,nxt;//pre先初始化为last
for (int i = 0; i < n; i++) {
nxt = cur.next;
//逐个节点翻转
cur.next = pre;
//更新节点
pre = cur;
cur = nxt;
}
return pre;
}
public ListNode reverseKGroup(ListNode head, int k) {
if(head==null){
return null;
}
//对于以head为头的链表,则tail指向第k-1个结点
ListNode tail = head;
//进行递归的头结点
ListNode newHead = head;
//如果不足k个结点,直接返回
for(int i=0;i<k;i++){
tail = newHead;
if(tail==null){
return head;
}
newHead = newHead.next;
}
//递归
tail.next = reverseKGroup(newHead,k);
//翻转前面k个结点
return reverseN(head,k);
}
234. 回文链表
方法一:快慢指针找中点再翻转
缺点:这个方法会破坏链表原有的结构
//翻转整条单链表
private ListNode reverse(ListNode head){
if(head==null || head.next==null){
return head;
}
ListNode last = reverse(head.next);
head.next.next = head;
head.next = null;
return last;
}
public boolean isPalindrome(ListNode head) {
if(head==null || head.next==null){
return true;
}
//快慢指针找到链表的中点
ListNode fast = head,slow = head;
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
}
//翻转后半部分链表
//如果链表的个数是单数
if(fast!=null){
slow = slow.next;
}
slow = reverse(slow);
//判断是否是回文
while(slow!=null){
if(head.val!=slow.val){
return false;
}
head = head.next;
slow = slow.next;
}
return true;
}
方法二:链表的后序遍历
//链表也可以有前序遍历和后序遍历,模版如下
public void travel(ListNode head){
//前序遍历位置
travel(head.next);
//后序遍历位置
}
//通过链表的后序遍历
//时间复杂度和空间复杂度都是O(n)
ListNode left = null;
public boolean isPalindrome(ListNode head) {
left = head;
return reverse(head);
}
private boolean reverse(ListNode right){
if(right==null){
return true;
}
boolean res = reverse(right.next);
//后序遍历
if(left.val==right.val && res==true){
left = left.next;
return true;
}
return false;
}