线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见
的线性表:顺序表、链表、栈、队列、字符串
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。
链表有以下三种特性
为什么要重点讲解这一种结构呢?
因为,这种链表结构简单,一般不会单独来存储数据,更多是作为其他数据结构的子结构,如哈希桶、图的链接表等等.另外这种数据结构在笔试中频繁出现,所以我们要重点讲解.
在实现链表之前,我们需要明确链表是由什么构成的,链表是由一个个节点构成的,节点有两个重要的属性 val值,next地址值然后不同的节点有每个节点的地址值串联在一起,像是珍珠由一根线串联在一起,这样一个完整的链表就组成了
代码实现
//构造一个节点类,方便构造链表
class ListNode{
public int val;
public ListNode next =null;
public ListNode (int val){
this.val = val;
}
}
public void addFirst(int data){
ListNode listNode = new ListNode(data);
if(head==null){
head = listNode;
}else{
listNode.next = head;
head = listNode;
}
}
public void addLast(int data){
ListNode listNode = new ListNode(data);
if(head==null) {
head = listNode;
}else{
ListNode cur = this.head;
while(cur.next!=null){
cur=cur.next;
}
cur.next = listNode;
}
}
public void display(){
ListNode cur = this.head;
while(cur!=null){
System.out.print(cur.val+" ");
cur = cur.next;
}
}
public ListNode findNode(int index){
ListNode cur = this.head;
while(index-1!=0){
cur = cur.next;
index--;
}
return cur;
}
public void addIndex(int index,int data){
if(index<0 || index>size()){
System.out.println("插入位置不合法");
return;
}
if(index==0){
addFirst(data);
return;
}
if ((index==size())){
addLast(data);
return;
}
ListNode node = new ListNode(data);
ListNode cur = findNode(index);
node.next = cur.next;
cur.next = node;
}
public ListNode searchNode(int key){
ListNode cur = this.head;
while(cur.next!=null){
if(cur.next.val==key){
return cur;
}
cur=cur.next;
}
return null;
}
此处需要注意,要想删除一个节点,就需要找到删除节点的前驱节点,否则无法删除
public void removeKey(int key){
if(this.head==null){
System.out.println("单链表为空,不能删除");
return;
}
if(this.head.val==key){
this.head = this.head.next;
return;
}
ListNode cur = this.head;
ListNode prve = cur;
ListNode node = findNode(key);
while(cur.next!=null){
if(cur.next.val==key){
cur.next = node.next;
}else{
prve = cur;
cur = cur.next;
}
}
if(cur.val==key){
prve.next = null;
}else{
System.out.println("没找到要删除的数据");
}
}
public ListNode removeAllKey(int key){
ListNode cur = this.head.next;
ListNode prev= this.head;
while(cur!=null){
if(cur.next.val==key){
prev.next = cur.next;
cur = cur.next;
}else{
prev = cur;
cur = cur.next;
}
}
if(this.head.val==key){
head = head.next;
}
return head;
}
public boolean contains(int key){
ListNode cur = this.head;
while(cur!=null){
if(cur.val==key){
return true;
}else {
cur = cur.next;
}
}
return false;
}
public int size(){
int count = 0;
ListNode cur = this.head;
while(cur!=null){
count++;
cur = cur.next;
}
return count;
}
以上就是自定义链表的相关方法与属性,接下来我们可以new MyLinkedList来实现各种方法
上述是我们自己创建的一个链表的数据结构,是为了我们能更好的理解与使用,官方早已经为我们设置好了链表的数据结构 ,我们只需要引用LinkedList 就可以使用我们上述的各种方法了
LinkedList linkedList = new LinkedList();
linkedList.addFirst(1);
linkedList.addLast(2);
linkedList.add(1,2);
链接https://leetcode.cn/problems/remove-linked-list-elements/
**基本思路,**此题言简意赅,就是从头到尾使用cur遍历链表,遇到key值久使用prev.next = cur.next,else 就prev = cur cur = cur.next
class Solution {
public ListNode removeElements(ListNode head, int val) {
if(head==null){
return null;
}
ListNode cur = head.next;
ListNode prev= head;
while(cur!=null){
if(cur.val==val){
prev.next = cur.next;
cur = cur.next;
}else{
prev = cur;
cur = cur.next;
}
}
if(head.val==val){
head = head.next;
}
return head;
}
}
次代码需要注意,在最后还需要判断一下 头节点的值是不是我们所要删除的值,如果是话,还需要做出相关操作
链接https://leetcode.cn/problems/reverse-linked-list/submissions/
基本思想:还是使用cur来遍历,还需要来设置两个值来保存cur的前驱,和cur的next来修改cur的next,然后将头节点的next滞空,再将head的位置改为prev的位置
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null) return null;
ListNode prev = head;
ListNode cur = head.next;
ListNode Cur;
while(cur!=null){
if(prev==head){
head.next=null;
}
Cur = cur.next;
cur.next = prev;
prev = cur;
cur = Cur;
}
head = prev;
return head;
}
}
链接https://leetcode.cn/problems/middle-of-the-linked-list/description/
基本思想我们普遍会想到遍历这个链表然后记录下count的值,来计算中间的值,但是如果要想遍历一变链表的话就需要使用快慢节点的思想来解决了,定义一个slow和一个fast来遍历,fast的速度是slow的双倍,当fast走到走后一个节点的时候,slow刚好走到中间的位置,我们只需要返回slow的位置即可
class Solution {
public ListNode middleNode(ListNode head) {
if(head==null) return null;
ListNode fast = head;
ListNode slow = head;
while(fast!=null && fast.next!=null){
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}
快慢节点是我们解决此题的中心思想
链接https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?
基本思想,本题也是适用的快慢节点的思想,不过此题是先让fast先走k-1步.然后slow和fast一起走,当fast走到末尾处,此时slow就是倒数第ke个节点.
class Solution {
public ListNode FindKthToTail(ListNode head, int k) {
if(k<=0 || head==null){
return null;
}
ListNode slow = head;
ListNode fast = head;
while (k - 1 != 0) {
fast = fast.next;
if(fast==null){
return null;
}
k--;
}
while (fast.next!= null) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
链接:https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=23267&ru=/exam/oj/ta&qru=/ta/coding-interviews/question-ranking&sourceUrl=%2Fexam%2Foj%2Fta%3Fpage%3D1%26tpId%3D13%26type%3D13
基本思想做这道题主要的想法就是穿针引线,创建一个新的虚拟节点,然后依次比较两个有序链表的val值,然后用这个新的节点一个一个穿起来,最后返回节点的next
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode head = new ListNode(-1);
ListNode cur = head;
if(list1==null){
return list2;
}
if(list2==null){
return list1;
}
while(list1!=null&&list2!=null ){
if(list1.val>list2.val){
cur.next = list2;
cur = list2;
list2 = list2.next;
}else{
cur.next = list1;
cur = list1;
list1 = list1.next;
}
}
if(list1==null){
cur.next = list2;
}else{
cur.next = list1;
}
return head.next;
}
}
链接https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70?
基本思想还是和上面的穿针引线的思想差不多,只不过这次需要用两根针来穿一条线,最后再将这俩串联在一起
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Partition {
public ListNode partition(ListNode pHead, int x) {
// write code here
ListNode bs = null;
ListNode be = null;
ListNode as = null;
ListNode ae = null;
ListNode cur = pHead;
while(cur!=null){
if(cur.val<x){
if(bs==null){
bs = cur;
be = cur;
cur = cur.next;
}else{
be.next = cur;
be = be.next;
cur = cur.next;
}
}else{
if(as==null){
as = cur;
ae = cur;
cur = cur.next;
}else{
ae.next = cur;
ae = ae.next;
cur = cur.next;
}
}
}
if(bs==null){
return as;
}
if(as!=null){
ae.next = null;
}
be.next = as;
return bs;
}
}
链接https://www.nowcoder.com/practice/fc533c45b73a41b0b44ccba763f866ef?tpId=13&tqId=23450&ru=/exam/oj/ta&qru=/ta/coding-interviews/question-ranking&sourceUrl=%2Fexam%2Foj%2Fta%3Fpage%3D1%26tpId%3D13%26type%3D13
基本思想,在此之前我们已经做了好几道删除节点的题目了,但是此题的做法与之前不同,要是像之前的那样删除,太过复杂,而且容易混乱,所以我们需要建立一个新节点,将那些没有重复过的节点放在一起,在返回,就可以达到删除重复节点的效果了
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode deleteDuplication(ListNode pHead) {
ListNode cur = pHead;
ListNode newHead = new ListNode(-1);
ListNode temp = newHead;
while(cur!=null){
if(cur!=null &&cur.next!=null && cur.val== cur.next.val){
while(cur!=null &&cur.next!=null && cur.val== cur.next.val){
cur = cur.next;
}
cur = cur.next;
}
else{
temp.next = cur;
temp = temp.next;
cur = cur.next;
}
}
temp.next = null;
return newHead.next;
}
}
链接https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?
基本思想本道题的基本思路就是先找到链表的中间节点,然后以中间节点为界,反转节点后面的链表直到最后一个节点然后再判断slow.val和head.val的值,只要有一个不相等,那么就说明链表不是回文结构的,如果slow和head相遇了,或者head.next=slow了,就说明链表是回文的了
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class PalindromeList {
public boolean chkPalindrome(ListNode A) {
// write code here
ListNode slow = A;
ListNode fast = A;
while(fast!=null && fast.next!=null){
slow = slow.next;
fast = fast.next.next;
}
ListNode cur = slow.next;
ListNode curNext = null;
while(cur!=null){
curNext = cur.next;
cur.next = slow;
slow = cur;
cur = curNext;
}
while(slow.val==A.val){
slow = slow.next;
A = A.next;
if(slow==A){
return true;
}else if(A.next==slow){
return true;
}
}
return false;
}
}
链接https://leetcode.cn/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/submissions/
基本思想:首先我们需要弄清两个问题
经过确定链表相交是呈Y的形状,并且是next相交 确定了这两个问题我们就好做了
第一步,我们先确定哪个链表最长,可以通过遍历两个链表的长度,来确定,并让最长的链表始终和pl绑定在一起,然后求出两个链表相差多少,再让那个最长的链表的pl节点走差值,然后两个链表按部就班的走,直到相遇.⚠️在此过程中改变的是pl与ps,并不是两个链表的头节点
class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA==null || headB==null){
return null;
}
int a[] = new int[10];
ListNode pl = headA;
ListNode ps = headB;
int lenA = 0;
int lenB = 0;
while(pl!=null){
lenA++;
pl = pl.next;
}
pl = headA;
while(ps!=null){
lenB++;
ps = ps.next;
}
ps = headB;
int len = lenA-lenB;
if(lenA-lenB<0){
pl = headB;
ps = headA;
len = -len;
}
while(len!=0){
pl = pl.next;
len--;
}
while(pl!=ps&&pl!=null&&ps!=null){
pl = pl.next;
ps = ps.next;
}
return pl;
}
写代码的时候需要注意,我们在给len赋值的时候要在lenA和lenB自增之后才能赋值,如果在定义lenA和lenB之后赋值代码逻辑就会出现错误,博主就出现了这个错误
链接https://leetcode.cn/problems/linked-list-cycle/
基本思想在做这个题之前我们需要明确怎么判断链表是否有环?
上文我们介绍链表有三种属性,其中是否循环就是属于环的一种,就是在链表的尾巴节点的next值不为空,而是链表中随机的一个地址值,这就叫做有环 那么怎么判断是否有环呢?
首先,我们需要创造两个节点,这两个节点速度不同,两个节点在一个链表里一起走,由于速度不同,那么就会有速度快的节点总会追上速度慢的那个节点的,那么有,如果fast==slow,那么就说明链表有环,如果fast或者fast.next为null,那么就说明链表无环,那么随之会有一个问题,怎么设置两个节点的速度呢?
我们会把fast的速度设为slow速度的二倍,为什么会这样设置呢?这是一个经典的面试题,我们可以用一张图来解答
如果fast与slow的速度是三倍的关系,那么就会有slow与fast始终不能重合就会陷入死循环,所以为了代码的合理性,速度设置为二倍最为妥当
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
if(head==null){
return false;
}
ListNode slow = head;
ListNode fast = head;
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
return true;
}
}
return false;
}
}
此题因为测试用例有所不同,如果在此题将速度设为三倍,是能够通过的,但是以防万一,还是将速度设置为二倍为好
链接https://www.nowcoder.com/practice/253d2c59ec3e4bc68da16833f79a38e4?tpId=13&tqId=23449&ru=/exam/oj/ta&qru=/ta/coding-interviews/question-ranking&sourceUrl=%2Fexam%2Foj%2Fta%3Fpage%3D1%26tpId%3D13%26type%3D13
基本思想 :第十题我们判断了什么叫做环形链表,那么在这个题我们就需要判断入环的节点
我们来画一张图来帮助我们理解
经过分析,我们可以得出X与Y相等,那么我们可以得知当fast与slow相遇的话,我们可以将slow放到起始点,然后slow和fast以相同的速度开始走,当它们相遇的时候,那个节点就是入口节点
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode EntryNodeOfLoop(ListNode head) {
if(head==null){
return null;
}
ListNode slow = head;
ListNode fast = head;
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
slow = head;
while(slow!=fast){
slow = slow.next;
fast = fast.next;
}
return fast;
}
}
return null;
}
}
以上是链表的一些较为详细的真题讲解