目錄
1、什麼是鍊錶?
2、實作一個單向非循環鍊錶
2.1 實作前的約定" >2.1 實作前的約定
#2.2 addFirst 方法" >#2.2 addFirst 方法
2.3 addList 方法" >2.3 addList 方法
2.4 addIndex 方法" >2.4 addIndex 方法
2.5 contains 方法" >2.5 contains 方法
2.6 remove 方法" >2.6 remove 方法
2.7 removeAllKey 方法" >2.7 removeAllKey 方法
2.8 size 和 clear 方法" >2.8 size 和 clear 方法
3、單鍊錶OJ題深度解剖
3.1 移除鍊錶元素(來源:LeetCode 難度:簡單)" >3.1 移除鍊錶元素(來源:LeetCode 難度:簡單)
3.2 反转链表(来源:LeetCode 难度:简单)" >3.2 反转链表(来源:LeetCode 难度:简单)
3.4 链表中倒数第k个节点" >3.4 链表中倒数第k个节点
3.6 链表分割(来源:牛客网 难度:较难) " >3.6 链表分割(来源:牛客网 难度:较难) 
3.7 链表的回文结构(来源:LeetCode 难度:较难) " >3.7 链表的回文结构(来源:LeetCode 难度:较难) 
3.8 相交链表(来源:LeetCode 难度:简单) " >3.8 相交链表(来源:LeetCode 难度:简单) 
首頁 Java java教程 Java資料結構之單鍊錶與OJ題

Java資料結構之單鍊錶與OJ題

Nov 24, 2022 pm 05:38 PM
java

本篇文章為大家帶來了關於java的相關知識,其中主要介紹了關於資料結構的相關內容,包括了單鍊錶與OJ題,下面一起來看一下,希望對大家有幫助。

Java資料結構之單鍊錶與OJ題

推薦學習:《java影片教學

1、什麼是鍊錶?

鍊錶是一種實體儲存結構上非連續儲存結構,資料元素的邏輯順序是透過鍊錶中的參考連結順序實現的 。

通俗點,就是每個元素是一個節點,然後用一個指標域給後面的節點連起來,第一個節點沒有前驅,最後一個節點沒有後繼。

實際上要實作的鍊錶的結構非常多樣,以下情況組合起來就有8種鍊錶結構:

1 . 單向、雙向         2. 帶頭、不帶頭         3. 循環、非循環

#我們著重講解單向非循環鍊錶和雙向非循環鍊錶,同時這兩個也是筆試中考的比較多的。

2、實作一個單向非循環鍊錶

2.1 實作前的約定

因為鍊錶的每個元素是一個節點,所以我們採取內部類別的方式,而我們還需要定義一個頭節點的引用,來始終指向頭節點。

public class MySingleList {
    private ListNode head; //引用头节点
    // 链表每个元素是一个节点
    private class ListNode {
        private int val; //存放数据元素
        private ListNode next; //存放下一个节点地址
        //构造方法
        public ListNode(int val) {
            this.val = val;
        }
    }
}
登入後複製

 注意:鍊錶最少有兩個域,分別是資料域和指標域,當然你也可以有多個資料域和指標域。

我們還需要實作以下幾個常用的方法:

public void addFirst(int data); //头插法

public void addLast(int data); //尾插法

public boolean addIndex(int index,int data); //任意位置插入,第一个数据节点为0号下标

public boolean contains(int key); //查找关键字key是否在单链表当中

public void remove(int key); //删除第一次出现关键字为key的节点

public void removeAllKey(int key); //删除所有值为key的节点

public int size(); //得到单链表的长度

public void clear(); //清空链表
登入後複製

#2.2 addFirst 方法

public void addFirst(int data) {
    ListNode newNode = new ListNode(data); //把传过来的值放到新的节点中
    newNode.next = this.head; //新节点的next指向头节点
    this.head = newNode; //使新节点成为头节点
}
登入後複製

因為head預設是指向空的,當鍊錶為null,也不影響這個程式碼的執行,不信你下來畫畫圖咯。

2.3 addList 方法

public void addLast(int data) {
    ListNode newNode = new ListNode(data);
    // 如果链表为空的情况
    if (this.head == null) {
        this.head = newNode;
        return;
    }
    // 先找到最后一个节点
    ListNode cur = this.head;
    while (cur.next != null) {
        cur = cur.next;
    }
    cur.next = newNode;
}
登入後複製

2.4 addIndex 方法

//任意位置插入,第一个数据节点为0号下标
private ListNode findIndexPrevNode(int index) {
    ListNode cur = this.head;
    while (index - 1 != 0) {
        cur = cur.next;
        index--;
    }
    return cur;
}
public boolean addIndex(int index,int data) {
    // 判断index下标的有效性
    if (index < 0 || index > size()) {
        return false;
    }
    // 如果在0下标插入则是头插
    if (index == 0) {
        addFirst(data); //头插
        return true;
    }
    // 如果在末尾插入则是尾插
    if (index == size()) {
        addLast(data); //尾插
        return true;
    }

    ListNode newNode = new ListNode(data); //新节点
    // 在中间插入
    ListNode prevNode = findIndexPrevNode(index); //找到index下标的前一个节点
    newNode.next = prevNode.next; //新节点的next被改为index的位置的节点
    prevNode.next = newNode; //index位置前一个节点next被改成新节点
    return true;
}
登入後複製

這個程式碼我們首先需要找到index下標的前一個節點,先讓新節點跟index位置的節點綁定起來,在把index的前一個節點與新節點連起來,這個地方說文字是不清楚的,小伙伴們可以下來按照我這個代碼畫圖就能理解了,也可也直接看博主之前的C語言實作資料結構專欄,那裡面有圖解哈。

2.5 contains 方法

//查找关键字key是否在单链表当中
public boolean contains(int key) {
    // 当链表为空的情况
    if (this.head == null) {
        return false;
    }
    ListNode cur = this.head;
    // 遍历列表
    while (cur != null) {
        if (cur.val == key) {
            return true; //找到了返回true
        }
        cur = cur.next;
    }
    return false; //找不到返回false
}
登入後複製

思路很簡單,遍歷一遍鍊錶,找到cur 為空位置,如果有回傳true,沒有回傳false,交給小夥伴自己下來畫圖咯。

2.6 remove 方法

//删除第一次出现关键字为key的节点
public void remove(int key) {
    if (this.head == null) {
        return;
    }
    ListNode cur = this.head;

    // 如果删除的是key为头节点
    if (this.head.val == key) {
        this.head = head.next;
        return;
    }

    // 这里不能是cur!=null, 不然会越界!!!
    while (cur.next != null) {
        // 找到 key 的前一个节点
        if (cur.next.val == key) {
            //当前的cur为key的前一个节点
            cur.next = cur.next.next; //cur链接到key的后一个节点
            return;
        }
        cur = cur.next;
    }
}
登入後複製

這裡我們需要找到key的前一個節點,然後進行跟key後面的節點綁定即可,當key對應節點沒人引用了,則會被自動回收掉。

2.7 removeAllKey 方法

//删除所有值为key的节点
public void removeAllKey(int key) {
    if (this.head == null) {
        return;
    }
    // 采用前后指针的方法
    ListNode cur = this.head;
    ListNode prev = this.head;
    while (cur != null) {
        if (cur.val == key) {
            prev.next = cur.next; //跳过key节点指向下一个节点
        } else {
            prev = cur;
        }
        cur = cur.next;
    }
    // 判断第一个节点是不是key
    if (this.head.val == key) {
        this.head = this.head.next; //head指向下一个节点
    }
}
登入後複製

這裡大傢伙先自己看看,後面講解OJ題會有這題詳解的。

2.8 size 和 clear 方法

我相信這兩個方法就不需要多說了吧?遍歷?直接頭指針置null?這不就簡單了嗎,這裡就不寫了哈,交給各位了!

3、單鍊錶OJ題深度解剖

這個才是今天的重頭戲,不是籃球哥不畫圖,是因為前面的圖太簡單了,小夥伴們結合程式碼也能自己畫出來,但是,對於OJ題,大傢伙下去還是得畫圖的,相信看完這幾題,你會愛上資料結構的。

3.1 移除鍊錶元素(來源:LeetCode 難度:簡單)

題目:給你一個鍊錶的頭節點 head 和一個整數 val# ,請你刪除鍊錶中所有滿足 Node.val == val 的節點,並回傳 新的頭節點 。

这个题我们可以用前后指针的思路来做,这样也比较通俗易懂,更适合初学者,大概的思路是这样的:我们可以定义一个cur和first的引用,如果碰到相等,也就是first.val == val,我们则让cur的next指向first的下一个节点,如果不相等,则让cur走到first的位置,最后first往后走一步,图解:

这里还没有完,如果第一个节点的值也是val呢?所以最后我们别忘了进行一个判断,那么最终的代码是这样的:

public ListNode removeElements(ListNode head, int val) {
    if (head == null) {
        return null;
    }
    ListNode cur = head;
    ListNode first = head;
    while (first != null) {
        if (first.val == val) {
            cur.next = first.next;
        } else {
            cur = first;
        }
        first = first.next;
    }
    // 判断头节点的值是否也是val
    if (head.val == val) {
        head = head.next;
    }
    return head;
}
登入後複製

3.2 反转链表(来源:LeetCode 难度:简单)

题目:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 

这个题我们可以先取到头节点,后续的节点都进行头插法即可?我们取到头节点,并且先将头节点的next置空,但是这样一来,就找不到后面的节点了,所以我们还需要有一个curNext引用来记录要反转节点的下一个节点:

我们的思路是这样的:首先找到头节点的next置空,cur走到curNext位置,curNext往前走,使得cur位置的next指向头节点,头节点cur再次成为新的头节点,当curNext走到null的时候循环结束。

public ListNode reverseList(ListNode head) {
    // 空链表的情况
    if (head == null) {
        return null;
    }
    ListNode cur = head;
    ListNode curNext = cur.next;
    head.next = null;
    while (curNext != null) {
        cur = curNext;
        curNext = curNext.next;
        cur.next = head;
        head = cur;
    }
    return head;
}
登入後複製

3.4 链表中倒数第k个节点

题目:输入一个链表,输出该链表中倒数第k个结点。 

这个题也是很简单的一道题,可以采用前后指针法,先让first指针走k步,走完之后slow和first一起走,这样slow和first之间就相差了k步,当first为null时,slow就是倒数第k个节点,在这个过程中,我们还要判断k的合法性,如果k小于等于0?或者k大于链表的长度呢?于是我们就能写出如下的代码:

public ListNode FindKthToTail(ListNode head,int k) {
    // 判断k的合法性
    if (k <= 0 || head == null) {
        return null;
    }
    ListNode first = head;
    ListNode slow = head;
    // 先让first走k步
    while (k != 0) {
        // k的长度大于链表的长度
        if (first == null) {
            return null;
        }
        first = first.next;
        k--;
    }
    // 一起走,当first为null,slow就是倒数第k个节点
    while (first != null) {
        first = first.next;
        slow = slow.next;
    }
    return slow;
}
登入後複製

3.6 链表分割(来源:牛客网 难度:较难) 

题目:现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。 

这个题的思路我们可以这样做,既然是按照给定的值x进行分割,那么我们设定两个盘子,盘子A放小于x的节点,盘子B放大于x的节点,最后把这两个盘子的节点连起来,放回盘子A的头节点即可!

 public ListNode partition(ListNode pHead, int x) {
        if (pHead == null) {
            return null;
        }
        ListNode headA = null;
        ListNode headB = null;
        ListNode curA = null;
        ListNode curB = null;
        ListNode cur = pHead;
        while (cur != null) {
            if (cur.val < x) {
                // 第一次放入A盘子
                if (headA == null) {
                    headA = cur;
                    curA = cur;
                } else {
                    curA.next = cur;
                    curA = cur;
                }
            } else {
                // 第一次放入B盘子
                if (headB == null) {
                    headB = cur;
                    curB = cur;
                } else {
                    curB.next = cur;
                    curB = cur;
                }
            }
            cur = cur.next;
        }
        // 如果A盘子为空
        if (headA == null) {
            return headB;
        }
        curA.next = headB;
        // 避免B盘子尾节点形成环
        if (headB != null) {
            curB.next = null;
        }
        return headA;
    }
登入後複製

3.7 链表的回文结构(来源:LeetCode 难度:较难) 

题目:对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。

给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

这个题有要求的,要求空间复杂度为O(1),并且还得在O(n)的时间复杂度下,那我们就原地解决这个题,我们可以分为三个步骤,首先找到中间节点,然后把中间节点往后的节点进行反转,最后左右两个指针同时往中间走。如果光看下面代码看不懂,可以结合着代码画图才能理解更透彻哦!

public boolean chkPalindrome(ListNode A) {
    if (A == null) {
        return false;
    }
    // 只有一个节点的情况
    if (A.next == null) {
        return true;
    }
    // 首先找到中间节点
    ListNode first = A;
    ListNode slow = A;
    while (first != null && first.next != null) {
        first = first.next.next;
        slow = slow.next;
    }
    // 走到这,slow是链表的中间节点,采用头插法反转slow后续的节点
    first = slow.next;
    ListNode cur = slow;
    while (first != null) {
        cur = first;
        first = first.next;
        cur.next = slow; //链接前一个节点
        slow = cur; //更新头节点的位置
    }
    // 走到这,反转完毕,cur指向最后一个节点,让slow等于A,往中间找
    slow = A;
    while (slow != cur) {
        if (slow.val != cur.val) {
            return false;
        }
        // 偶数的情况下需要特殊判断
        if (slow.next == cur) {
            return true;
        }
        slow = slow.next;
        cur = cur.next;
    }
    return true;
}
登入後複製

3.8 相交链表(来源:LeetCode 难度:简单) 

题目:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

这个题我们可以这样做,长链表先走两个链表的长度差的步数,因为相交之后的节点都是一样的个数,所以走了差值后,就两个链表一起往后走,相等了则就是相交节点。

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    if (headA == null || headB == null) {
        return null;
    }
    ListNode longList = headA; //longList始终记录长的链表
    ListNode shortList = headB;
    // 分别求出两个链表的长度
    int lenA = 0;
    int lenB = 0;
    ListNode cur = headA;
    while (cur != null) {
        lenA++;
        cur = cur.next;
    }
    cur = headB;
    while (cur != null) {
        lenB++;
        cur = cur.next;
    }
    int len = lenA - lenB;
    // 如果B链表长于A链表
    if (len < 0) {
        // 修正相差长度
        len = lenB - lenA;
        longList = headB; //longList始终记录长的链表
        shortList = headA;
    }
    // 让长链表先走差值len步,然后同步走,相等了即为相交节点
    while (len != 0) {
        longList = longList.next;
        len--;
    }
    while (longList != shortList) {
        longList = longList.next;
        shortList = shortList.next;
    }
    // 如果两个链表走到了null,则没有中间节点返回null,如果有,返回任意一个即可
    return longList;
}
登入後複製

推荐学习:《java视频教程

以上是Java資料結構之單鍊錶與OJ題的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1670
14
CakePHP 教程
1428
52
Laravel 教程
1329
25
PHP教程
1273
29
C# 教程
1256
24
PHP:網絡開發的關鍵語言 PHP:網絡開發的關鍵語言 Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP與Python:了解差異 PHP與Python:了解差異 Apr 11, 2025 am 12:15 AM

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

突破或從Java 8流返回? 突破或從Java 8流返回? Feb 07, 2025 pm 12:09 PM

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

PHP與其他語言:比較 PHP與其他語言:比較 Apr 13, 2025 am 12:19 AM

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

PHP與Python:核心功能 PHP與Python:核心功能 Apr 13, 2025 am 12:16 AM

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

PHP的影響:網絡開發及以後 PHP的影響:網絡開發及以後 Apr 18, 2025 am 12:10 AM

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

PHP:許多網站的基礎 PHP:許多網站的基礎 Apr 13, 2025 am 12:07 AM

PHP成為許多網站首選技術棧的原因包括其易用性、強大社區支持和廣泛應用。 1)易於學習和使用,適合初學者。 2)擁有龐大的開發者社區,資源豐富。 3)廣泛應用於WordPress、Drupal等平台。 4)與Web服務器緊密集成,簡化開發部署。

PHP與Python:用例和應用程序 PHP與Python:用例和應用程序 Apr 17, 2025 am 12:23 AM

PHP適用於Web開發和內容管理系統,Python適合數據科學、機器學習和自動化腳本。 1.PHP在構建快速、可擴展的網站和應用程序方面表現出色,常用於WordPress等CMS。 2.Python在數據科學和機器學習領域表現卓越,擁有豐富的庫如NumPy和TensorFlow。

See all articles