首頁  >  文章  >  Java  >  Java資料結構之單鍊錶與OJ題

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

WBOY
WBOY轉載
2022-11-24 17:38:022135瀏覽

本篇文章為大家帶來了關於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中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除