================================================================================================
1.链表
-
链表:与顺序表不同,链表不限制数据的物理存储状态,换句话说,使用链表存储的数据元素,其物理存储位置是随机的,所以链表不支持随机访问;
-
链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。我们用头节点 head 表示整个链表;
class Node {
int val; // 保存我们的元素数据
Node next; // 保存指向下一个结点的引用;其中尾节点的 next == null
}
- 元素(element): 真实存于线性表中的内容,是我们关心的核心内容。
- 结点(node): 为了组织链表而引入的一个结构,除了保存我们的元素之外,还会保存指向下一个结点的引用;
- 当前结点(current / cur): 表示链表中某个结点。
- 前驱结点(previous / prev): 表示链表中某个结点的前一个结点;头结点没有前驱结点。
- 后继结点(next): 表示链表中某个结点的后一个结点;尾结点没有后继结点。
- 链表的种类:单链表(带环/不带环;带傀儡节点/不带傀儡节点)、双链表、循环链表
- 创建一个单链表:
// 创建节点类:
public class Node {
public int val;
public Node next;
public Node(int val) {
this.val = val;
}
@Override
public String toString() {
return "["+val+"]";
}
}
// 创建链表的函数/方法:返回 头节点
public static Node createList(){
Node a=new Node(1);
Node b=new Node(2);
Node c=new Node(3);
Node d=new Node(4);
a.next=b;
b.next=c;
c.next=d;
d.next=null;
return a;
}
2.单链表的遍历
- 通过遍历,打印链表的每个元素。
public static void main(String[] args) {
//创建链表,用头节点代表整个链表
Node head= createList();
//通过遍历,打印链表的每个元素:
for (Node cur=head;cur!=null;cur=cur.next){
System.out.println(cur);
}
}
- 通过遍历,找到链表的最后一个结点
//通过遍历,找到链表的最后一个结点:
Node node=head;
while(cur!=null && node.next!=null){
node=node.next;
}
System.out.println(node);
- 通过遍历,找到链表的倒数第二个结点。
//通过遍历,找到链表的倒数第二个结点:
Node cur=head;
while(cur!=null && cur.next!=null && cur.next.next!=null ){
cur=cur.next;
}
System.out.println(cur);
- 通过遍历,找到链表的第 n 个结点。(链表的长度 >= n)
Node cur=head;
for(int i=0;i<n;i++){
cur=cur.next;
}
System.out.println(cur);
- 通过遍历,计算链表中元素的个数。
int length=0;
for (Node cur=head;cur!=null;cur=cur.next){
length++;
}
- 通过遍历链表,返回链表 倒数第 n 个节点:需已知链表长度 length;
- 顺数第 m 个节点 = length +1 - 倒数 n;
Node cur=head;
for(int i=1;i<length+1-n;i++){
cur=cur.next;
}
//或者 i 从 0 ~ length-n;
return cur;
- 通过遍历,找到链表中是否包含某个元素 e。
Node cur=head;
while(cur!=null){
if(e==cur.val){
return true;
}
}
return false;
3.单链表面试题
(1)给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。Leetcode链接
public ListNode removeElements(ListNode head, int val) {
if(head==null){
return null;
}
ListNode prev=head;
while(prev.next!=null){
if(prev.next.val==val){
prev.next=prev.next.next;
}else{
prev=prev.next;
}
}
if(head.val==val){
head=head.next;
}
return head;
}
(2)给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
public ListNode reverseList(ListNode head) {
if(head==null||head.next==null){
return head;
}
ListNode newHead=null;
ListNode prev=null;
ListNode cur=head;
while(cur!=null){
ListNode nextNode=cur.next;
if(nextNode==null){
newHead=cur;
}
cur.next=prev;
prev=cur;
cur=nextNode;
}
return newHead;
}
(3)给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
public ListNode middleNode(ListNode head) {
int count=0;
for(ListNode cur=head;cur!=null;cur=cur.next){
count++;
}
ListNode middleNode=head;
for(int i=1;i<=count/2;i++){
middleNode=middleNode.next;
}
return middleNode;
}
(4)将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1==null){
return l2;
}
if(l2==null){
return l1;
}
ListNode cur1=l1;
ListNode cur2=l2;
//设置一个傀儡节点,方便尾插;
ListNode newHead=new ListNode(0);
ListNode tailNode=newHead;
while(cur1!=null&&cur2!=null){
if(cur1.val<cur2.val){
tailNode.next=new ListNode(cur1.val);
cur1=cur1.next;
}else{
tailNode.next=new ListNode(cur2.val);
cur2=cur2.next;
}
tailNode=tailNode.next;
}
if (cur1==null){
tailNode.next=cur2;
}else{
tailNode.next=cur1;
}
return newHead.next;
}
(5)现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
public ListNode partition(ListNode pHead, int x) {
// write code here
if(pHead==null||pHead.next==null){
return pHead;
}
ListNode smallList=new ListNode(0);
ListNode tailSmall=smallList;
ListNode largeList=new ListNode(0);
ListNode tailLarge=largeList;
ListNode cur=pHead;
while(cur!=null){
if(cur.val<x){
tailSmall.next=new ListNode(cur.val);
tailSmall=tailSmall.next;
}else{
tailLarge.next=new ListNode(cur.val);
tailLarge=tailLarge.next;
}
cur=cur.next;
}
tailSmall.next=largeList.next;
return smallList.next;
}
(6)在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5;
public ListNode deleteDuplication(ListNode pHead) {
if (pHead==null||pHead.next==null){
return pHead;
}
ListNode dummy=new ListNode(0);
ListNode prev=dummy;
ListNode cur=pHead;
while(cur!=null){
if(cur.next!=null && cur.val== cur.next.val){
cur = cur.next;
while(cur!=null && cur.next!=null&& cur.val== cur.next.val) {
cur = cur.next;
}
}else{
prev.next=new ListNode(cur.val);
prev=prev.next;
}
cur=cur.next;
}
return dummy.next;
}
(7)对于一个链表,判断其是否为回文结构。给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
方法1:
//时间复杂度为O(n),额外空间复杂度为O(1)的算法:将后半段链表逆序再和前半段比较;
public int size(ListNode A){
int size=0;
for(ListNode cur=A;cur!=null;cur=cur.next){
size++;
}
return size;
}
public boolean chkPalindrome(ListNode A){
if (A == null || A.next == null) {
return true;
}
ListNode B=A;
//为了得到原链表A的后半段链表;
int size=size(A)/2;
for(int i=0;i<size;i++){
B=B.next;
}
//逆序B
ListNode prev=null;
ListNode cur=B;
while (cur != null) {
ListNode next=cur.next;
if(next==null){
B=cur;
}
cur.next=prev;
prev=cur;
cur=next;
}
ListNode a=A;
ListNode b=B;
while (a != null && b != null) {
if (a.val != b.val) {
return false;
}
a=a.next;
b=b.next;
}
return true;
}
方法2:
// 空间复杂度为O(n):拷贝一份链表,并逆序再比较两个链表,相同为回文;
public boolean chkPalindrome(ListNode A) {
if(A==null||A.next==null){
return true;
}
ListNode head=new ListNode(0);
ListNode c=head;
for (ListNode cur=A;cur!=null;cur=cur.next){
c.next=new ListNode(cur.val);
c=c.next;
}
ListNode B=head.next;
ListNode prev=null;
ListNode cur=B;
while (cur != null) {
ListNode next=cur.next;
if(next==null){
B=cur;
}
cur.next=prev;
prev=cur;
cur=next;
}
ListNode a=A;
ListNode b=B;
while(a!=null&&b!=null){
if(a.val!=b.val){
return false;
}
a=a.next;
b=b.next;
}
return true;
}
(8)给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
// 长的链表引用多走多余长的节点;然后用 == 比较两个节点的引用(地址)是否相等;
public int size(ListNode A){
int size=0;
for(ListNode cur=A;cur!=null;cur=cur.next){
size++;
}
return size;
}
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA==null||headB==null){
return null;
}
int la=size(headA);
int lb=size(headB);
if(la>lb){
int step=la-lb;
for (int i = 0; i < step; i++) {
headA=headA.next;
}
}else{
int step=lb-la;
for (int i = 0; i < step; i++) {
headB=headB.next;
}
}
ListNode a=headA;
ListNode b=headB;
while (a != null && b != null) {
if(a==b){
return a;
}
a=a.next;
b=b.next;
}
return null;
}
(9)给定一个链表,判断链表中是否有环:使用快慢指针判断
public boolean hasCycle(ListNode head) {
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;
}
(10)给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
// 第一次碰撞点Pos到连接点Join的距离=头指针到连接点Join的距离,因此,分别从第一次碰撞点Pos、头指针head开始走,相遇的那个点就是连接点。
public ListNode detectCycle(ListNode head) {
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;
}
ListNode cur1=head;
ListNode cur2=fast;
while (cur1!=cur2){
cur1=cur1.next;
cur2=cur2.next;
}
return cur1;
}
4.双链表的实现
- 带头节点、尾节点;
- 双向链表插入节点:
- 双向链表删除节点:
每个节点定义有前驱和后继:
class Node {
public int val;
public Node prev=null;
public Node next=null;
public Node(int val) {
this.val = val;
}
@Override
public String toString() {
return "[" + val +"]";
}
}
- MyLinkList实现:
//双向链表的实现
public class MyLinkList {
private Node head;
private Node tail;
private int length;
public MyLinkList() {
head=null;
tail=null;
}
//增加操作
//双向链表头插法:
public void addFirst(int val){
Node newNode=new Node(val);
//要插入的双向链表为空时
if (head==null){
head=newNode;
tail=newNode;
}else{
newNode.next=head;
head.prev=newNode;
head=newNode;
}
length++;
}
//尾插法:
public void addLast(int val){
Node newNode = new Node(val);
if(tail==null){
head=newNode;
tail=newNode;
}else{
tail.next=newNode;
newNode.prev=tail;
tail=newNode;
}
length++;
}
//在任一个指定位置,插入新节点
public void add(int index,int val){
if (index<1||index>length+1){
return;
}
if (index==1){
addFirst(val);
return;
}
if (index==length+1){
addLast(val);
return;
}
Node cur=getNode(index);
Node newNode = new Node(val);
Node prevNode=cur.prev;
//先处理前面的链接
prevNode.next=newNode;
newNode.prev=prevNode;
//在处理和当前节点的链接
newNode.next=cur;
cur.prev=newNode;
length++;
}
//删除操作
//头删法:
public void removeFirst() {
if (head == null) {
return;
}
head=head.next;
length--;
}
//尾删法:
public void removeLast() {
if (head == null) {
return;
}
tail.prev.next=tail.next;
tail=tail.prev;
length--;
}
//删除任意位置的一个节点
public void removeByIndex(int index) {
if (head == null) {
return;
}
if (index<1||index>length){
return;
}
if (index==1){
removeFirst();
return;
}
if (index==length){
removeLast();
return;
}
Node prevNode=getNode(index-1);
Node nextNode=getNode(index+1);
prevNode.next=nextNode;
nextNode.prev=prevNode;
length--;
}
//删除一个值为val的节点
public void removeByVal(int val) {
if (head == null) {
return;
}
boolean b=contains(val);
if(b){
int index=indexOf(val);
removeByIndex(index);
}else{
System.out.println("该链表没有该元素,删除失败!");
}
}
//获取链表长度
public int getLength() {
return this.length;
}
//获取某一位置的节点
public Node getNode(int index) {
if (index<0||index>length){
return null;
}
Node cur=head;
for(int i=1;i<index;i++){
cur=cur.next;
}
return cur;
}
//修改链表某一位置的节点的值
public void setNode(int index, int val) {
if (head == null) {
return;
}
if (index < 1 || index > length) {
return;
}
Node curNode = getNode(index);
curNode.val=val;
}
//查找值为e的节点在链表中的第一个位置
public int indexOf(int val){
if (head == null) {
return -1;
}
int index=0;
for (Node cur = head; cur != null; cur = cur.next) {
index++;
if(cur.val==val){
break;
}
}
return index;
}
//查找值为e的节点在链表中的倒数第一的位置
public int lastIndexOf(int val) {
if (head == null) {
return -1;
}
int index=0;
for (Node cur = tail; cur != null; cur = cur.prev) {
if(cur.val==val){
break;
}
index++;
}
return getLength()-index;
}
//打印链表
@Override
public String toString() {
StringBuilder list=new StringBuilder();
list.append("[");
Node cur=head;
while(cur!=null){
list.append(cur.val);
if (cur.next!=null){
list.append("->");
}
cur=cur.next;
}
list.append("]");
return list.toString();
}
public boolean contains(int val){
Node cur=head;
while (cur != null) {
if (cur.val==val){
return true;
}
cur=cur.next;
}
return false;
}
public boolean isEmpty(){
return head == null && tail == null;
}
public void clear(){
head=null;
tail=null;
}
}