劍指offer在線編程(08-14)【9】

 Date: 2019-08-14

1.  鏈表中環的入口結點   (考察知識點:鏈表)

題目描述

給一個鏈表,若其中包含環,請找出該鏈表的環的入口結點,否則,輸出null。

分析: 第一種不考慮時複雜度時,可以直接利用一個列表對環形鏈表進行遍歷的元素存儲,這樣的時間複雜是O(N).在存儲時,如果該元素已經在list中了,則表明到環形鏈表的入口了。否則繼續向後遍歷。

 

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def EntryNodeOfLoop(self, pHead):
        # write code here
        if not  pHead:
            return None
        p = pHead
        reslist = []
        while p:
            if p in reslist:
                return p
            else:
                reslist.append(p)
            p = p.next

第二種思路來自劍指offer的思想:

如果鏈表中 有n個結點,指針P1在鏈表上向前移動n步,然後兩個指針以相同的速度向前移動。當第二個指針指向環的入口結點時,第一個指針已經圍繞着環走了一圈又回到了入口結點。但是首先要得到環中結點的數目。計算數目的思想是:上面相遇的節點一定是在環中,因此可以從這個節點出發,一邊繼續向前移動一邊計數,下次再回到這個節點時,就可以得到環中的數目了。

具體:

  1. 第一步,找環中相匯點。分別用p1,p2指向鏈表頭部,p1每次走一步,p2每次走二步,直到p1==p2找到在環中的相匯點。
  2. 第二步,找環的入口。接上步,當p1==p2時,p2所經過節點數爲2x, p1所經過節點數爲x, 設環中有n個節點, p2比p1多走一圈有2x=n+x; n=x; 可以看出p1實際走了一個環的步數,再讓p2指向鏈表頭部,p1位置不變,p1,p2每次走一步直到p1==p2; 此時p1指向環的入口。

 

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def EntryNodeOfLoop(self, pHead):
        # write code here
        # 先決條件的判斷
        if not pHead or not pHead.next or not pHead.next.next:
            return None
        # 讓慢指針每次走一步,快指針每次走兩步,第一次相遇一定是在環中。相遇時:慢走x,則快走2x=x+n(塊其實多走了一個環的數目),因此慢指針一共走了n步
        low = pHead.next
        fast = pHead.next.next
        while low != fast:
            if not low or not fast:
                return None
            low = low.next
            fast = fast.next.next
        # 相遇時,慢指針已經走了環數步,則讓快指針從頭開始,兩指針每次都走一步,下次相遇必定在環的入口節點處。
        fast = pHead
        while low != fast:
            low = low.next
            fast = fast.next
        return fast

2. 刪除鏈表中重複的結點   (考察知識點:鏈表)

題目描述

在一個排序的鏈表中,存在重複的結點,請刪除該鏈表中重複的結點,重複的結點不保留,返回鏈表頭指針。 例如,鏈表1->2->3->3->4->4->5 處理後爲 1->2->5

第一種思路:非遞歸的進行刪除。

1. 首先添加一個頭節點,以方便碰到第一個,第二個節點就相同的情況

2.設置 pre ,nex指針, pre指針指向當前確定不重複的那個節點,而nex指針相當於工作指針,一直往後面搜索,,刪除與當前pre重複的指針,最後在當前循環下記得刪除pre指針!

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None
class Solution:
    def deleteDuplication(self, pHead):
        # write code here
        # 首先判斷先決條件
        if pHead == None or pHead.next == None:
            return pHead
        # 建立一個新的節點,便於後續進行迭代
        new_head = ListNode(-1)
        new_head.next = pHead
        pre = new_head  # 將新節點設置爲pre
        p = pHead       # 當前需要去判斷是否重複的節點,需要更新
        nex = None       # 初始化下一個節點
        while p != None and p.next != None:  # 當p非空,p下一節點也非空是,設置nex,否則直接返回結束
            nex = p.next
            if p.val == nex.val:  # 這一個if語句,先固定p,然後判斷nex和p的值,如果相等刪除nex,然後不斷往下尋找重複的nex,最後不滿足跳出while語句進行更新
                while nex != None and nex.val == p.val:
                    nex = nex.next
                pre.next = nex
                p = nex
            else:  # 如果p和nex的值不等,pre和p向下遍歷,進行判斷
                pre = p
                p = p.next
        return new_head.next  # 因爲new_head爲新增頭結點,所以返回的是其next節點

第二種思路:牛客上有人用的遞歸的思想:(時間複雜度要高一些,畢竟遞歸)

class Solution:
    def deleteDuplication(self, pHead):
        # write code here
        # 先決條件
        if pHead is None or pHead.next is None:
            return pHead
        head1 = pHead.next
        if head1.val != pHead.val: # 如果兩節點值不相等,則往下遞歸調用判斷
            pHead.next = self.deleteDuplication(pHead.next)
        else:
            while pHead.val == head1.val and head1.next is not None: # 否則,如果相等且head1.next非空,刪除head1,並while循環
                head1 = head1.next
            if head1.val != pHead.val:  # 刪除節點終止後,節點不相等時,遞歸判斷head1的後續節點情況
                pHead = self.deleteDuplication(head1)
            else:
                return None
        return pHead

3.  二叉樹的下一個結點   (考察知識點:樹)

題目描述

給定一個二叉樹和其中的一個結點,請找出中序遍歷順序的下一個結點並且返回。注意,樹中的結點不僅包含左右子結點,同時包含指向父結點的指針。

  
思路:

(1) 若該節點存在右子樹:則下一個節點爲右子樹最左子節點(如圖節點 B )

(2) 若該節點不存在右子樹:這時分兩種情況:

        2.1 該節點爲父節點的左子節點,則下一個節點爲其父節點(如圖節點 D )

        2.2 該節點爲父節點的右子節點,則沿着父節點向上遍歷,直到找到一個節點的父節點的左子節點爲該節點,則該節點的父節點下一個節點(如圖節點 I ,沿着父節點一直向上查找找到 B ( B 爲其父節點的左子節點),則 B 的父節點 A 爲下一個節點)。可以綜合考慮第二種情況

# -*- coding:utf-8 -*-
# class TreeLinkNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
#         self.next = None
class Solution:
    def GetNext(self, pNode):
    # write code here
        if pNode.right: #有右子樹
            p=pNode.right
            while p.left:
                p=p.left
            return p
        while pNode.next:   #無右子樹,則找第一個當前節點是父節點左孩子的節點
             if(pNode.next.left==pNode):  # 如果第一次if就成立了,就表明其是父節點的左孩子,返回父節點即可
                return pNode.next
             pNode = pNode.next   #否則,沿着父節點向上遍歷        
        return  None  #到了根節點仍沒找到,則返回空

4.  對稱的二叉樹 (考察知識點:樹)

題目描述

請實現一個函數,用來判斷一顆二叉樹是不是對稱的。注意,如果一個二叉樹同此二叉樹的鏡像是同樣的,定義其爲對稱的。

分析:有很對不同的處理細節,有點直接採用遞歸的思想,有的是將樹賦值一棵,然後左右子樹進行相等的遍歷。

我在這裏採用 的是:直接往下走,進行逐步比較。首先需要判斷四種情況:

樹根爲空,True;

左子樹空,右子樹不爲空,False

左子樹不爲空,右子樹空,False

左右子樹均爲空,True

最後遞歸遍歷左右子樹均不爲空的情況,進行值的比較!

# -*- coding:utf-8 -*-  我的代碼,但沒有通過,思路和後面一版本的代碼相同,疑惑。難道是子函數的使用?
# class TreeNode: 
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def isSymmetrical(self, pRoot):
        # write code here
        if not pRoot:
            return True
        if not pRoot.left and not pRoot.right:
            return True
        if pRoot.left and not pRoot.right:
            return False
        if not pRoot.left and pRoot.right:
            return False
        while pRoot.left and pRoot.right:
            if pRoot.left.val == pRoot.right.val:
                return self.isSymmetrical(pRoot.left) and self.isSymmetrical(pRoot.right) 
            return False
        return True


# -*- coding:utf-8 -*-  # 通過的代碼
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def isSymmetrical(self, pRoot):
        # write code here
        def is_same(p1,p2):
            if not p1 and not p2:    # 左右子樹均爲空,True
                return True
            if (p1 and p2) and p1.val==p2.val:   # 左右子樹均不爲空,且值相等時
                return is_same(p1.left,p2.right) and is_same(p1.right,p2.left)
            return False
        if not pRoot:  # 樹根爲空,True
            return True
        if pRoot.left and not pRoot.right:  # 左子樹不空,右子樹爲空,False
            return False
        if not pRoot.left and pRoot.right:   # 右子樹不空,左子樹爲空,False
            return False
        return is_same(pRoot.left,pRoot.right) # 否則遞歸調用

5.  按之字形順序打印二叉樹   (考察知識點:棧+樹)

題目描述

請實現一個函數按照之字形打印二叉樹,即第一行按照從左到右的順序打印,第二層按照從右至左的順序打印,第三行按照從左到右的順序打印,其他行以此類推。

分析:

* 大家的實現很多都是將每層的數據存進ArrayList中,偶數層時進行reverse操作。但是在海量數據時,這樣效率太低了。

 如果面試,算法考的就是之字形打印二叉樹,用reverse,會被鄙視,面試官會說海量數據時效率根本就不行。

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def Print(self, pRoot):
        # write code here
        root=pRoot
        if not root:
            return []
        level=[root]
        result=[]
        lefttoright=False  # 標記符號,判定是從左往右False,還是從右往左True
        while level:
            curvalues=[] # 目前的值,一般是從左往右
            nextlevel=[]  # 該層的節點記錄,左節點+右節點
            for i in level:
                curvalues.append(i.val)
                if i.left:
                    nextlevel.append(i.left)
                if i.right:
                    nextlevel.append(i.right)
            if lefttoright:   
                curvalues.reverse() # 第一層是從左往右False,下一層時從右往左True,交替
            if curvalues:
                result.append(curvalues)  # 將正確的順序後的該層值進行放入最後的result中
            level=nextlevel #  循環中使用的level的更新
            lefttoright=not lefttoright  # 標記的變化
        return result

其他方法: 沒有利用reverse,進行先存後改變順序,而是一步到位:

'''解法:利用一個標誌變量flag來標記從左往右還是從右往走
如果從左往右,那就從頭到尾遍歷當前層的節點current_nodes,然後將左孩子和右孩子分別append到一個list new_nodes中
如果從右往前,那就從尾到頭遍歷當前層的節點current_nodes,然後將右孩子和左孩子分別insert到一個list new_nodes中
這樣得到的new_nodes還是從左到右有序的'''
# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:   
    def Print(self, pRoot):
        # write code here
        if pRoot == None:
            return []
        falg = 0  # 0表示從左往右,1表示從右往左
        node_list = [[pRoot]]
        result = []
        while node_list:
            current_nodes = node_list[0] # 當前層的節點            
            node_list = node_list[1:]
            new_nodes = [] # 下一層的節點,按照從左往右的順序存儲            
            res = [] # 當前層得到的輸出
            while len(current_nodes) > 0:
                # 從左往右
                if falg == 0:
                    res.append(current_nodes[0].val)
                    if current_nodes[0].left != None:
                        new_nodes.append(current_nodes[0].left)
                    if current_nodes[0].right != None:
                        new_nodes.append(current_nodes[0].right)
                    current_nodes = current_nodes[1:]
                # 從右往左
                else:
                    res.append(current_nodes[-1].val)
                    if current_nodes[-1].right != None:
                        new_nodes.insert(0, current_nodes[-1].right)
                    if current_nodes[-1].left != None:
                        new_nodes.insert(0, current_nodes[-1].left)
                    current_nodes = current_nodes[:-1]
            result.append(res)
            falg = 1 - falg
            if new_nodes:
                node_list.append(new_nodes)
        return result

6. 把二叉樹打印成多行   (考察知識點:樹+隊列)

題目描述

從上到下按層打印二叉樹,同一層結點從左至右輸出。每一層輸出一行。

分析:此題比上題還要簡單些,第一種實現如下:直接從上到下分別存儲從左到右的節點值和從左到右每個節點的左右孩子的節點。

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:   
    # 返回二維列表[[1,2],[4,5]]
    def Print(self, pRoot):
        # write code here
        if pRoot == None:
            return []
        node_list = [[pRoot]]
        result = [] # 每一層的節點的值從左到右寫成一個list
        while node_list:
            current_nodes = node_list[0] # 每次取第一個值爲當層從左到右的節點順序,當前層的節點            
            node_list = node_list[1:]   # 取完之後,node_list會進行第一個值的刪除更新
            new_nodes = [] # 下一層的節點,按照從左往右的順序存儲            
            res = [] # 當前層節點的值按照從左到右的順序輸出
            while len(current_nodes) > 0:
                res.append(current_nodes[0].val)  # 進行一個節點的值的存儲res中
                if current_nodes[0].left != None:  #節點的左右孩子節點按照順序存儲current_nodes中
                    new_nodes.append(current_nodes[0].left)
                if current_nodes[0].right != None:
                    new_nodes.append(current_nodes[0].right)
                current_nodes = current_nodes[1:]  # 當前結點的刪除更新
            result.append(res)
            if new_nodes:
                node_list.append(new_nodes)
        return result

一種更簡潔的實現方式:初始化兩個list,res用作存放每一行節點值row的最大list。tmp用於存放不斷更新的當前節點。

1. 首先進行每一行節點值row的更新和存儲,並將其保存到 res中

2. 然後進行當層節點tmp的更新:不斷刪除該層的節點(從左到右),同時獲取其左右孩子的節點到tmp中。[注意:這裏只需要進行該層節點數len(tmp)次循環:刪除和更新]

# -*- coding:utf-8 -*-  # 個人支持這種,簡單易懂
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:   
    # 返回二維列表[[1,2],[4,5]]
    def Print(self, pRoot):
        # write code here
        if not pRoot: # 根節點爲空,則最後結果也爲空
            return []
        res = []  # 用於存放每一層節點值row的大list
        tmp = [pRoot]  # 用於存放當層的節點,從左到右的順序
        while tmp:
            row = [] # 當層節點值的存儲,形成小list
            for i in tmp:
                row.append(i.val)
            res.append(row)  # 存儲完該層的節點值後,對存儲該層節點tmp進行刪除更新。
            for i in range(len(tmp)):
                t = tmp.pop(0)  # 從左到右刪除節點,同時存儲該節點的左右孩子節點
                if t.left:
                    tmp.append(t.left)
                if t.right:
                    tmp.append(t.right)
        return res
                    

7.  序列化二叉樹   (考查知識點:隊列 + 樹)  (Diff  多看!!!)

題目描述

請實現兩個函數,分別用來序列化和反序列化二叉樹

二叉樹的序列化是指:把一棵二叉樹按照某種遍歷方式的結果以某種格式保存爲字符串,從而使得內存中建立起來的二叉樹可以持久保存。序列化可以基於先序、中序、後序、層序的二叉樹遍歷方式來進行修改,序列化的結果是一個字符串,序列化時通過 某種符號表示空節點(#),以 ! 表示一個結點值的結束(value!)。


二叉樹的反序列化是指:根據某種遍歷順序得到的序列化字符串結果str,重構二叉樹。

分析:首先解析題目:序列化實質上就是按照某一種遍歷的方式(這裏採用先序遍歷的方式),不斷遍歷樹的值得到一個字符串,注意空節點#和','進行分割!;反序列化實質上就是二叉樹的重構,之前有一題是二叉樹的重構,在哪裏我們用到了先序遍歷和中序遍歷進行樹的重建,但是這裏我們直接採用先序遍歷的字符串結果就可以進行樹的重建。

1) 序列化:初始化一個空列表,首先判斷函數運行的先決條件;如果不爲空,先增添當前節點(父節點)的值(注意轉成str形式),然後遞歸調用該父節點的左孩子,如果存在則進行值的增添(在調用過程中,可能會調用的左子樹的左孩子及右孩子);然後再遞歸調用該父節點的右子樹進行元素的增添。

2)反序列化:會在類下先初始化一個全局成員變量flag來實現記錄目前應該獲取s中的那一個元素值,每次遞歸調用一次反序列化函數,flag+1,根節點是0,知道len(s)-1 結束。如果超出了len(s)的長度,則表明序列中的值已經重建二叉樹完成;否則當前值非#時,先在root上增加節點,然後序列化結果中遞歸增加左節點,然後再是右節點!

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def __init__(self):
        self.flag = -1
        
    def Serialize(self, root):
        # write code here
        res = []
        if not root:  # 如果當前遞歸調用的根節點爲空,則返回#
            return '#'
        res.append(str(root.val))  # 否則非空時,先增加根節點的值
        l = self.Serialize(root.left)   # 遞歸調用當前節點的左子樹,會一直往左下調用直到到達葉節點,才往回增加元素
        res.append(l)
        r = self.Serialize(root.right)   # 遞歸調用當前節點的右子樹,如果右子樹有左節點,先遞歸左節點,然後右節點。
        res.append(r)
        return ','.join(res)

    def Deserialize(self, s):
        # write code here
        self.flag += 1  # 用於記錄每次取s中哪一點,每遞歸調用一次,+1,從根節點爲0到len(s)-1
        l = s.split(',')  # 將s進行分割
        if (self.flag >= len(s)):  #  如果當前累計的Flag已經到達了最大深度,則直接返回,不再繼續增加節點
            return None
        root = None
        if (l[self.flag] != '#'):  # 判斷l中當前標記的元素是否爲#,如果不是增加節點,然後先不斷往左邊增加節點值
            root = TreeNode(int(l[self.flag]))
            root.left = self.Deserialize(s)  # 當左邊不到增加節點,直到第一次遇到#,則當前循環不增加節點,表明左邊已經到底
            root.right = self.Deserialize(s)  # 從而開始往右邊增加節點(注意在往左邊遞歸的時候,會遇到左節點有右孩子的情況,仍然是左邊走完,再回到右邊)
        return root
# 更簡潔的序列化版本,一樣的遞歸調用思想
"""
 def Serialize(self, root):
        # write code here
        if not root:
            return '#,'
        return str(root.val)+','+self.Serialize(root.left)+self.Serialize(root.right)
"""

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章