链表经典算法题

单链表经典算法题

文章目录

  • 单链表经典算法题
    • 1.删除链表指定节点
      • 方法一:移动指针法删除
      • 方法二:递归删除指定节点
    • 2.找中间节点---------快慢指针法
    • 3.实现反转反转链表
      • 方法一:开辟新的链表用头插法将原链表反转复制过来
      • 方法二: 用三指针法
    • 4.判断回文链表
    • 5.用递归求出数组的长度
    • 6.合并有序链表
      • 方法一:下面这种方法是我自己写的,看着稍微有点繁杂,但思路是比较简单的,第二种方法思路方法一样,只不过更加简洁
      • 方法二:
    • 7.寻找链表的倒数n节点
    • 8.删除链表的倒数n节点
    • 9.实现k个一组翻转链表
    • 10.两两交换链表中的节点
    • 11.旋转链表(将链表右移k个单位)

1.删除链表指定节点

方法一:移动指针法删除

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
     
    public ListNode removeElements(ListNode head, int val) {
     
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;
        ListNode pre = dummyHead;
        while (pre.next != null) {
     
            if(pre.next.val == val) {
     
                pre.next = pre.next.next;
            }
            else {
     
                pre = pre.next;
            }
        }
        return dummyHead.next;
    }
}

方法二:递归删除指定节点

思路图解:
1551873653777

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
     
    public ListNode removeElements(ListNode head, int val) {
     
       if(head == null) {
     
            return head;
        }
        ListNode res = removeElements(head.next, val);
        if(head.val == val) {
     
            return res;
        }else {
     
            head.next = res;
            return head;
        }
    }
}

2.找中间节点---------快慢指针法

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
/**
 *   给定一个带有头结点 head 的非空单链表,返回链表的中间结点。
 *   如果有两个中间结点,则返回第二个中间结点。
 */
class Solution {
     
    public ListNode middleNode(ListNode head) {
     
        ListNode fast = head;
        ListNode low = head;
        while(fast.next!=null) {
     
            if(fast.next.next != null) {
                  //不这样写会产生nullpointer错误
                fast = fast.next.next;
                low = low.next;
            }else {
     
                fast = fast.next;
                low = low.next;
            }
        }
        return low;
    }
}

3.实现反转反转链表

方法一:开辟新的链表用头插法将原链表反转复制过来

思路:创建一个新的链表,用头插法来放入数据达到反转目的;

  • 此算法空间复杂度为O(1),比较浪费空间(相对于三指针法);
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
     
    public ListNode reverseList(ListNode head) {
     
                //创建新链表头节点,该新链表是由原链表头插发新创建的
        ListNode dummyHead = new ListNode(-1);
        ListNode temp = new ListNode(-1);
        for (temp.next = head;temp.next != null; temp = temp.next) {
     
            ListNode newNode = new ListNode(temp.next.val);
            //头插法
            newNode.next = dummyHead.next;
            dummyHead.next = newNode;
            
        }
        return dummyHead.next;
            
    }
}

方法二: 用三指针法

思路:

  • 举例子:要反转 1–>2–>3–>4,先反转一二两个,得到 2–>1–>3–>4,
    再将2–>1看成一个整体,再反转一二部分,得到 3–>2–>1–>4,
    以此类推,最终将得到 4–>3–>2–>1,
  • 此算法的时间复杂度为O(1),空间复杂度为O(0);
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
     
    public ListNode reverseList(ListNode head) {
     
       ListNode pre = new ListNode(-1);
        pre.next = head;
        if (head == null || head.next ==null) {
        //判断头节点是否为空或只有一个节点
            return head;
        }else {
     
            ListNode q = head;
            ListNode h = q.next;
            while(h != null) {
     
                q.next = h.next;
                h.next = pre.next;    //这几条语句顺序不能乱
                pre.next = h;
                h = q.next;
            }
            return pre.next;
        }
            
    }
}

4.判断回文链表

思路:要判断一个单链表是否为回文链表:

  • 第一步:先找到它的中间节点;
  • 第二步:将中间节点以后链表反转;
  • 第三步:将反转后的链表与前半部分链表比较是否全等,若是则返回true,否则false;
/**
 * @Author : YangY
 * @Description :   对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。
 *                  给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
 *                  测试样例:
 *                  1->2->2->1
 *                  返回:true
 * @Time : Created in 15:27 2019/3/3
 */

class ListNode {
     
    int val;
    ListNode next;
    ListNode(int x) {
      val = x; }
}

public class Solution {
     
    public boolean chkPalindrome(ListNode A) {
     
        // write code here
        if (A == null || A.next == null) {
     
            return true;
        }
        //寻找中间节点,若为偶数则返回两个中间节点后面那一个;
        ListNode fast = A;
        ListNode slow = A;
        while (fast.next != null) {
     
            if(fast.next.next != null) {
     
                fast = fast.next.next;
                slow = slow.next;
            }else {
     
                fast = fast.next;
                slow = slow.next;
            }
        }
        ListNode mid = slow;

        //反转后半部分
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = mid;
        ListNode q = mid;
        ListNode h = mid.next;
        while (h != null) {
     
            q.next = h.next;
            h.next = dummyHead.next;
            dummyHead.next = h;
            h = q.next;
        }
        //比较翻转后的链表与前半部分链表
        while (A.next != null && dummyHead.next != null) {
     
            if (A.val != dummyHead.next.val) {
     
                return false;
            } else {
     
                A = A.next;
                dummyHead.next = dummyHead.next.next;
            }
        }
        return true;
    }
}

5.用递归求出数组的长度

/**
 * @Author : YangY
 * @Description :  递归计算数组内所有元素的和
 * @Time : Created in 19:22 2019/3/5
 */
public class Test {
     
    public static void main(String[] args) {
     
        int[] arr = new int[]{
     1,3,4,5};
        System.out.println(sum(0,arr));
    }
    public static int sum(int l, int[] array) {
     
        if (l == array.length) {
     
            return 0;
        }else {
     
            return array[l] + sum(l+1, array);
        }

    }
}

6.合并有序链表

方法一:下面这种方法是我自己写的,看着稍微有点繁杂,但思路是比较简单的,第二种方法思路方法一样,只不过更加简洁

/**
 * 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) {
     
        int count = 0;
        ListNode dummyHeadL1 = new ListNode(-1);
        ListNode dummyHeadL2 = new ListNode(-1);
        dummyHeadL1.next = l1;
        dummyHeadL2.next = l2;
        ListNode dummyHeadNew = new ListNode(-1);
        ListNode dummyHeadCopy = dummyHeadNew;
        //若两个都为空
        if (l1==null && l2== null) {
     
            return l1;
        }
        //比较到最后一定会只剩下一个不为空
        while(dummyHeadL1.next != null || dummyHeadL2.next != null) {
     
            if (dummyHeadL1.next == null || dummyHeadL2.next == null) {
     
                if (dummyHeadL1.next == null) {
     
                    dummyHeadNew.next = dummyHeadL2.next; 
                    dummyHeadL2 = dummyHeadL2.next;
                }else {
     
                    dummyHeadNew.next = dummyHeadL1.next; 
                    dummyHeadL1 = dummyHeadL1.next;
                }
            }else {
     
                if (dummyHeadL1.next.val <= dummyHeadL2.next.val) {
     
                    dummyHeadNew.next = dummyHeadL1.next; 
                    dummyHeadL1.next = dummyHeadL1.next.next;
                }else {
     
                    dummyHeadNew.next = dummyHeadL2.next;
                    dummyHeadL2.next = dummyHeadL2.next.next;
                }
            }         
            count++;
            if(count == 1) {
     
                dummyHeadCopy = dummyHeadNew.next;
            }
            dummyHeadNew = dummyHeadNew.next;
        }
        return dummyHeadCopy;
    }
}

方法二:

/**
 * 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(0);
        ListNode cur = dummyHead;
        while (l1 != null && l2 != null) {
     
            if (l1.val < l2.val) {
     
                cur.next = l1;
                cur = cur.next;
                l1 = l1.next;
            } else {
     
                cur.next = l2;
                cur = cur.next;
                l2 = l2.next;
            }
        }
        // 任一为空,直接连接另一条链表
        if (l1 == null) {
     
            cur.next = l2;
        } else {
     
            cur.next = l1;
        }
        return dummyHead.next;
    }
}

开始我很疑惑,dummyHead始终都没有初始化,怎么会得出结果,其实ListNode cur = dummyHead这条语句为引用传递,此处并没有new关键字,所以它两指向同一块空间。

7.寻找链表的倒数n节点

这种问题用快慢指针法,十分简单
规律:快指针先走n-1步,然后快慢指针一起走,当快指针走到最后时,慢指针则为倒数n节点;

代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */

class Solution {
    public ListNode FoundNthFromEnd(ListNode head, int n) {
        ListNode fast = head;
        while(n-1 > 0) {
            fast = fast.next;
            n--;
        }
        ListNode slow = head;
        while(fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

8.删除链表的倒数n节点

这个问题是第7个问题的升级版
思路:可容易发现,我们可以用快慢指针找到被删节点的前一节点,由于是删除节点,所以要找的是前一节点,与上面的寻找倒数n节点只有一点差异;

规律:先让快指针走n步,在让快慢指针一起走,当快指针为空时,慢指针则是倒数n节点的上一节点
坑点:要删除的节点为首节点时,我们要按另一种方法来算;(只有当删除头节点时,first节点移动n步后一定为空,其他情况绝对不会为空,凭这一点可找出删除头节点的情况)

代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        //快指针提前先走n步
        ListNode fast = head;   
        while(n > 0){
            fast = fast.next;
            n--;
        }
        //如果此时快指针已经为空了,说明待删除的是头节点,直接返回结果
        if(fast == null) return head.next;
        ListNode slow = head;
        while(fast.next != null){
            fast = fast.next;
            slow = slow.next;
        }
        //此时slow指向的是待删除节点的前一个前点
        slow.next = slow.next.next;
        return head;
    }
}

9.实现k个一组翻转链表

题目描述:
给出一个链表,每 k 个节点一组进行翻转,并返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么将最后剩余节点保持原有顺序。

示例 :

给定这个链表:1->2->3->4->5

k = 2 时,应当返回: 2->1->4->3->5

k = 3 时,应当返回: 3->2->1->4->5

说明 :

  • 你的算法只能使用常数的额外空间。
  • 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

解题思路:
该问题适合用递归来解决,我们将第k个节点作为参数进行递归,每个函数返回翻转后的头节点,这个头节点作为上一个递归的尾节点,有了尾节点和头节点,在函数体内就可以执行翻转;
代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
     
    public ListNode reverseKGroup(ListNode head, int k) {
     
        ListNode current_node = head;
        int count = 0;
        while(count < k && current_node != null) {
     
            current_node = current_node.next;
            count++;
        }
        //判断是否够分k个
        if(count == k) {
     
            current_node = reverseKGroup(current_node,k);
            while(count > 0) {
     
                ListNode h = head.next;
                head.next = current_node;
                current_node = head;
                head = h;
                count--;
            }
            //走完这个循环,current_node代表当前链表的头;
            head = current_node;
        }
        return head;
        
    }
}

10.两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例:

给定 1->2->3->4, 你应该返回 2->1->4->3.

解答:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
     
    public ListNode swapPairs(ListNode head) {
     
        if(head == null || head.next == null) {
     
            return head;
        }else {
     
            ListNode res = swapPairs(head.next.next);
            ListNode temp = head.next;
            temp.next = head;
            head.next = res;
            return temp;
        }

    } 
}

11.旋转链表(将链表右移k个单位)

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

示例 1:

输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL

示例 2:

输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释:
向右旋转 1 步: 2->0->1->NULL
向右旋转 2 步: 1->2->0->NULL
向右旋转 3 步: 0->1->2->NULL
向右旋转 4 步: 2->0->1->NULL

发现规律:若右移k个,则只需将倒数k个节点搬移到head的前面即可;

解题:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
     
    public ListNode rotateRight(ListNode head, int k) {
     
        if(head == null) {
     
            return head;
        }
        ListNode temp = head;
        int count = 0;
        while(temp.next!=null) {
     
            temp = temp.next;
            count++;    //这里的count是总节点数减一哟
        }
        int i = 0;
        ListNode newNode = null;
        if (k%(count+1)==0) {
        //如果为链表总长度的整数倍,则不需要移动
            return head;
        }else {
     
            ListNode last = head;
            int num =count - k%(count+1);
            for(;i < num;i++) {
     
                last = last.next;
            }
            temp.next = head;
            newNode = last.next;
            last.next = null;
        }
        return newNode;
    }
}

你可能感兴趣的