文章目录
1. 单向链表
1.1 节点定义
package linkedlist;
public class ListNode {
private int data;
private ListNode next;
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
public ListNode(int data) {
this.data = data;
}
public ListNode() {
}
}
1.2 单向链表操作
package linkedlist;
public class MyLinkedList {
private ListNode head=null;
private int sz=0;//节点数量
public MyLinkedList(){
head=new ListNode();//添加的头节点(哑节点) 不包含数据 方便操作
}
//在位置pos上插入节点
public void insertInLinkedList(ListNode newNode,int pos){
if(pos>sz+1||pos<1)//合法的插入位置[1,sz+1]
{
System.out.println("插入位置错误");
return;
}
ListNode pre=head;
int cnt=1;
while(cnt<pos){
pre=pre.getNext();//找到待插入位置前一个位置
cnt++;
}
//比如pos=1 相当于在头节点位置插入
ListNode next=pre.getNext();
pre.setNext(newNode);
newNode.setNext(next);
sz++;
}
//删除pos位置上的节点
public void deletetInLinkedList(int pos){
if(pos>sz||pos<1)//合法的删除位置[1,sz]
{
System.out.println("删除位置错误");
return;
}
int cnt=1;
ListNode pre=head;
while(cnt<pos){
pre=pre.getNext();//找到待删除位置上一个位置
cnt++;
}
ListNode del=pre.getNext();
pre.setNext(del.getNext());
del.setNext(null);
sz--;
}
public void print(){
if(sz==0){
System.out.println("链表为空");
return;
}
ListNode cur=head.getNext();
while(cur!=null){
System.out.print(cur.getData()+"->");
cur=cur.getNext();
}
System.out.println();
}
public int getSize(){
return sz;
}
//清空链表
public void clear(){
ListNode cur=head.getNext(),tmp;
while(cur!=null){
tmp=cur.getNext();
cur.setNext(null);
cur=tmp;
}
head.setNext(null);
sz=0;
}
public static void main(String[] args) {
MyLinkedList list=new MyLinkedList();
ListNode node1=new ListNode(1);
ListNode node2=new ListNode(2);
ListNode node3=new ListNode(3);
ListNode node4=new ListNode(4);
list.insertInLinkedList(node1,1);
list.insertInLinkedList(node2,2);
list.insertInLinkedList(node3,3);
list.insertInLinkedList(node4,4);
list.print();
list.deletetInLinkedList(2);
list.print();
list.deletetInLinkedList(1);
list.print();
list.clear();
list.print();
}
}
2. 双向链表
2.1 节点定义
package linkedlist;
public class DLLNode{
private int data;
private DLLNode pre;
private DLLNode next;
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public DLLNode getPre() {
return pre;
}
public void setPre(DLLNode pre) {
this.pre = pre;
}
public DLLNode getNext() {
return next;
}
public void setNext(DLLNode next) {
this.next = next;
}
public DLLNode(int data) {
this.data = data;
}
public DLLNode() {
}
}
2.2 双向链表操作
package linkedlist;
public class MyDDLLinkedList {
private DLLNode head;
int sz;
public MyDDLLinkedList(){
head=new DLLNode();//创建dummy节点
sz=0;
}
//在pos位置上插入一个新节点
public void insert(DLLNode newNode,int pos){
if(pos<1||pos>sz+1){
System.out.println("插入位置不合法");
return;
}
int cnt=1;
DLLNode pre=head;
while(cnt<pos){
cnt++;
pre=pre.getNext();//找到待插入位置的上一个位置
}
DLLNode next=pre.getNext();
pre.setNext(newNode);
newNode.setNext(next);
newNode.setPre(pre);
if(next!=null)//如果插入的是最后一个位置 pre刚开始就是指向最后一个节点
next.setPre(newNode);//加入判断 防止next为null出现空指针异常
sz++;
}
//删除pos位置上的节点
public void delete(int pos){
if(pos<1||pos>sz){
System.out.println("删除位置不合法");
return;
}
int cnt=1;
DLLNode pre=head;
while(cnt<pos){
cnt++;
pre=pre.getNext();//找到待删除位置的上一个位置
}
DLLNode cur=pre.getNext();//cur指向待删除节点
DLLNode next=cur.getNext();//next指向待删除节点的下一个节点
pre.setNext(next);
if(next!=null)
next.setPre(pre);
cur.setPre(null);
cur.setNext(null);
cur=null;
sz--;
}
//顺序打印
public void print(){
if(sz==0){
System.out.println("链表为空");
return;
}
DLLNode cur=head.getNext();
while(cur!=null){
System.out.print(cur.getData()+"->");
cur=cur.getNext();
}
System.out.println();
}
//逆序打印
public void printReverse(){
if(sz==0){
System.out.println("链表为空");
return;
}
int cnt=1;
DLLNode cur=head.getNext();
while(cnt<sz){
cur=cur.getNext();
cnt++;
}
cnt=1;
while(cur!=head){
System.out.print(cur.getData()+"->");
cur=cur.getPre();
}
System.out.println();
}
public static void main(String[] args) {
MyDDLLinkedList list=new MyDDLLinkedList();
DLLNode node1=new DLLNode(1);
DLLNode node2=new DLLNode(2);
DLLNode node3=new DLLNode(3);
DLLNode node4=new DLLNode(4);
list.insert(node1,1);
list.insert(node2,2);
list.insert(node3,3);
list.insert(node4,4);
list.print();
list.printReverse();
list.delete(2);
list.print();
list.printReverse();
list.delete(1);
list.print();
list.printReverse();
}
}
3. 循环链表
在单链表的基础上使得最后一个节点的指向不是null, 而是指向第一个节点
package linkedlist;
public class MyCircualList {
private ListNode head=null;
private ListNode tail;//指向尾结点
private int sz=0;//节点数量
public MyCircualList(){
head=new ListNode();//添加的头节点(哑节点) 不包含数据 方便操作
}
//在位置pos上插入节点
public void insertInLinkedList(ListNode newNode,int pos){
if(pos>sz+1||pos<1)//合法的插入位置[1,sz+1]
{
System.out.println("插入位置错误");
return;
}
ListNode pre=head;
int cnt=1;
while(cnt<pos){
pre=pre.getNext();//找到待插入位置前一个位置
cnt++;
}
//比如pos=1 相当于在头节点位置插入
ListNode next=pre.getNext();
pre.setNext(newNode);
tail=newNode;
tail.setNext(head.getNext());//最后一个节点指向头节点
sz++;
}
//删除第pos个节点
public void deletetInLinkedList(int pos){
if(pos>sz||pos<1)//合法的删除位置[1,sz]
{
System.out.println("删除位置错误");
return;
}
int cnt=1;
ListNode pre=head;
while(cnt<pos){
pre=pre.getNext();//找到待删除位置上一个位置
cnt++;
}
ListNode del=pre.getNext();
pre.setNext(del.getNext());
if(pos==1&&tail!=del){//删除的是第一个节点 并且链表中不止有1个节点
tail.setNext(del.getNext());//修改尾结点指向
}
if(pos==sz)//删除的是表中的最后一个节点
tail=null;
del.setNext(null);
sz--;
}
//从链表的第pos个节点开始遍历
public void print(int pos){
if(sz==0){
System.out.println("链表为空");
return;
}
if(pos<1||pos>sz){
System.out.println("遍历开始位置错误");
return;
}
ListNode cur=head.getNext();
int cnt=1;
while(cnt<pos){
cur=cur.getNext();//找到第pos个节点
cnt++;
}
cnt=1;
while(cnt<=sz){
System.out.print(cur.getData()+"->");
cur=cur.getNext();
cnt++;
}
System.out.println();
}
public int getSize(){
return sz;
}
public void clear(){
ListNode cur=head.getNext(),tmp;
while(cur!=null){
tmp=cur.getNext();
cur.setNext(null);
cur=tmp;
}
head.setNext(null);
sz=0;
}
public static void main(String[] args) {
MyCircualList list=new MyCircualList();
ListNode node1=new ListNode(1);
ListNode node2=new ListNode(2);
ListNode node3=new ListNode(3);
ListNode node4=new ListNode(4);
list.insertInLinkedList(node1,1);
list.insertInLinkedList(node2,2);
list.insertInLinkedList(node3,3);
list.insertInLinkedList(node4,4);
list.print(1);
list.print(2);
list.deletetInLinkedList(2);
list.print(1);
list.print(2);
list.deletetInLinkedList(1);
list.print(1);
list.print(2);
list.clear();
list.print(2);
}
}
4. 链表常见问题
4.1 找到链表的倒数第n个节点
快慢指针 快指针先走n步 然后快慢指针一起走 这样当快指针变成null时 慢指针就指向倒数第n个节点
public ListNode findRevNthNode(int n){
ListNode slow=head.getNext(),fast=head.getNext();//slow和fast指向头节点
for(int i=1;i<=n;i++)
fast=fast.getNext();
while(fast!=null){
fast=fast.getNext();
slow=slow.getNext();
}
return slow;
}
4.2 判断链表是否有环
快慢指针 快指针每次走两步 慢指针每次走一步 如果二者最终相遇说明存在环 否则不存在
public static boolean hasCircle(ListNode head){
ListNode slow=head,fast=head;//slow和fast指向头节点
while(slow!=null&&fast.getNext()!=null){
slow=slow.getNext();
fast=fast.getNext().getNext();
if(slow==null||fast==null)
return false;
if(slow==fast)
return true;
}
return false;
}
4.3 如果链表有环,求出环的入环点
当链表有环时,根据4.2的算法,快慢指针会相遇,相遇之后,使其中一个指针从head头节点重新开始走,此时快慢指针速度一样,二者再次相遇的地方就是入环点
证明:快指针的速度是慢指针的2倍,当相遇时,假设慢指针走了k步,则快指针走了2k步,由于二者最后位置相同,所以快指针多走的k步实际上就环的长度的整数倍(类似于操场跑步,可能多跑了1圈,也可能多跑了2圈);假设入环点到相遇点的距离是m,则起点到入环点的距离是k-m, 而从相遇点再走到入环点的距离也是k-m, 因此相遇后,让一个节点从起点开始走,一个节点从相遇点开始走,此时速度一样,则再次相遇时,相遇点就是入环点
public ListNode detectCycle(ListNode head) {
if(head==null)
return null;
ListNode fast=head,slow=head;
boolean hasCircle=false;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==null||slow==null){//等于null说明没有环
return null;
}
if(fast==slow){//存在环 退出循环开始后面找环的入口
hasCircle=true;
break;
}
}
if(!hasCircle)
return null;
fast=head;//一个指向头节点
while(fast!=slow){//再次相遇的地方就是入环口
fast=fast.next;
slow=slow.next;
}
return fast;
4.4 如果有环,求出环的长度
根据4.3中的图,当快慢指针相遇时,慢指针不动(我在原位置等你),然后快指针没次一个步长移动,当快指针再次走到相遇的位置时,运动的步数即是环的长度
public int getCycleLength(ListNode head) {
if(head==null)
return 0;
ListNode fast=head,slow=head;
boolean hasCircle=false;
while(fast!=null&&fast.getNext()!=null){
fast=fast.getNext().getNext();
slow=slow.getNext();
if(fast==null||slow==null){//等于null说明没有环
break;
}
if(fast==slow){//存在环 退出循环开始后面找环的入口
hasCircle=true;
break;
}
}
if(!hasCircle)
return 0;
int cnt=1;
fast=fast.getNext();
while(fast!=slow){
fast=fast.getNext();
cnt++;
}
return cnt;
}
4.5 在有序列表中插入一个节点,使得链表依然有序
//升序为例
public static ListNode insertInSortedList(ListNode head,ListNode newNode){
if(head==null)
return newNode;
ListNode cur=head,pre=null;
while(cur!=null&&cur.getData()<newNode.getData()){
pre=cur;
cur=cur.getNext();
}
if(pre==null){//新节点值最小 那么直接在作为新的头节点
newNode.setNext(head);
return newNode;
}
ListNode next=pre.getNext();
pre.setNext(newNode);
newNode.setNext(next);
return head;
}
4.6 逆置单链表
public static ListNode reverse(ListNode head){
ListNode pre=null,next=null;
ListNode cur=head;
while(cur!=null){
next=cur.getNext();
cur.setNext(pre);
pre=cur;
cur=next;
}
return pre;
}
4.7 求出两条链表的相交节点
先使用两个指针pA pB分别指向headA headB 当pA!=pB时 如果A链表没有遍历完,pA往后走一步,如果A遍历完了,就让pA指向headB, 从B链表的头部开始移动; 对于headB同理
证明:
pA走到公共节点时走过的路程: a+(b-c)
pB走到公共节点时走过的路程: b+(a-c)
a+(b-c)= b+(a-c) 因此该距离内就能相遇 当两条链表没有公共部分时,c=0, 即走过的路程为a+b, 也就是pA pB都遍历了两条链表,最后都指向null, 此时pA也等于pB , 没有交点时返回null
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pA=headA,pB=headB;
while(pA!=pB)
{
pA=pA==null?headB:pA.next;
pB=pB==null?headA:pB.next;
}
return pA;
}
4.8 找到链表的中间节点
快慢指针 快指针的速度是慢指针的两倍
链表节点数为奇数: 起始:fast=slow=head 当fast到达最后一个节点时(fast.next==null) slow达到中间节点
链表节点数为偶数fast=slow=head 当fast到达倒数第二节点时(fast.next.next==null) slow达到中间节点
为了统一者两种情况,将节点进行0-n-1编号 偶数节点时只移到快指针 奇数节点时两个指针都移到 当快指针达到最好一个节点时 慢指针达到中间节点(奇数是最中间 偶数的中间偏左)
public static ListNode findMiddle(ListNode head){
if(head==null)
return null;
ListNode fast=head,slow=head;
int i=0;
while(fast.getNext()!=null){
if(i%2==0){
fast=fast.getNext();
}else{
fast=fast.getNext();
slow=slow.getNext();
}
i++;
}
return slow;
}
4.9 逆序打印链表
采用递归做法
public static void printRev(ListNode head){
if(head==null)
return;
printRev(head.getNext());
System.out.println(head.getData());
}
4.10 判断链表长度是奇数还是偶数
一个指针从头节点开始,每次走两步,如果最后指向null,说明偶数,如果最后指向最后一个节点,说明奇数
public static int evenOrOdd(ListNode head){
ListNode p=head;
while(p!=null&&p.getNext()!=null){
p=p.getNext().getNext();
}
if(p==null)
return 0;//偶数个节点
else
return 1;//奇数个节点
}
4.11 将两个有序链表合并为一个有序链表
public ListNode mergeTwoSortedList(ListNode headA,ListNode headB){
ListNode dummy=new ListNode();//创建一个哑节点 方便操作
ListNode cur=dummy;
while(headA!=null&&headB!=null)
{
if(headA.getData()<headB.getData()){//找小的
cur.setNext(headA);
headA=headA.getNext();
}
else{
cur.setNext(headB);
headB=headB.getNext();
}
cur=cur.getNext();
}
if(headA!=null)//链表A还有剩余 直接加上
cur.setNext(headA);
if(headB!=null)
cur.setNext(headB);
return dummy.getNext();
}
4.12 逐对交换链表节点
//递归
public ListNode swapPairs(ListNode head) {
if(head==null||head.next==null)//链表为空或者只有1个节点
return head;
ListNode newHead=head.next;//新的头节点是节点2
head.next=swapPairs(newHead.next);//节点1指向后面n-2个节点完成反转后的头节点
newHead.next=head;//新的头节点的连接节点1
return newHead;
}
//迭代
public ListNode swapPairs(ListNode head) {
if(head==null||head.next==null)//链表为空或者只有1个节点
return head;
ListNode dummy=new ListNode();
dummy.next=head;
ListNode tmp=dummy;
while(tmp.next!=null&&tmp.next.next!=null){//有2个节点
ListNode node1=tmp.next;//指向第1个节点
ListNode node2=tmp.next.next;//指向第2个节点
tmp.next=node2;//第2个节点作为头节点
node1.next=node2.next;//第1个节点指向第3个节点
node2.next=node1;//第2个节点指向第一个节点
tmp=node1;//tmp往后移动一个节点
}
return dummy.next;
}
4.13 K个一组逆置链表
k=2 1->2->3->4->5 变成 2->1->4->3->5
k=3 1->2->3->4->5 变成 3->2->1->4->5
不足k个的不反转
public ListNode reverseKGroup(ListNode head, int k) {
if(k==0||k==1)//0个1组或1个一组相当于不用反转
return head;
ListNode cur=head;
ListNode newHead=null;
if(hasKNodes(cur,k))//链表长度>=k
newHead=getKthNode(cur,k);
else //链表长度<k 相当于不用反转
return head;
ListNode pre=null,tmp;
while(cur!=null&&hasKNodes(cur, k)){
int i=0;
ListNode last=cur;//last保存当前组的第一个节点 反转之后last就是最后1个节点
while(i<k){//逆置某一组 和单链表的反转代码类似
tmp=cur.next;
cur.next=pre;
pre=cur;
cur=tmp;
i++;
}
last.next=getKthNode(cur,k);//逆置完后该组原来的第一个节点变成最后一个
//最后一个节点连接下一组的第k个节点(下一组有k个则和第k个连接 不足就跟第一个节点连接)
}
return newHead;
}
//获取从cur开始往后数的第k个节点
public ListNode getKthNode(ListNode cur,int k){
if(cur==null)
return null;
int i=1;
ListNode p=cur,pre=p;
while(p!=null&&i<k){
pre=p;
p=p.next;
i++;
}
return p==null?cur:p;//有k个则返回改组第k个节点 不足k个则返回改组第一个节点
}
//以cur节点开始是否还有k个节点?
public boolean hasKNodes(ListNode cur,int k){
int i=0;
while(cur!=null){
i++;
if(i==k)
break;
cur=cur.next;
}
return i==k;
}
4.13 约瑟夫环问题
N个人围成一圈,编号为1-N, 从某个人开始从1报数,报到M的人出局,然后从下一个人开始从1报数,直到只剩下一个人
public static ListNode getJosephusPosition(int N,int M,ListNode head){
//head是一个环形链表中的某个节点
ListNode p=head;
for(int cnt=N;cnt>1;cnt--){
for(int i=2;i<=M-1;i++){//找到待删除节点上一个节点 一开始p就指向某个节点(报数1) 因此只需要从2开始报数
p=p.getNext();
}
//前面报数了M-1次 p指向待删除节点的上一个节点
p.setNext(p.getNext().getNext());
p=p.getNext();//从待删除的人的下一个人开始报数
}
return p;
}
4.14 复制带随机指针的链表
先根据已有节点的值创建一个具有相同值的节点,建立原有节点和新节点的映射,先不考虑next和random指针
创建完映射之后,对于原始节点X,对应的新创建的节点Y=map.get(X) Y.next就是map.get(X.next) Y.random就是map.get(X.random) X.next和X.random都是指向原有的节点,原有的节点是map中的键,因此都对应一个新创建的节点
HashMap<Node,Node> map;
public Node copyRandomList(Node head) {
map=new HashMap<>();
Node cur=head;
while(cur!=null){
map.put(cur,new Node(cur.val));
cur=cur.next;
}
cur=head;
while(cur!=null){
Node cloneNode=map.get(cur);
cloneNode.next=map.get(cur.next);
cloneNode.random=map.get(cur.random);
cur=cur.next;
}
return map.get(head);
}