文章目录
前言
结构越复杂 - 使用越简单 , 结构越简单-使用越复杂; 我愿称之为思考守恒定律
这篇文章主要写 - 链表相关的一些题型以及解题思路 : 这里收录的题只是 , 单纯使用链表的题型不涉及其他的数据结构
一些感想 :
对于做题思路 : 最重要的是写出它的结构流程 ,结构流程图越完备, 写代码就越快 , 所以尽量将结构流程图写好
对于解题方法 : 解题方法不是唯一的,只不过是低效与高效的区分 , 这里尽可能的会多写解题思路与方法,重要的是其后面的思考思路, 以后未必不能优化, 好的算法 与数据结构 就是一步步不断优化过来的 .
对于数据结构 : 没有最好的数据结构 , 只有最适合当前的数据结构 ,要学会自我改动,经典之所以是经典 就是因为它的可扩展性是极大的 .
对于链表 : 链表常见的考察形式为 : 无头 - 单向 - 不循环 , 对于这种结构 : 最大的难点在于, 怎么去寻找它的头结点 , 而我们可以一开始就给这个链表加一个头结点 , 返回的时候再去返回新插入头结点的下一个 - 这样许多做题的思路会很简单
题型1 - 设计一个链表结构
import java.util.List;
import java.util.Stack;
/**
* @Author 12629
* @Description: shift+f6(fn+esc)
*/
public class MySingleList {
static class ListNode {
public int val;//存储的数据
public ListNode next;//存储下一个节点的地址
public ListNode(int val) {
this.val = val;
}
}
public ListNode head;// 代表当前链表的头节点的引用
public void createLink() {
ListNode listNode1 = new ListNode(12);
ListNode listNode2 = new ListNode(45);
ListNode listNode3 = new ListNode(23);
ListNode listNode4 = new ListNode(90);
listNode1.next = listNode2;
listNode2.next = listNode3;
listNode3.next = listNode4; /* */
head = listNode1;
}
//递归打印链表
public void display3(ListNode pHead) {
if(pHead == null) {
return;
}
if(pHead.next == null) {
System.out.print(pHead.val+" ");
return;
}
display3(pHead.next);
System.out.print(pHead.val+" ");
}
public void display4() {
Stack<ListNode> stack = new Stack<>();
ListNode cur = head;
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
//遍历栈
while (!stack.isEmpty()) {
ListNode top = stack.pop();
System.out.print(top.val+" ");
}
System.out.println();
}
/**
* 遍历链表
*/
public void display() {
//如果说 把整个链表 遍历完成 那么 就需要 head == null
// 如果说 你遍历到链表的尾巴 head.next == null
ListNode cur = head;
while (cur != null) {
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
/**
* 从指定位置开始打印链表
* @param newHead
*/
public void display(ListNode newHead) {
//如果说 把整个链表 遍历完成 那么 就需要 head == null
// 如果说 你遍历到链表的尾巴 head.next == null
ListNode cur = newHead;
while (cur != null) {
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key){
ListNode cur = head;
while (cur != null) {
if(cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
//得到单链表的长度 O(N)
public int size(){
int count = 0;
ListNode cur = head;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
//头插法 O(1)
public void addFirst(int data){
ListNode listNode = new ListNode(data);
listNode.next = head;
head = listNode;
}
//尾插法 O(N) 找尾巴的过程
public void addLast(int data){
ListNode listNode = new ListNode(data);
if(head == null) {
head = listNode;
return;
}
ListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
cur.next = listNode;
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data)
throws ListIndexOutOfException{
checkIndex(index);
if(index == 0) {
addFirst(data);
return;
}
if(index == size()) {
addLast(data);
return;
}
ListNode cur = findIndexSubOne(index);
ListNode listNode = new ListNode(data);
listNode.next = cur.next;
cur.next = listNode;
}
/**
* 找到 index-1位置的节点的地址
* @param index
* @return
*/
private ListNode findIndexSubOne(int index) {
ListNode cur = head;
int count = 0;
while (count != index-1) {
cur = cur.next;
count++;
}
return cur;
}
private void checkIndex(int index) throws ListIndexOutOfException{
if(index < 0 || index > size()) {
throw new ListIndexOutOfException("index位置不合法");
}
}
//删除第一次出现关键字为key的节点 O(N)
public void remove(int key){
if(head == null) {
return ;//一个节点都没有
}
if(head.val == key) {
head = head.next;
return;
}
ListNode cur = searchPrev(key);
if(cur == null) {
return;
}
ListNode del = cur.next;//要删除的节点
cur.next = del.next;
}
/**
* 找到关键字key的前一个节点
* @param key
* @return
*/
private ListNode searchPrev(int key) {
ListNode cur = head;
while (cur.next != null) {
if(cur.next.val == key) {
return cur;
}
cur = cur.next;
}
return null;//没有你要删除的节点
}
//删除所有值为key的节点
public void removeAllKey(int key){
if(head == null) {
return;
}
/*while(head.val == key) {
head = head.next;
}*/
ListNode prev = head;
ListNode cur = head.next;
while (cur != null) {
if(cur.val == key) {
prev.next = cur.next;
cur = cur.next;
}else {
prev = cur;
cur = cur.next;
}
}
if(head.val == key) {
head = head.next;
}
}
/**
* 保证链表当中 所有的节点 都可以被回收
*/
public void clear() {
head = null;
//StringBuilder sb = "fdfa";
}
public ListNode reverseList() {
if(head == null) {
return null;
}
//说明 只有一个节点
if(head.next == null) {
return head;
}
ListNode cur = head.next;
head.next = null;
while (cur != null) {
ListNode curNext = cur.next;
//头插法 插入cur
cur.next = head;
head = cur;
cur = curNext;
}
return head;
}
public ListNode middleNode() {
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
public ListNode findKthToTail(int k) {
if(k <= 0 || head == null) {
return null;
}
ListNode fast = head;
ListNode slow = head;
//1. fast走k-1步
while (k-1 != 0) {
fast = fast.next;
if(fast == null) {
return null;
}
k--;
}
//2、3、
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
public boolean chkPalindrome() {
if(head == null) {
return false;
}
if(head.next == null) {
return true;
}
ListNode fast = head;
ListNode slow = head;
//1、找中间节点
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
//2、 翻转
ListNode cur = slow.next;//代表当前需要翻转的节点
while (cur != null) {
ListNode curNext = cur.next;
cur.next = slow;
slow = cur;
cur = curNext;
}
//3、一个从头往后 一个从后往前
while (slow != head) {
if(head.val != slow.val) {
return false;
}
//偶数的情况
if(head.next == slow) {
return true;
}
slow = slow.next;
head = head.next;
}
return true;
}
public ListNode partition( int x) {
// write code here
ListNode bs = null;
ListNode be = null;
ListNode as = null;
ListNode ae = null;
ListNode cur = head;
while (cur != null) {
if(cur.val < x) {
if(bs == null) {
bs = cur;
be = cur;
}else {
be.next = cur;
be = be.next;
}
}else {
if(as == null) {
as = cur;
ae = cur;
}else {
ae.next = cur;
ae = ae.next;
}
}
cur = cur.next;
}
// 有可能不会同时存在小于x 和 大于等于x 的数据
if(bs == null) {
return as;
}
//第一段不为空
be.next = as;
//第2个段为空不为空的问题
if(as != null) {
ae.next = null;
}
return bs;
}
public boolean hasCycle() {
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if(fast == slow) {
return true;
}
}
return false;
}
public boolean hasCycle1() {
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if(fast == slow) {
break;
}
}
if(fast == null || fast.next == null) {
return false;
}
return true;
}
public void createLoop() {
ListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
cur.next = head.next.next;
}
public ListNode detectCycle() {
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if(fast == slow) {
break;
}
}
if(fast == null || fast.next == null) {
return null;
}
//
slow = head;
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
}
}
题型2 - 反转链表
描述 :给定一个单链表的头结点pHead , 长度为n,反转该链表后,返回新链表的表头。
要求 : 时间复杂度 O(N ) ,空间复杂度 O(1)
public ListNode ReverseList(ListNode head) {
// 不加头结点的方式
if (head == null || head.next == null ){
return null;
}
ListNode cur=head.next;
head.next=null;//假如头结点不置于空,那么遍历的时候会出错
while (cur != null){
ListNode curNext=cur.next;
cur.next=head;
head=cur;
cur=curNext;
}
return head;
}
public ListNode ReverseList2(ListNode head) {
// 加头结点的方式
if (head == null || head.next == null ){
return null;
}
ListNode pre=new ListNode(-1);
pre.next = head;
ListNode cur=head.next;
head.next =null;
while (cur != null){
head = cur.next;
cur.next = pre.next;
pre.next = cur;
cur =head;
}
head = pre.next;
return head;
}
链表内指定区间反转
描述 :将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n),空间复杂度 O(1)。
解题思路2
public ListNode reverseBetween (ListNode head, int m, int n) {
//
//加个表头
ListNode res = new ListNode(-1);
res.next = head;
//前序节点
ListNode pre = res;
//当前节点
ListNode cur = head;
//找到m
for(int i = 1; i < m; i++){
pre = cur;
cur = cur.next;
}
//从m反转到n
for(int i = m; i < n; i++){
ListNode temp = cur.next;
cur.next = temp.next;
temp.next = pre.next;
pre.next = temp;
}
//返回去掉表头
return res.next;
}
链表中的节点每k个一组翻转
描述 : 将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表 , 如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样 , 你不能更改节点中的值,只能更改节点本身。
public ListNode reverseKGroup(ListNode head, int k) {
//先创建一个哑节点
ListNode dummy = new ListNode(0);
//让哑节点的指针指向链表的头
dummy.next = head;
//开始反转的前一个节点,比如反转的节点范围是[link1,link2],
//那么pre就是link1的前一个节点
ListNode pre = dummy;
ListNode end = dummy;
while (end.next != null) {
//每k个反转,end是每k个链表的最后一个
for (int i = 0; i < k && end != null; i++)
end = end.next;
//如果end是空,说明不够k个,就不需要反转了,直接退出循环。
if (end == null)
break;
//反转开始的节点
ListNode start = pre.next;
//next是下一次反转的头结点,先把他记录下来
ListNode next = end.next;
//因为end是这k个链表的最后一个结点,把它和原来链表断开,
//这k个节点我们可以把他们看做一个小的链表,然后反转这个
//小链表
end.next = null;
//因为pre是反转链表的前一个节点,我们把小链表[start,end]
//反转之后,让pre的指针指向这个反转的小链表
pre.next = reverse(start);
//注意经过上一步反转之后,start反转到链表的尾部了,就是已经
//反转之后的尾结点了,让他之前下一次反转的头结点即可(上面分析
//过,next就是下一次反转的头结点)
start.next = next;
//前面反转完了,要进入下一波了,pre和end都有重新赋值
pre = start;
end = start;
}
return dummy.next;
}
//链表的反转
private ListNode reverse(ListNode head) {
ListNode pre = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = pre;
pre = curr;
curr = next;
}
return pre;
}
题型3 - 合并两个排序的链表
描述 :输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode newHead=new ListNode(-1);
ListNode tmp;
//判断是否为空
if (list1 ==null){
return list2;
}
if (list2 == null){
return list1;
}
//将头结点给tmp
if (list1.val <= list2.val) {
tmp = list1;
newHead.next = list1;
list1 = list1.next;
} else {
tmp = list2;
newHead.next = list2;
list2 = list2.next;
}
//依次插入
while (list1 !=null && list2 !=null ) {
if (list1.val <= list2.val){
tmp.next=list1;
list1=list1.next;
tmp=tmp.next;
}else{
tmp.next=list2;
list2=list2.next;
tmp=tmp.next;
}
}
//判断是否有剩余
if (list1 !=null){
tmp.next=list1;
}
if (list2 !=null){
tmp.next=list2;
}
//删除第一个头结点
return newHead.next;
}
合并k个升序的链表
合并 k 个升序的链表并将结果作为一个升序的链表返回其头节点。
解题思路 : 在合并俩个链表的基础上 , 依次调用需要合并的链表 ,
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode newHead=new ListNode(-1);
ListNode tmp;
//判断是否为空
if (list1 ==null){
return list2;
}
if (list2 == null){
return list1;
}
//将头结点给tmp
if (list1.val <= list2.val) {
tmp = list1;
newHead.next = list1;
list1 = list1.next;
} else {
tmp = list2;
newHead.next = list2;
list2 = list2.next;
}
//依次插入
while (list1 !=null && list2 !=null ) {
if (list1.val <= list2.val){
tmp.next=list1;
list1=list1.next;
tmp=tmp.next;
}else{
tmp.next=list2;
list2=list2.next;
tmp=tmp.next;
}
}
//判断是否有剩余
if (list1 !=null){
tmp.next=list1;
}
if (list2 !=null){
tmp.next=list2;
}
//删除第一个头结点
return newHead.next;
}
/**
* 合并 k 个升序的链表并将结果作为一个升序的链表返回其头节点。
*/
public ListNode mergeKLists(ArrayList<ListNode> lists) {
ListNode list=null;
for (int i = 0; i < lists.size(); i++) {
ListNode getlist=lists.get(i);
list=Merge(list,getlist);
}
return list;
}
题型4 - 判断链表中是否有环
描述 : 判断给定的链表中是否有环。如果有环则返回true,否则返回false。
public boolean hasCycle(ListNode head) {
if (head == null ){
return false;
}
if( head.next == null){
return false;
}
ListNode fast=head.next.next;
ListNode slow=head.next;
while( fast !=null && fast.next!=null){
if (fast == slow || fast.next==slow){
return true;
}
fast=fast.next.next;
slow=slow.next;
}
return false;
}
判断一个链表是否为回文结构
描述 : 给定一个链表,请判断该链表是否为回文结构。回文是指该字符串正序逆序完全一致。
import java.util.*;
public class Solution {
//反转链表指针
ListNode reverse(ListNode head) {
//前序节点
ListNode prev = null;
while(head != null){
//断开后序
ListNode next = head.next;
//指向前序
head.next = prev;
prev = head;
head = next;
}
return prev;
}
public boolean isPail (ListNode head) {
ListNode p = head;
int n = 0;
//找到链表长度
while(p != null){
n++;
p = p.next;
}
//中点
n = n / 2;
p = head;
//遍历到中点位置
while(n > 0){
p = p.next;
n--;
}
//中点处反转
p = reverse(p);
ListNode q = head;
while(p != null){
//比较判断节点值是否相等
if(p.val != q.val)
return false;
p = p.next;
q = q.next;
}
return true;
}
}
判断链表中环的入口节点
给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public static ListNode hasCycle1(ListNode head) {
//先判断链表为空的情况
if (head == null)
return null;
//快慢双指针
ListNode fast = head;
ListNode slow = head;
//如果没环快指针会先到链表尾
while (fast != null && fast.next != null) {
//快指针移动两步
fast = fast.next.next;
//慢指针移动一步
slow = slow.next;
//相遇则有环,返回相遇的位置
if (fast == slow)
return slow;
}
//到末尾说明没有环,返回null
return null;
}
public ListNode EntryNodeOfLoop(ListNode pHead) {
ListNode slow = hasCycle1(pHead);
//没有环
if(slow == null)
return null;
//快指针回到表头
ListNode fast = pHead;
//再次相遇即是环入口
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
链表中倒数最后k个结点
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* public ListNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pHead ListNode类
* @param k int整型
* @return ListNode类
*/
public ListNode FindKthToTail (ListNode pHead, int k) {
if (pHead == null || k<=0){
return null;
}
if (pHead.next == null){
return pHead;
}
ListNode fast=pHead;
ListNode solw=pHead;
while(k-1>0){
if (fast.next == null){
return null;
}
fast=fast.next;
k--;
}
while (fast.next != null){
fast= fast.next;
solw=solw.next;
}
return solw;
}
}
删除链表的倒数第n个节点
描述 : 给定一个链表,删除链表的倒数第 n 个节点并返回链表的头指针
import java.util.*;
public class Solution {
public ListNode removeNthFromEnd (ListNode head, int n) {
//添加表头
ListNode res = new ListNode(-1);
res.next = head;
//当前节点
ListNode cur = head;
//前序节点
ListNode pre = res;
ListNode fast = head;
//快指针先行n步
while(n != 0){
fast = fast.next;
n--;
}
//快慢指针同步,快指针到达末尾,慢指针就到了倒数第n个位置
while(fast != null){
fast = fast.next;
pre = cur;
cur = cur.next;
}
//删除该位置的节点
pre.next = cur.next;
//返回去掉头
return res.next;
}
}
两个链表的第一个公共结点
描述 : 输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode l1=pHead1;
ListNode l2=pHead2;
while (l1 != l2){
if (l1 == null ){
l1=pHead2;
}else {
l1= l1.next;
}
if (l2 == null){
l2=pHead1;
}else {
l2= l2.next;
}
}
return l1;
}
}
题型5 - 单链表排序
描述: 给定一个节点数为n的无序单链表,对其按升序排序。
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类 the head node
* @return ListNode类
*/
public ListNode sortInList (ListNode head) {
//没写完
if (head == null){
return null;
}
if (head.next == null){
return head;
}
ListNode tmp=head;
int n=0;
while (tmp != null){
n++;
tmp=tmp.next;
}
int [] array=new int[n];
tmp=head;
for (int i = 0; i < n; i++) {
array[i]=tmp.val;
tmp= tmp.next;
}
Arrays.sort(array);
tmp=head;
for (int i = 0; i < n; i++) {
tmp.val=array[i];
tmp=tmp.next;
}
return head;
}
}
链表的奇偶重排
描述 : 给定一个单链表,请设定一个函数,将链表的奇数位节点和偶数位节点分别放在一起,重排后输出。注意是节点的编号而非节点的数值。
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* public ListNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* @param head ListNode类
* @return ListNode类
*/
public ListNode oddEvenList (ListNode head) {
if (head == null){
return null;
}
if (head.next == null){
return head;
}
ListNode p=head;
int n=1;
ListNode Js=new ListNode(-1);
ListNode Os=new ListNode(-1);
ListNode cur = Js;
ListNode cur1 = Os;
while (p!=null){
if (n%2!=0){
ListNode listNode = new ListNode(p.val);
cur.next = listNode;
cur=listNode;
} else{
ListNode listNode = new ListNode(p.val);
cur1.next = listNode;
cur1=listNode;
}
p=p.next;
n++;
}
p=Js;
while (p.next!=null){
p=p.next;
}
p.next=Os.next;
return Js.next;
}
}
删除有序链表中重复的元素-I
删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类
* @return ListNode类
*/
public ListNode deleteDuplicates (ListNode head) {
// write code here
if (head == null){
return null;
}
if (head.next == null){
return head;
}
ListNode pre=new ListNode(-1);
pre.next=head;
ListNode p=head;
while (p != null){
if (pre.val == p.val){
pre.next=p.next;
p=pre.next;
}else{
pre=pre.next;
p=p.next;
}
}
return head;
}
}
给出一个升序排序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。
import java.util.*;
public class Solution {
public ListNode deleteDuplicates (ListNode head) {
//空链表
if(head == null)
return null;
ListNode res = new ListNode(0);
//在链表前加一个表头
res.next = head;
ListNode cur = res;
while(cur.next != null && cur.next.next != null){
//遇到相邻两个节点值相同
if(cur.next.val == cur.next.next.val){
int temp = cur.next.val;
//将所有相同的都跳过
while (cur.next != null && cur.next.val == temp)
cur.next = cur.next.next;
}
else
cur = cur.next;
}
//返回时去掉表头
return res.next;
}
}