Leetcode-Python-剑指offer做题整理【六月上】

6-1-剑指 Offer 14- I. 剪绳子

6-2-剑指 Offer 07. 重建二叉树

考点:递归

分析:这是一道明显的递归题目,前序遍历 可以将树划分为  根 左 右 中序遍历将树划分为 左 根 右,因此可以先从前序遍历中找出根节点,然后再利用根节点将中序遍历划分为 左子树和右子树,然后继续传递左子树和右子树在根节点的两侧。

难点:如何能够实现中序遍历简单有效划分。并且前序遍历也要用(得靠前序遍历去划分)

解题:来自题解,不直接利用中序遍历而是转向其index,将每个中序遍历对应的index找到。以便于前序遍历找根节点在中序遍历中的位子。以 left,right来表示一个子树,继续向下传递。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None    

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        def buildTree(root,left,right):
            if len(left) == 0 or len(right) == 0:
                return 
            root = TreeNode(preorder[0]) # 建立根节点 
            ### 将左右子树分开 
            index =  inorder.index(preorder[0])
            left_inorder = inorder[:index]
            right_inorder = inorder[index:]
            ### 将前序遍历左右分开
            left_preorder = []
            right_preorder = []
            for num in preorder:
                if num in left_inorder :
                    left_preorder.append(num)
            for num in preorder:
                if num in right_inorder :
                    right_preorder.append(num)
            ###  建立左边,建立右边
            root.left = buildTree(root,left_preorder,left_inorder)
            root.right  = buildTree(root,right_preorder,right_inorder)
            return root 
        buildTree(TreeNode(0),preorder,inorder) 
 
       
        ############### 利用索引的解决方法 ###########
        self.dic,self.pro = {},preorder
        for i in range(len(inorder)):
            self.dic[inorder[i]] = i ###bulid index
        return build(0,0,len(inorder)-1) ### initial 
        

    def build(self,root_index,left,right):
        if left > right:return # index is exceed 
        root = TreeNode[self.pro[root_index]]# 根节点 index
        index = self.dic[self.pro[root_index]] # 中序遍历 根节点 的 index

        root.left = build(root_index+1,left,index-1) 
        # left,right 都指向中序遍历节点,root指向前序遍历
        root.right =build(root_index+index-left+1,index+1,right)
        # 根节点是 此 节点+下一层左子树长度
        # [1,2,3,4,5] [1] [2,3,4] [5] 5 is the next root 
        #  root_index + (index-1-left+1) + 1 左子树右边减去左边 index(4)-index(2)+1 
        # 额外的加1 是为了跳出左子树 到左子树长度后的第一个节点  
        return root 

剑指 Offer 42. 连续子数组的最大和

考点:DP

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:        
        sum_ = -float('inf')
        for i in range(0,len(nums)): ##### O(N^2) 超出时间限制 
            for j in range(i,len(nums)):
                if sum(nums[i:j+1]) > sum_:
                    sum_ = sum(nums[i:j+1])
        return sum_ 

这个题不同于以往的DP题目的点在于每进行一次迭代操作,要判断当前的dp是否是最大值。所以比以往的多了一个判断过程作为干扰。

DP问题看的都是i状态下和前一个状态下的关系。那么这道题,dp[i] 应该是前i项之和,那么dp[i-1]就应该是前i-1项之和,因为它求解的是 最大值。那么就要看

dp[i-1]是否大于0,如果大于0,那么加上nums[i]是比原来大的。否则直接截断前面(体现剪枝),从nums[i]从新开始。前面是负数那么肯定不会是增大的只能是减小。分支条件出来了,那么整个方程就容易了。

        if len(nums) == 0:return 0
        if len(nums) == 1 : return nums[0]
        dp = [0 for i in range(2)]
        dp[0] = nums[0]
        max_ = dp[0]
        for i in range(1,len(nums)):
            if dp[0] > 0:
                dp[1] = dp[0] + nums[i]
            else:
                dp[1] = nums[i] ##体现截断点
            dp[0] = dp[1]
            if dp[1] > max_: #### 多了一个判断 
                max_  = dp[1]
        return max_

6-3-剑指 Offer 31. 栈的压入、弹出序列

直接模拟栈的入栈和出栈,然后判断出栈的元素是否和栈的出栈相同,相同继续,不同直接返回fasle。

时间复杂度O(N^2)  space is O(N) ??是不是O(N^2)

class Solution:
    def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
        push = [] 
        ###### 用双端队列 代替pushed 
        while(popped):    
            a = popped.pop(0) #输出第一个需要pop的元素 ####
                ##
            if a not in push:
                while(pushed):
                    b = pushed.pop(0) ##
                    if b == a:
                        break 
                    push.append(b)
            if a in push :
                b = push.pop(-1)
                if a!=b:
                    return False
                else:
                    continue
        return True

分析一下上述代码 主要耗时点在 pushed.pop(0)。利用双端队列尝试是否可以降低时间。->提高了一点点

看了题解之后发现不用这么复杂的写,直接从压入栈开始考虑。利用index 来省略 poped.pop()操作。【这一点我老是想不起来 OVO 】

        push = []
        index  = 0 
        for i in range(0,len(pushed)):
            push.append(pushed[i]) ## 入栈 
            while push and push[-1] == popped[index]: ##判断栈顶是否是出栈的, if is ture
                index += 1 # the next pop 
                push.pop()    # stack is pop(-1)
        return not push  ## if push's length  is 0 , is true

6-4 剑指 Offer 26. 树的子结构 【有问题】

6-5-剑指 Offer 61. 扑克牌中的顺子 【做完了】

6-6-剑指 Offer 49. 丑数 

考点:DP ,三指针问题,堆

分析:(1三指针问题)

因为n只含有质因子2,3,5,那么这个数只能被2,3,5整除。因此这个数字一定是2.3.5中某两个或者某一个的公倍数。并且这个数字可以用更小的丑数*(2,3,5)组成。比如18:2*9.

那么要找出第·N个这样数字,那么得从比它小的满足条件的数字开始,让1乘以(2,3,5)然后排列。再将第二数乘以2,3,5,再继续找。那么2,3,5,分别可以看成是三个指针。

A:{1*2,2*2,3*2....}

B:{1*3,2*3,3*3,4*3,.....}

C:{1*5,2*5,3*5,4*5,...}

因此是三个指针,从A1 B1 C1开始,如果这个最小那么指针向后移动一位。可能存在A和B中相同的元素,因此不仅仅是A要移动,B 和C 也要判断一下,做相应移动。【题目类似于有序多链表合并问题】上述分析来自此链接@sunrise。

class Solution:
    def nthUglyNumber(self, n: int) -> int:
        dp = [1 for i in range(n)] # 存储N个值 
        a,b,c =0,0,0 # index 
        for i in range(1,n):# dp[0]=1 固定
            dp[i] = min(dp[a]*2,dp[b]*3,dp[c]*5) # 从上一个符合要求中乘以2,3,5找最小的
            ## 注意是 上一个状态的末端 
            ## 更新a,b,c
            if dp[i] == dp[a]*2:a+=1
            if dp[i] == dp[b]*3:b+=1
            if dp[i] == dp[c]*5:c+=1
        
        return dp[n-1]

(2)DP问题

从上面的分析可知,Xn的值一定是 Xi*(2,3,5)得到的,又因为Xn应该是 Xi*(2,3,5)中的最小的那一个。Xi可能来自集合A,B,C中的某一个。所以有了如下的递推关系:

                                                        Xn = min(Xa*2,  Xb*3, Xc*5)

那么依旧是设置a,b,c分别指向Xa,  Xb, Xc。当最小值来自Xa时,更新a指针指向下一个。此时为了避免最小值可能出现相等情况,同时更新b,c的值。代码同上,理解思路不太相同,这个理解角度是从前面通过递推关系得到的。

(3)堆排序。相同题目:面试题 17.09. 第 k 个数

因为题目要求的是第N个,那么这个前面的所有的满足条件的可以做成一个小根堆。因为规定了N最大不超过1690,所以可以直接建立一个N=1690的小根堆,然后依次每次小根堆弹出最小的,直到符合第N个。

在python中有一个模块 heapq 的模块。基本函数操作可详见链接

下面代码来自提交记录中。我加了一下注释。

class Ugly:
    def __init__(self):
        #seen = {1, } # 集合 去重作用 
        hp = [] # 小根堆的存储  
        heapq.heappush(hp, 1) # 向堆中添加第一个元素,以list为基础 
        self.nums = nums = [] # 存储从小到大的每个值 
        for _ in range(1690): # 创建一个1690个元素的小根堆 
            cur_ugly = heapq.heappop(hp) 
            self.nums.append(cur_ugly) # 提取最小值(pop是删除)
            for i in [2, 3, 5]: # 添加下一个值
                new = cur_ugly * i
                if new not in hp: ## 更改了一下 原来是 seen,seen没什么必要添加 浪费空间  
                     ## 提取的是最小的,那么*2,,*3,*5也应该在heap中,按照是否出现添加到heap中 
                    #seen.add(new)  ##集合中添加新值
                    heapq.heappush(hp, new) ## 向堆中加入新元素
                
class Solution:
    u = Ugly()
    def nthUglyNumber(self, n: int) -> int:
        return self.u.nums[n-1]

这样做的确很快,是因为class Ugly已经创建好了,但是堆排序是一个时间复杂度为O(NlogN)的算法,并没有比DP的方法快很多O(N),这个快完全是因为N较小,而且后面每次取值都是O(1)的操作。

6-7-剑指 Offer 34. 二叉树中和为某一值的路径  【做完了,整理在了二叉树中】

DFS+回溯

6-8-剑指 Offer 33. 二叉搜索树的后序遍历序列【做完了,整理在了二叉搜索树中 】

单调栈很厉害的一种思路。

剑指 Offer 37. 序列化二叉树

(1) 正反全是 BFS。

正序列二叉树:BFS

反序列构建二叉树:BFS [以层次遍历的方式来构建一棵二叉树]

反序列中依旧利用队列的方式,进行node.left 和 node.right的传递,一般情况下是利用递归进行节点和根、子树之间传递的。

    def deserialize(self, data): ## 反序列化将root.left 存储在里面,每次给其赋值  
        if data == "[]": return  ## 层次遍历的方式建立一棵二叉树
        vals, i = data[1:-1].split(','), 1
        root = TreeNode(int(vals[0])) ## 构造根节点 
        q = [root]
        i = 1
        while(q): ## 正序列化和反序列化必须保证完全对应,否则可能报错 
            node = q.pop(0) # 出队伍,找这个节点的左右节点,按照层次遍历,那么i ,i+1就是其左右节点
            if vals[i] != 'null':
                node.left = TreeNode(int(vals[i])) # 左节点找到
                q.append(node.left) # 将该节点存储,用于下一层的寻找,过程  
            i+=1      
            if vals[i] != 'null' :
                node.right = TreeNode(int(vals[i]))  
                q.append(node.right)
            i+=1
        return root

剑指 Offer 38. 字符串的排列

剑指 Offer 51. 数组中的逆序对

考点:归并排序【分治思想】  相似题目: 327,493。 【树状数组】

分析:归并排序,就是以最小单位来进行排序,然后在合并在一起。

[3,-1] [1,2] 这是合并前的元素,那么从小到大排序的过程中,可以知道一共可以产生多少逆序数。

i =0,j=0。如果left[i] > right[j].那么left和right都是基本有序的(从小到大的),那么 len(left)-i都是比right[i]大的,那么在归并排序的过程中加入此条统计语句就行。

class Solution:
    # 归并排序 过程中计数, 时间复杂度 O(NlogN)
    #def merge(self, arr1: List[int], arr2: List[int]) -> List[int]:
    def merge(self, nums,low,mid,high) -> List[int]:
        #global count 
        container = []    # 用于存储有序数字
        i, j = low, mid+1
        while i <= mid and j <= high: ##比较合并过程
            if nums[i] <= nums[j]:
                container.append(nums[i])
                i += 1
            else:
                self.ans += mid-i+1 
                ## self.ans 全局变量  
                container.append(nums[j])
                j += 1
        if i <= mid:
            container.extend(nums[i:mid+1])
        elif j <= high:
            container.extend(nums[j:high+1])
        nums[low:high+1] = container
        #print(arr,low,mid,high)


    def merge_sort(self,nums,low,high):
        if low<high:
            mid = (low+high)//2
            self.merge_sort(nums,low,mid)
            #print('left,',low,mid)
            self.merge_sort(nums,mid+1,high)
            #print('right,',mid+1,high)
            self.merge(nums,low,mid,high)
                #print('merge')
        #else:
            #return 
    
    def reversePairs(self, nums: List[int]) -> int:
        self.ans = 0
        self.merge_sort(nums,0,len(nums)-1)
        return self.ans

剑指 Offer 41. 数据流中的中位数

分析:查找操作时间复杂度O(1);添加操作:因为插入前序列是有序的,所以可以直接采用插入排序的方式。

         利用二分查找法 找插入的位置 O(logN);插入操作时间复杂度O(N). 

class MedianFinder:

    def __init__(self):
        self.sort = [] 
        #self.n = 0 
    
    def addNum(self, num: int) -> None:
        #self.sort.append(num)
        #self.sort.sort()
        
        n = len(self.sort)
        if self.n == 0:
            self.sort.append(num)
            return 
        ## 二分插入排序
        i,j=0,n-1
        while(i<=j): ## 二分查找 
            mid = (i+j)//2
            if self.sort[mid] > num:
                j = mid-1
            else:
                i = mid+1
        self.sort.insert(i,num) #O(N)

    def findMedian(self) -> float: # O(1)
        n = len(self.sort)
        if n == 0:return 
        if n%2 == 0:
            return (sort[n//2]+self.sort[n//2-1])/2 
        else:
            return self.sort[n//2]   

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Foneone

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值