力扣鏈表+簡單遞歸

鏈表算法

https://leetcode-cn.com/problemset/all/?topicSlugs=linked-list&difficulty=%E7%AE%80%E5%8D%95 ----題目連接java

1.設計鏈表

設計鏈表的實現。您能夠選擇使用單鏈表或雙鏈表。單鏈表中的節點應該具備兩個屬性:val 和 next。val 是當前節點的值,next 是指向下一個節點的指針/引用。若是要使用雙向鏈表,則還須要一個屬性 prev 以指示鏈表中的上一個節點。假設鏈表中的全部節點都是 0-index 的。node

在鏈表類中實現這些功能:算法

get(index):獲取鏈表中第 index 個節點的值。若是索引無效,則返回-1。
addAtHead(val):在鏈表的第一個元素以前添加一個值爲 val 的節點。插入後,新節點將成爲鏈表的第一個節點。
addAtTail(val):將值爲 val 的節點追加到鏈表的最後一個元素。
addAtIndex(index,val):在鏈表中的第 index 個節點以前添加值爲 val 的節點。若是 index 等於鏈表的長度,則該節點將附加到鏈表的末尾。若是 index 大於鏈表長度,則不會插入節點。
deleteAtIndex(index):若是索引 index 有效,則刪除鏈表中的第 index 個節點。app

代碼

class MyLinkedList {
    
     class Node {
     int val;
     Node next;
     Node(int x) { this.val = x; }
     }
    int size=0;
    private Node head =new Node(0);//頭節點,
    private Node tail=head;//尾結點
     
    /** Initialize your data structure here. */
    
    public MyLinkedList() {
    
 }
        
    
    
    /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
    public int get(int index) {
        if(index <0 || index >=size){
            return -1;
        }
        Node curr =head;
        while(index>=0){
            index--;
            curr=curr.next;
        }
        return curr.val;
        
    }
    
    /** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
    public void addAtHead(int val) {
        size++;
        Node node =new Node(val);
        node.next =head.next;
        head.next =node;
        if(tail == head){//尾結點(只有一個節點的時候)
            tail=node;
        }
    }
    
    /** Append a node of value val to the last element of the linked list. */
    public void addAtTail(int val) {
        size++;
        Node node = new Node(val);
        tail.next =node;
        tail =tail.next;
        
        
    }
    
    /** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
    public void addAtIndex(int index, int val) {
        if(index>size){
            return;
        }
        size++;
        Node node= new Node(val);
        if(index==size-1){
            tail.next=node;
            tail=tail.next;
            return;
        }
        Node curr =head;
        while(index>0){
            index--;
            curr =curr.next;
        }
        node.next =curr.next;
        curr.next=node;
        
    }
    
    /** Delete the index-th node in the linked list, if the index is valid. */
    public void deleteAtIndex(int index) {
        if(index<0 || index>=size){
            return;
        }
        size--;
        Node curr=head;
        while(index>0){
            index--;
            curr=curr.next;
        }
        if(curr.next!=null)
        {
        curr.next=curr.next.next;
        }
        if(curr.next == null){
            tail =curr;
        }
        
    }
}

解析

仍是蠻基礎、蠻實用的。不是很難看一看就會了函數

2.迴文鏈表

請判斷一個鏈表是否爲迴文鏈表。this

示例 1:設計

輸入: 1->2
輸出: false
示例 2:指針

輸入: 1->2->2->1
輸出: truecode

代碼

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
   boolean isPalindrome(ListNode head) {
    ListNode slow =head;ListNode fast =head;ListNode prev = null;
    while(fast!=null){
        slow=slow.next;
        if(fast.next !=null) fast=fast.next.next;
        else fast=fast.next;
    }
    while(slow!=null){
        //反轉
        ListNode ovn =slow.next;
        slow.next =prev;
        prev= slow;
        slow=ovn;
    }
    while(head!=null && prev!=null)
    {
        //check
        if(head.val != prev.val)
        {
            return false;
        }
        head =head.next;
        prev =prev.next;
    }
    return true;
}
}

解析

其一,find mid node 使用快慢指針找到鏈表中點。 其二,reverse 逆序後半部分。 其三,check 從頭、中點,開始比較是否相同。對象

3.刪除鏈表中的節點

請編寫一個函數,使其能夠刪除某個鏈表中給定的(非末尾)節點,你將只被給定要求被刪除的節點。

現有一個鏈表 -- head = [4,5,1,9],它能夠表示爲:

示例 1:

輸入: head = [4,5,1,9], node = 5
輸出: [4,1,9]
解釋: 給定你鏈表中值爲 5 的第二個節點,那麼在調用了你的函數以後,該鏈表應變爲 4 -> 1 -> 9.
示例 2:

輸入: head = [4,5,1,9], node = 1
輸出: [4,5,9]
解釋: 給定你鏈表中值爲 1 的第三個節點,那麼在調用了你的函數以後,該鏈表應變爲 4 -> 5 -> 9.

代碼

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
public void deleteNode(ListNode node) {
   node.val=node.next.val;
    node.next=node.next.next;
}

}

解析:很精巧的一道題,參數沒有給head一開始沒注意到。

4.鏈表中間節點

給定一個帶有頭結點 head 的非空單鏈表,返回鏈表的中間結點。

若是有兩個中間結點,則返回第二個中間結點。

示例 1:

輸入:[1,2,3,4,5]
輸出:此列表中的結點 3 (序列化形式:[3,4,5])
返回的結點值爲 3 。 (測評系統對該結點序列化表述是 [3,4,5])。
注意,咱們返回了一個 ListNode 類型的對象 ans,這樣:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
示例 2:

輸入:[1,2,3,4,5,6]
輸出:此列表中的結點 4 (序列化形式:[4,5,6])
因爲該列表有兩個中間結點,值分別爲 3 和 4,咱們返回第二個結點。

代碼

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode slow=head;
        ListNode fast=head;
        
        while(fast.next!=null)
        {
            slow=slow.next;
            
            if(fast.next.next==null)
            {
                fast=fast.next;
                
            }
            else{
                fast=fast.next.next;
                
            }
        
        
    }
         
            return slow;
        
}
}

解析

快慢指針,一個二倍速,很簡單啦

4..合併兩個有序隊列(java)

將兩個有序鏈表合併爲一個新的有序鏈表並返回。新鏈表是經過拼接給定的兩個鏈表的全部節點組成的。

示例:

輸入:1->2->4, 1->3->4
輸出:1->1->2->3->4->4

代碼

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    ListNode Newhead = new ListNode(0);
    ListNode newl = Newhead;
    while(l1!=null && l2!=null){
        if(l1.val<=l2.val)
        {
            newl.next=l1;
            l1=l1.next;
             newl=newl.next;
            
        }
           
        else
        {
           newl.next=l2;
            l2=l2.next; 
             newl=newl.next;
        }
        
        
    }
    if(l1 == null)
    {
        newl.next=l2;
    }
    else{
        newl.next=l1;
    }
    return Newhead.next;
}
}

解析

  1. 這已是一個排好序的隊列了,兩個隊列若是都不空,就誰更小誰進入新的鏈表。最後剩下的一條不空鏈表必定是有序的接在新鏈表的尾部便可(也必定是大於前面的元素)
  2. 最後要返回一整條鏈表,發現定義的new1的指針指在了鏈表的中部,因此在一開始定義的時候必定要留有一個指向鏈表頭部的Newhead,這樣才能返回一個完整的鏈表

5.相交鏈表

示例 1:

輸入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
輸出:Reference of the node with value = 8
輸入解釋:相交節點的值爲 8 (注意,若是兩個列表相交則不能爲 0)。從各自的表頭開始算起,鏈表 A 爲 [4,1,8,4,5],鏈表 B 爲 [5,0,1,8,4,5]。在 A 中,相交節點前有 2 個節點;在 B 中,相交節點前有 3 個節點。

代碼

/**
     * Definition for singly-linked list.
     * public class ListNode {
     *     int val;
     *     ListNode next;
     *     ListNode(int x) {
     *         val = x;
     *         next = null;
     *     }
     * }
     */
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    ListNode a = headA;
    ListNode b = headB;
    int m=0,n=0;
    if(a ==null ||b ==null ) return null;
    while(a.next!= null){
        ++m;
        a=a.next;
        
    }
   
    while(b.next!=null)
    {
        ++n;
        b=b.next;
    }
    if(a !=b) return null;
    int i=0,l=0;
    a=headA;
    b=headB;
    if(n<m){
        l=m-n;
       
    }
    else{
        l=n-m;
        a=headB;
        b=headA;
    }
    
    while(i<l){
        a=a.next;
        i++;
    }
    while(a!=null && b!= null)
    {
        if(a== b) return a;
        a=a.next;
        b=b.next;
       // System.out.println(a.val);
        // System.out.println(b.val);
        
    }
    return null;
}
}

解析

  1. 鏈表爲空必定沒有相交的,返回null
  2. 遍歷兩個鏈表的長度(若是末尾的元素不等也返回NULL),削短長度長的鏈表使兩個鏈表等長。
  3. 一次遍歷兩個鏈表,若是地址相同,則是相交的點。(注意不是鏈表節點的值相同,而是地址相同)。返回節點便可

    遞歸接替三部曲

何爲遞歸?程序反覆調用自身便是遞歸。

我本身在剛開始解決遞歸問題的時候,老是會去糾結這一層函數作了什麼,它調用自身後的下一層函數又作了什麼…而後就會以爲實現一個遞歸解法十分複雜,根本就無從下手。

相信不少初學者和我同樣,這是一個思惟誤區,必定要走出來。既然遞歸是一個反覆調用自身的過程,這就說明它每一級的功能都是同樣的,所以咱們只須要關注一級遞歸的解決過程便可。

咱們須要關心的主要是如下三點:

  1. 整個遞歸的終止條件。

  2. 一級遞歸須要作什麼?

  3. 應該返回給上一級的返回值是什麼?

所以,也就有了咱們解樹形遞歸題的三部曲:

  1. 找整個遞歸的終止條件:遞歸應該在何時結束?

  2. 找返回值:應該給上一級返回什麼信息?

  3. 本級遞歸應該作什麼:在這一級遞歸中,應該完成什麼任務?

必定要理解這3步,這就是之後遞歸秒殺算法題的依據和思路。

但這麼說好像很空,咱們來以題目做爲例子,看看怎麼套這個模版,相信3道題下來,你就能慢慢理解這個模版。以後再解這種套路遞歸題都能直接秒了。

6.刪除排序鏈表中的重複元素

給定一個排序鏈表,刪除全部重複的元素,使得每一個元素只出現一次。

示例 1:

輸入: 1->1->2
輸出: 1->2
示例 2:

輸入: 1->1->2->3->3
輸出: 1->2->3

代碼

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
public ListNode deleteDuplicates(ListNode head) {
    if(head == null || head.next ==null )
    {
        return head;
    }
    head.next = deleteDuplicates(head.next);
    if(head.val == head.next.val) 
    {
        return head.next;
    }
    else {
        return head;
    }
    
}
}

解析

遞歸套路解決鏈表問題:

  1. 找終止條件:當head指向鏈表只剩一個元素的時候,天然是不可能重複的,所以return
  2. 想一想應該返回什麼值:應該返回的天然是已經去重的鏈表的頭節點
  3. 每一步要作什麼:宏觀上考慮,此時head.next已經指向一個去重的鏈表了,而根據第二步,我應該返回一個去重的鏈表的頭節點。所以這一步應該作的是判斷當前的head和head.next是否相等,若是相等則說明重了,返回head.next,不然返回head

7.刪除鏈表元素

題目

刪除鏈表中等於給定值 val 的全部節點。

示例:

輸入: 1->2->6->3->4->5->6, val = 6
輸出: 1->2->3->4->5

代碼

/**
     * Definition for singly-linked list.
     * public class ListNode {
     *     int val;
     *     ListNode next;
     *     ListNode(int x) { val = x; }
     * }
     */
    class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if(head == null) return head;
        head.next=removeElements(head.next,val);
        if(head.val ==val){
            return head.next;
        }
        else return head;
    }
    }

解析:暫無

8.反轉鏈表

題目

反轉一個單鏈表。

示例:

輸入: 1->2->3->4->5->NULL
輸出: 5->4->3->2->1->NULL

代碼:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/

class Solution {
 
public ListNode reverseList(ListNode head) {

    if(head == null || head.next == null)
    {
        
        return head;
    }
    
    ListNode p =reverseList(head.next);
    head.next.next=head;
    head.next =null;
    return p;
}
}

9.二叉樹深度

給定一個二叉樹,找出其最大深度。

二叉樹的深度爲根節點到最遠葉子節點的最長路徑上的節點數。

說明: 葉子節點是指沒有子節點的節點。

示例:
給定二叉樹 [3,9,20,null,null,15,7],
tupian------------------

返回它的最大深度 3 。

代碼

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
public int maxDepth(TreeNode root) {
    if(root ==null)
    {
        return 0;
    }
    
    int Deepleft = maxDepth(root.left);
    int DeepRight = maxDepth(root.right);
    return Math.max(Deepleft,DeepRight)+1;
    
}
}

解析

求二叉樹的最大深度,那麼直接套遞歸解題三部曲模版:

  1. 找終止條件。 什麼狀況下遞歸結束?固然是樹爲空的時候,此時樹的深度爲0,遞歸就結束了。

  2. 找返回值。 應該返回什麼?題目求的是樹的最大深度,咱們須要從每一級獲得的信息天然是當前這一級對應的樹的最大深度,所以咱們的返回值應該是當前樹的最大深度,這一步能夠結合第三步來看。

  3. 本級遞歸應該作什麼。 首先,仍是強調要走出以前的思惟誤區,遞歸後咱們眼裏的樹必定是這個樣子的,看下圖。此時就三個節點:root、root.left、root.right,其中根據第二步,root.left和root.right分別記錄的是root的左右子樹的最大深度。那麼本級遞歸應該作什麼就很明確了,天然就是在root的左右子樹中選擇較大的一個,再加上1就是以root爲根的子樹的最大深度了,而後再返回這個深度便可。

10.兩兩相交鏈表的節點

給定一個鏈表,兩兩交換其中相鄰的節點,並返回交換後的鏈表。

你不能只是單純的改變節點內部的值,而是須要實際的進行節點交換。

示例:

給定 1->2->3->4, 你應該返回 2->1->4->3.

代碼

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
public ListNode swapPairs(ListNode head) {
    if(head == null || head.next ==null)
    {
        return head;
    }
    ListNode next =head.next;
    head.next = swapPairs(next.next);
    next .next =head;
    return next;
}
}

解析

直接上三部曲模版:

  1. 找終止條件。 什麼狀況下遞歸終止?沒得交換的時候,遞歸就終止了唄。所以當鏈表只剩一個節點或者沒有節點的時候,天然遞歸就終止了。

  2. 找返回值。 咱們但願向上一級遞歸返回什麼信息?因爲咱們的目的是兩兩交換鏈表中相鄰的節點,所以天然但願交換給上一級遞歸的是已經完成交換處理,即已經處理好的鏈表。

  3. 本級遞歸應該作什麼。 結合第二步,看下圖!因爲只考慮本級遞歸,因此這個鏈表在咱們眼裏其實也就三個節點:head、head.next、已處理完的鏈表部分。而本級遞歸的任務也就是交換這3個節點中的前兩個節點,就很easy了。

11.平衡二叉樹

給定一個二叉樹,判斷它是不是高度平衡的二叉樹。

本題中,一棵高度平衡二叉樹定義爲:

一個二叉樹每一個節點 的左右兩個子樹的高度差的絕對值不超過1。

示例 1:

給定二叉樹 [3,9,20,null,null,15,7]

代碼

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */

class Solution {
private class ReturnNode{
//這個ReturnNode是參考我描述的遞歸套路的第二步:思考返回值是什麼
//一棵樹是BST等價於它的左、右倆子樹都是BST且倆子樹高度差不超過1
//所以我認爲返回值應該包含當前樹是不是BST和當前樹的高度這兩個信息
    boolean isB;
    int depth;
    public ReturnNode(int depth,boolean  isB){
        this.isB = isB;
        this.depth = depth;
    }
}
//主函數
public boolean isBalanced(TreeNode root) {
    return isBST(root).isB;
    
}
//參考遞歸套路的第三部:描述單次執行過程是什麼樣的
//這裏的單次執行過程具體以下:
//是否終止?->沒終止的話,判斷是否知足不平衡的三個條件->返回值

public ReturnNode isBST(TreeNode root){
    if(root == null){
        return new ReturnNode(0,true);
    }
    //不平衡的狀況有3種:左樹不平衡、右樹不平衡、左樹和右樹差的絕對值大於1
    ReturnNode left = isBST(root.left);
    ReturnNode right = isBST(root.right);
    if(left.isB == false || right.isB == false){
        return new ReturnNode(0,false);
    }
    if(Math.abs(left.depth - right.depth)> 1){
        return new ReturnNode(0,false);
    }
    //不知足上面的三種狀況,說明平衡了,樹的深度爲左右子樹的最大深度+1;
    return new ReturnNode(Math.max(left.depth,right.depth)+1,true);
}
}

解析

  1. 找終止條件。 什麼狀況下遞歸應該終止?天然是子樹爲空的時候,空樹天然是平衡二叉樹了。

  2. 應該返回什麼信息:

爲何我說這個題是集合了模版精髓?正是由於此題的返回值。要知道咱們搞這麼多花裏胡哨的,都是爲了能寫出正確的遞歸函數,所以在解這個題的時候,咱們就須要思考,咱們到底但願返回什麼值?

何爲平衡二叉樹?平衡二叉樹即左右兩棵子樹高度差不大於1的二叉樹。而對於一顆樹,它是一個平衡二叉樹須要知足三個條件:它的左子樹是平衡二叉樹,它的右子樹是平衡二叉樹,它的左右子樹的高度差不大於1。換句話說:若是它的左子樹或右子樹不是平衡二叉樹,或者它的左右子樹高度差大於1,那麼它就不是平衡二叉樹。

而在咱們眼裏,這顆二叉樹就3個節點:root、left、right。那麼咱們應該返回什麼呢?若是返回一個當前樹是不是平衡二叉樹的boolean類型的值,那麼我只知道left和right這兩棵樹是不是平衡二叉樹,沒法得出left和right的高度差是否不大於1,天然也就沒法得出root這棵樹是不是平衡二叉樹了。而若是我返回的是一個平衡二叉樹的高度的int類型的值,那麼我就只知道兩棵樹的高度,但沒法知道這兩棵樹是否是平衡二叉樹,天然也就無法判斷root這棵樹是否是平衡二叉樹了。

所以,這裏咱們返回的信息應該是既包含子樹的深度的int類型的值,又包含子樹是不是平衡二叉樹的boolean類型的值。能夠單獨定義一個ReturnNode類,以下:

class ReturnNode{
  boolean isB;
  int depth;
  //構造方法
  public ReturnNode(boolean isB, int depth){
    this.isB = isB;
    this.depth = depth;
  }
}
  1. 本級遞歸應該作什麼。 知道了第二步的返回值後,這一步就很簡單了。目前樹有三個節點:root,left,right。咱們首先判斷left子樹和right子樹是不是平衡二叉樹,若是不是則直接返回false。再判斷兩樹高度差是否不大於1,若是大於1也直接返回false。不然說明以root爲節點的子樹是平衡二叉樹,那麼就返回true和它的高度。
相關文章
相關標籤/搜索