1. 程式人生 > 實用技巧 >資料結構和演算法-常見演算法題

資料結構和演算法-常見演算法題

參考:

連結串列反轉:

https://blog.csdn.net/weixin_40807247/article/details/91435275

https://blog.csdn.net/qq_33958946/article/details/84326965

https://blog.csdn.net/qq_37117521/article/details/80808631

連結串列反轉

理解單鏈表的反轉(java實現)

遍歷法
遍歷法就是在連結串列遍歷的過程中將指標順序置換

//如何實現連結串列的反轉
    public static Node ReverseIteratively(Node head){
        Node pre = null;
        Node next = null;
        while (head != null) {
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }

依舊是1->2->3->4
準備兩個空結點 pre用來儲存先前結點、next用來做臨時變數
在頭結點node遍歷的時候此時為1結點
next = 1結點.next(2結點)
1結點.next=pre(null)
pre = 1結點
node = 2結點
進行下一次迴圈node=2結點
next = 2結點.next(3結點)
2結點.next=pre(1結點)=>即完成2->1
pre = 2結點
node = 3結點
進行迴圈…

遞迴法
總體來說,遞迴法是從最後一個Node開始,在彈棧的過程中將指標順序置換的。

遞迴實質上就是系統幫你壓棧的過程,系統在壓棧的時候會保留現場。
我們來看是怎樣的一個遞迴過程:1->2->3->4
程式到達Node newHead = reverse(head.next);時進入遞迴
我們假設此時遞迴到了3結點,此時head=3結點,temp=3結點.next(實際上是4結點)
執行Node newHead = reverse(head.next);傳入的head.next是4結點,返回的newHead是4結點。
接下來就是彈棧過程了
程式繼續執行 temp.next = head就相當於4->3
head.next = null 即把3結點指向4結點的指標斷掉。
返回新連結串列的頭結點newHead

//遞迴實質上就是系統幫你壓棧的過程,系統在壓棧的時候會保留現場
    public Node reverse(Node head) {
        //遞迴
        if (null == head ||null  == head.next)
            return head;
        Node temp = head.next;
        Node newHead = reverse(head.next);
        temp.next = head;
        head.next = null;
        return newHead;
    }
@Test
    public void testCase() {
        LinkedList list = new LinkedList();
        System.out.println("測試頭插法");
        list.addNodeHead(4);
        list.addNodeHead(3);
        list.display();
        list.addNodeHead(2);
        list.addNodeHead(1);
        /*list.display();
        list.addNodeTail(4);
        list.display();
        System.out.println("測試某個位置刪除法");
        list.deleteNode(3);
        list.display();
        System.out.println("測試刪除重複資料前");
        list.addNodeHead(1);
        list.display();
        System.out.println("測試刪除重複資料");
        list.deleteDuplecate1(list.head);
        list.display();
        System.out.println("測試刪除重複資料1");
        list.addNodeIndex(2,1);
        list.display();
        list.deleteDuplecate(list.head);*/
        list.display();
        System.out.println("連結串列的反轉");
        //Node node = LinkedList.ReverseIteratively(list.head);
        Node node1 = list.reverse(list.head);
        while (node1 != null){
            System.out.println(node1.data);
            node1 = node1.next;
        }
    }

java經典面試題:單鏈表反轉問題詳解(含遞迴法)

java經典面試題:單鏈表反轉問題,有兩種方法,一種為迴圈遍歷法,一種遞迴法。
1、迴圈遍歷法

  首先設定三個節點,把當前節點的下一節點指向它前面的節點,此時你會發現指標鏈會斷,所以要先把它後面一個節點用nextNode儲存下來,之後把節點向後移動遍歷即可。
  
程式碼如下:

//定義單鏈表節點類
public class ListNode {
		int value;
		ListNode next;
		
		public ListNode() {
			this.value=0;
			this.next=null;
		}
		public ListNode(int value) {
			this.value=value;
		}
}
	
//定義連結串列反轉方法
public ListNode reverseList(ListNode root) {
		if(root==null)return root;
		ListNode newhead=null;  //反轉後的新連結串列頭結點
		ListNode preNode = null; //前一節點
		ListNode nextNode=null; //下一節點
		ListNode curNode=root; //當前節點
		while(curNode.next!=null) {
			nextNode=curNode.next; //儲存當前節點的下一節點
		    curNode.next=preNode; //使當前節點指向前一節點
		    preNode=curNode;  //把前一節點往後移
		    curNode=nextNode; //把當前節點往後移
		}
		newhead=curNode; 
		return newhead;
	}

2、遞迴法
如果當前節點或者當前節點的下一節點為空,直接返回該節點;
否則使該節點的後一節點指向該節點,以此遞迴。

public BinaryNode reverseList2(BinaryNode root) {
		if(root==null||root.next==null)return root;
		BinaryNode nextNode=root.next; //得到當前節點的下一節點
		root.next=null; //打斷當前指標鏈
		BinaryNode re=reverseList2(nextNode); //每次遞迴下一節點進行反轉
		nextNode.next=root; //反轉指標域
		return re;
	}

使用遞迴反轉連結串列

連結串列反轉:

(圖1)

把問題規模減小,並且減小的量為1

(圖2)

假設我們的程式能夠正常的反轉:則反轉後為

(圖3)

反轉後,1元素並沒有任何操作,所以反轉1的next仍然指向2,

(圖4)

假設2開頭的連結串列已經反轉成功,接下來只要將2的next指向1,

(圖5)

而1的next指向null即可。

(圖6)

看似複雜的問題,把如此多的指標反過來指,其實只要將兩個節點反過來即可。

程式碼如下:

package com.sise.recursion;
 
import java.util.ArrayList;
import java.util.Arrays;
 
public class LinkedListReverser {
 
    /*
     * 反轉一個連結串列
     * head為待反轉連結串列的頭結點
     * @return 反轉後的連結串列頭結點,當然該連結串列也是以null結尾
     */
    public Node reverseLinkedList(Node head) {
        
        /*
         *特殊處理
         */
//        //空連結串列,sise==0
//        if(head==null){
//            return null;
//        }
//        //只有一個結點的時候,size==1
//        if(head.getNext()==null){
//            return head;
//        }
        
        //把上兩個特殊情況合起來
        if(head==null||head.getNext()==null){
            return head;
        }
        
        
        
        //假設函式能夠反轉連結串列,返回頭結點
        //---------------此處head有可能是null,head。getNext()有可能是null-----------
        Node newHead=reverseLinkedList(head.getNext());
        //此時如圖4狀態,1的getNext就是第二個結點2,
        //把第二結點2的next指向head則實現把2的指標指向1,如圖5
        //------------此處的getNext()有可能是null------
        head.getNext().setNext(head);
        head.setNext(null);//最後指向null,如圖6
        return newHead;
    }
    public static void main(String[] args) {
        LinkedListCreator creator=new LinkedListCreator();
        LinkedListReverser reverser=new LinkedListReverser();
        
        
        ArrayList arrayList=new ArrayList<>();
        
        Node.printLinkedList(
                reverser.reverseLinkedList(creator.createLinkedList(arrayList))
                );
        Node.printLinkedList(
                reverser.reverseLinkedList(creator.createLinkedList(Arrays.asList(1)))
                );
        Node.printLinkedList(
                reverser.reverseLinkedList(creator.createLinkedList(Arrays.asList(1,2,3,4,5)))
                );
 
    }
 
}

package com.sise.recursion;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
 
 
public class LinkedListCreator {
 
    /*
     * 建立一個連結串列
     * @return 連結串列的頭結點,返回連結串列的最後一個結點的getNext()==null.
     */
    public Node createLinkedList(List<Integer> data){
        //假設傳入空的List
        if(data.isEmpty()){
            return null;
        }
        
        //取出傳入資料的第一個結點
        Node firstNode=new Node(data.get(0));
        //取走一個元素後,從第二個元素建立一個連結串列,
        //因為返回的是Node,所以用Node來接收
        //假設傳入來的List有一個元素,則走到這裡時sublist傳入的兩個引數相等
        //但是sublist函式的定義可以看到fromIndex==toIndex時,返回null
        /*
         *  <tt>fromIndex</tt> and <tt>toIndex</tt> are equal, the returned list is
         */
        //與我們期望返回值一致
//        Node headOfSublistNode=
//                createLinkedList(data.subList(1, data.size()));
//        //第一個結點的next指向規模縮小的連結串列返回來的頭結點
//        firstNode.setNext(headOfSublistNode);
        //上面兩行程式碼清理成如下程式碼
        firstNode.setNext(createLinkedList(data.subList(1, data.size())));
        return firstNode;
        
    }
    public static void main(String[] args) {
        LinkedListCreator creator=new LinkedListCreator();
        
        ArrayList arrayList=new ArrayList<>();
        
        Node.printLinkedList(
                creator.createLinkedList(arrayList)
                );
        Node.printLinkedList(
                creator.createLinkedList(Arrays.asList(1))
                );
        Node.printLinkedList(
        creator.createLinkedList(Arrays.asList(1,2,3,4,5))
    );
    }
 
}

package com.sise.recursion;
 
public class Node {
    
    private final int value;//使用者定義之後就不能修改
    private Node next;
    
    public Node(int value){
        this.value=value;
        this.next=null;//這樣建立出來的結點都是單點Node
    }
    
    public Node getNext() {
        return next;
    }
    public void setNext(Node next) {
        this.next = next;
    }
    public int getValue() {
        return value;
    }
    //列印函式
    public static void printLinkedList(Node head) {
        while(head!=null){
            System.out.print(head.getValue());;
            System.out.print(" ");
            head=head.getNext();
        }
        System.out.println();
    }
    
}

執行結果: