献芹奏曝-Python面试题-算法-动态规划篇

     上一篇:献芹奏曝-Python面试题 

      开篇的话:本文目的是收集和归纳力扣上的算法题,希望用python语言,竭我所能做到思路最清奇、代码最简洁、方法最广泛、性能最高效,了解常见题目,找到最利于记忆的答案,更加从容的应对面试。希望广思集益,共同进步。 

动态规划篇 

  1. 70. 爬楼梯(难度系数✯)

    class Solution:
        def climbStairs(self, n: int) -> int:
            if n == 1:
                return 1
            elif n == 2:
                return 2
            else:
                return self.climbStairs(n - 1) + self.climbStairs(n - 2)
    方法一

    运行结果:1:超时

    知识点/技巧: 递归调用,会有大量重复计算

    class Solution:
        dict_result = {}
    
        def climbStairs(self, n: int) -> int:
            if self.dict_result.get(n):
                return self.dict_result.get(n)
            if n == 1:
                self.dict_result[n] = 1
                return 1
            elif n == 2:
                self.dict_result[n] = 2
                return 2
            else:
                self.dict_result[n] = self.climbStairs(n - 1) + self.climbStairs(n - 2)
                return self.dict_result[n]
    方法二

    运行结果:1:耗时超过54%。2:内存超过79% 

    知识点/技巧:通过字典,将计算过的值保存,减少重复计算

    class Solution:
        def climbStairs(self, n: int) -> int:
            if n == 1:
                return 1
            a = [1] * (n + 1)
            a[1] = 1
            a[2] = 2
            for i in range(3, n+1):
                a[i] = a[i - 1] + a[i - 2]
            return a[n]
    方法三

    运行结果:1:耗时超过79%。2:内存超过89% 

    知识点/技巧:通过数组,求和的速度更快,根据索引取值,比字典查找更快

    class Solution:
        def climbStairs(self, n: int) -> int:
            if n == 1:
                return 1
            first = 1
            second = 2
            for i in range(3, n + 1):
                first, second = second, first + second
            return second
    方法四

    运行结果:1:耗时超过79%。2:内存超过75% 

    知识点/技巧:通过数字滚动的方式,但是疑惑的是,并没发现内存空间提升多少
    当然还有一些其他方法,矩阵法、通项公式法,这里不一一介绍

  2. 53. 最大子数组和(难度系数✯)

    class Solution:
        def maxSubArray(self, nums: List[int]) -> int:
            # 构造和目标数组一样长度的数组
            r = nums.copy()
            for i in range(1, len(nums)):
                if r[i - 1] > 0:
                    r[i] += r[i - 1]
            return max(r)
    方法一

    运行结果:1:耗时超过78%。2:内存超过15% 。

    知识点/技巧: 

    为了便于理解记忆:可以想象成一个赌徒赌博,什么时候,手中筹码最多,
    我们记录每一把赌牌后的手中筹码数,当然有一点不同的是,
    如果上一步赢了,或者还有剩余,我们财富积累,
    如果上一步钱输光了,往事随风,另起炉灶,重新开始,从当前记录
    class Solution:
        def maxSubArray(self, nums: List[int]) -> int:
            if len(nums) == 1:
                return nums[0]
            top_one = nums[0]
            for i in range(1, len(nums)):
                if nums[i - 1] > 0:
                    nums[i] += nums[i - 1]
                if nums[i] > top_one:
                    top_one = nums[i]
            return top_one
    方法二

    运行结果:1:耗时超过70%。2:内存超过36% 

    知识点/技巧:通过数字滚动的方式,节省内存

  3. 416. 分割等和子集(难度系数✯)

    """
    1:要想使得分割成后的两个子集和相等,那么:和必须为偶数
    2:我们马上可以转换成0-1背包问题:即每个物品可以出现0次或1次
    3:                     0   1   2   3   4   5   6   7   8   9   10  11
    第一个物品放或者不放      √   √
    第二个物品放或者不放      √   √               √   √                       如果第二个物品5没放,那就是原来的值,如果5放了,那就是在原来的基础上向后移动5个单位
    第三个物品放或者不放      √   √               √   √                √   √
    但是需要注意的是放与不放这个动作一气呵成,
    """
    from typing import List
    
    
    class Solution:
        def canPartition(self, nums: List[int]) -> bool:
            s = sum(nums)
            if s % 2:
                # 1:要想使得分割成后的两个子集和相等,那么:和必须为偶数
                return False
            target = s // 2
            a = [False] * (target + 1)
            a[0] = True
            for index_n, n in enumerate(nums):
                if n > target:
                    # 2:如果遍历的元素比目标值大,说明没有找到结果,返回False
                    return False
                change_index = []
                for index, item in enumerate(a):
                    if item:
                        if index + n > target:
                            for c_index in change_index:
                                a[c_index] = True
                            break
                        if index + n == target:
                            return True
                        else:
                            change_index.append(index + n)
                else:
                    # 为什么只有在退出的时候才统一赋值,[1,2,5] 此时会出错
                    for c_index in change_index:
                        a[c_index] = True
            return False
    方法一

    运行结果:1:耗时超过95%。2:内存超过83%

    知识点/技巧: 转换成01背包问题,详情看代码中注释

    class Solution:
        def canPartition(self, nums: List[int]) -> bool:
            s = sum(nums)
            if s % 2:
                # 1:要想使得分割成后的两个子集和相等,那么:和必须为偶数
                return False
            target = s // 2
            a = [False] * (target + 1)
            a[0] = True
            for index_n, n in enumerate(nums):
                if n > target:
                    # 2:如果遍历的元素比目标值大,说明没有找到结果,返回False
                    return False
                for i in reversed(range(0, target - n + 1)):
                    if a[i]:
                        if i + n == target:
                            return True
                        a[i + n] = True
            return False
    方法二

    运行结果:1:耗时超过96%。2:内存超过99%

    知识点/技巧: 倒叙遍历,是列表变化(长度、内容)的克星

    class Solution:
        def canPartition(self, nums: List[int]) -> bool:
            s = sum(nums)
            if s % 2:
                # 1:要想使得分割成后的两个子集和相等,那么:和必须为偶数
                return False
            target = s // 2
            a = [False] * (target + 1)
            dir_list = {0: True}
            for n in nums:
                if n > target:
                    # 2:如果遍历的元素比目标值大,说明没有找到结果,返回False
                    return False
                list_keys=[]
                for key in reversed(list(dir_list.keys())):
                    if dir_list.get(target - n):
                        return True
                    list_keys.append(key + n)
                else:
                    for l_key in list_keys:
                        dir_list[l_key] = True
            return False
    方法三

    运行结果:1:耗时超过96%。2:内存超过43%

    知识点/技巧:利用哈希表实现,实现快速查找

  4. 217. 存在重复元素(难度系数✯)

    方法一

    运行结果:1:耗时超过40%。2:内存超过33%。3:代码简洁

    知识点/技巧: 利用set去重,比较长度

    方法二

    运行结果:1:耗时超过76%。2:内存超过30%。

    知识点/技巧: 利用字典记录字母出现次数

  5. 198.打家劫舍 (难度系数✯✯)

    class Solution:
        def rob(self, nums: List[int]) -> int:
            if not nums:
                return 0
            size = len(nums)
            if size==1:
                return nums[0]
    
            dp = [0] * size
            dp[0] = nums[0]
            dp[1] = max(nums[0],nums[1])
    
            for i in range(2,size):
                dp[i] = max(dp[i-2]+nums[i],dp[i-1])
    
            return dp[size-1]
    方法一 python

    运行结果:1:耗时超过5.99%。2:内存超过54.25%。

    知识点/技巧:由少到多,无限套娃

    /**
     * @param {number[]} nums
     * @return {number}
     */
    var rob = function(nums) {
        let n_length = nums.length
        if (n_length == 0){
            return 0
        }
        if (n_length==1){
            return nums[0]
        }
        var dp = new Array(n_length)
        dp[0] = nums[0]
        dp[1] = Math.max(nums[0],nums[1])
        for(i=2;i<n_length;i++){
            dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1])
        }
        return dp[n_length-1]
    };
    方法一 js

    运行结果:1:耗时超过5.99%。2:内存超过54.25%。 

    class Solution:
        def rob(self, nums: List[int]) -> int:
            if not nums:
                return 0
            size = len(nums)
            if size == 1:
                return nums[0]
            _pre = nums[0]
            _next = max(nums[0], nums[1])
            for i in range(2, size):
                _pre, _next = _next, max(_pre + nums[i], _next)
            return _next
    方法二 python

    运行结果:1:耗时超过90%。2:内存超过46%。

    知识点/技巧:较方法1,通过滚动字段的方式替换了列表

    /**
     * @param {number[]} nums
     * @return {number}
     */
    var rob = function(nums) {
        let n_length = nums.length
        if (n_length == 0){
            return 0
        }
        if (n_length==1){
            return nums[0]
        }
        var dp = new Array(n_length)
        _pre = nums[0]
        _next = Math.max(nums[0],nums[1])
        for(i=2;i<n_length;i++){  
            _pre_pre = _pre  
            _pre = _next 
            _next = Math.max(_pre_pre+nums[i],_next)   
        }
        return _next
    };
    方法二 js

    运行结果:1:耗时超过66%。2:内存超过20%。

    知识点/技巧:较方法1,通过滚动字段的方式替换了列表

  6. 213. 打家劫舍 II (难度系数✯✯)

    class Solution:
        def rob(self, nums: List[int]) -> int:
            def my_rob(nums):
                cur, pre = 0, 0
                for num in nums:
                    cur, pre = max(pre + num, cur), cur
                return cur
    
            if not nums:
                return 0
            elif len(nums) == 1:
                return nums[0]
            else:
                return max(my_rob(nums[:-1]), my_rob(nums[1:]))
    python

    运行结果:1:耗时超过10%。2:内存超过65%。

    知识点/技巧:分情况考虑:偷首不偷尾,不偷首偷尾

  7. 337. 打家劫舍 III (难度系数✯✯)

    class Solution:
        def rob(self, root: TreeNode) -> int:
            if not root:
                return 0
            money = root.val # 当前层节点
            if root.left:
                money += self.rob(root.left.left) + self.rob(root.left.right) # 孙子层节点
            if root.right:
                money += self.rob(root.right.left) + self.rob(root.right.right) # 孙子层节点
            return max(money,self.rob(root.left)+self.rob(root.right))
    方法一

    运行结果:超时。

    知识点/技巧:隔一层统计求和(爷+孙  <==> 父)

    # Definition for a binary tree node.
    # class TreeNode:
    #     def __init__(self, val=0, left=None, right=None):
    #         self.val = val
    #         self.left = left
    #         self.right = right
    class Solution:
        dict_result = {}
        def rob(self, root: TreeNode) -> int:
            if not root:
                return 0
            if self.dict_result.get(root):
                return self.dict_result.get(root)
            money = root.val # 当前层节点
            if root.left:
                money += self.rob(root.left.left) + self.rob(root.left.right) # 孙子层节点
            if root.right:
                money += self.rob(root.right.left) + self.rob(root.right.right) # 孙子层节点
            result = max(money,self.rob(root.left)+self.rob(root.right))
            self.dict_result[root] = result
            return result
    方法二

    运行结果:1:耗时超过47.99%。2:内存超过6.21%。

    知识点/技巧:引入字典,减少中间结果计算

    # Definition for a binary tree node.
    # class TreeNode:
    #     def __init__(self, val=0, left=None, right=None):
    #         self.val = val
    #         self.left = left
    #         self.right = right
    class Solution:
        def rob(self, root: TreeNode) -> int:
            # 我们使用一个大小为2的元祖表示 偷、不偷
            def my_rob(node):
                if not node:
                    return 0, 0
                l = my_rob(node.left)
                r = my_rob(node.right)
                selected = node.val + l[1] + r[1]
                not_selected = max(l[0],l[1]) + max(r[0],r[1])
                return selected, not_selected
            return max(my_rob(root))
    方法三

    运行结果:1:耗时超过87.65%。2:内存超过70.08%。

    知识点/技巧:回到最初的起点,每个节点可选择偷或者不偷两种状态

posted @ 2022-03-20 17:30  逍遥小天狼  阅读(75)  评论(0编辑  收藏  举报