DP-special

目录

  • 53、max subarray
  • 62、unique paths
  • 63、unique path2
  • 70、climbing stairs
  • 91、decode ways
  • 120、triangle
  • 121、best time sell and buy stocks
  • 139、 Word Break
  • 152、Maximum Product Subarray
  • 198、 House Robber
  • 213、house robber II
  • 221、 Maximal Square
  • 264、Ugly Number II (比较重要)
  • 279、Perfect Squares
  • 300、Longest Increasing Subsequence (比较重要)
53、max subarray

Find the contiguous subarray within an array (containing at least one number) which has the largest sum.
For example, given the array [-2,1,-3,4,-1,2,1,-5,4],
the contiguous subarray [4,-1,2,1] has the largest sum = 6.

my answer:

class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if nums is []:
            return 0
        max_dict = {}
        for i, item in enumerate(nums):
            if i == 0:
                max_dict[i] = item
            else:
                if max_dict[i-1] > 0:
                    max_dict[i] = max_dict[i-1] + item
                else:
                    max_dict[i] = item
        max_num = max(max_dict.values())
        return max_num

别人程序:

    def maxSubArray_v2(self, nums):
        if max(nums) < 0:
            return max(nums)
        global_max,local_max = float('inf'), 0 
        for item in nums:
            local_max = max(0, local_max+item)
            global_max = max(global_max, local_max)
        return global_max

比较精炼,不拖泥带水。
DP思想,目标可以分解为一个个小的子目标

62、unique paths
# A robot is located at the top-left corner of a m x n grid (marked 'Start' in the diagram below).
# The robot can only move either down or right at any point in time.
# The robot is trying to reach the bottom-right corner of the grid (marked 'Finish' in the diagram below).
# How many possible unique paths are there?


class Solution(object):
    def uniquePaths(self, m, n):
        """
        :type m: int
        :type n: int
        :rtype: int
        """
        dp = [[0 for _ in range(n)] for _ in range(m)]
        for i in xrange(m):
            for j in xrange(n):
                if i == 0 and j == 0:
                    dp[i][j] = 1
                elif j == 0 and i != 0:
                    dp[i][j] = dp[i-1][j]
                elif i == 0 and j != 0:
                    dp[i][j] = dp[i][j-1]
                else:
                    dp[i][j] = dp[i-1][j] + dp[i][j-1]
        return dp[m-1][n-1]

注意边界条件,上面的比较好理解,但是不是很GEEK,改进:

    def uniquePath_v2(self, m, n):
        dp = [[0 for _ in range(n)] for _ in range(m)]
        for i in range(m):
            dp[i][0] = 1
        for j in range(n):
            dp[0][j] = 1
        for i in xrange(1, m):
            for j in xrange(1, n):
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
        return dp[m - 1][n - 1]

一位数组代替:

def uniquePaths(self, m, n):
        if m < n:
            return self.uniquePaths(n, m)
        ways = [1] * n
        
        for i in xrange(1, m):
            for j in xrange(1, n):
                ways[j] += ways[j - 1]
        
        return ways[n - 1]

上面的方法极大降低了空间复杂度,只用来保存一维数组信息。以3*3为例,从第二行第二列开始,到当前位置的方式有a[1] = a[1] + a[1-1] 种。本质上,ways[j] += ways[j - 1]中,waya[i-1]表示的是从左边过来的路径,waya[i]表示从上边过来的路径,符合dp[i][j] = dp[i - 1][j] + dp[i][j - 1]内涵,只是不断进行刷新。

63、unique path2
# Follow up for "Unique Paths":
# Now consider if some obstacles
# are added to the grids. How many unique paths would there be?
# An obstacle and empty space is marked as 1 and 0 respectively in the grid.

class Solution(object):
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """
        if obstacleGrid == []:
            return 0
        m = len(obstacleGrid)
        n = len(obstacleGrid[0])

        obstacleGrid[0][0] = 1 if obstacleGrid[0][0] == 0 else 0
        for j in range(1, n):
            obstacleGrid[0][j] = obstacleGrid[0][j-1] * (1-obstacleGrid[0][j])
        for i in range(1, m):
            obstacleGrid[i][0] = obstacleGrid[i-1][0] * (1-obstacleGrid[i][0])
        for i in range(1, m):
            for j in range(1, n):
                if obstacleGrid[i][j] == 1:
                    obstacleGrid[i][j] = 0
                else:
                    obstacleGrid[i][j] = obstacleGrid[i-1][j] + obstacleGrid[i][j-1]
        return obstacleGrid[m-1][n-1]
64、min path sum

常规解法:

# Given a m x n grid filled with non-negative numbers, find a path from top
#  left to bottom right which minimizes the sum of all numbers along its path.
# Note: You can only move either down or right at any point in time.


class Solution(object):
    def minPathSum(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        m, n = len(grid), len(grid[0])
        res = [[0 for _ in xrange(n)] for _ in xrange(m)]
        res[0][0] = grid[0][0]
        for i in xrange(1, m):
            res[i][0] = res[i-1][0] + grid[i][0]
        for j in xrange(1, n):
            res[0][j] = res[0][j-1] + grid[0][j]
        for i in xrange(1, m):
            for j in xrange(1, n):
                res[i][j] = min(res[i-1][j], res[i][j-1]) + grid[i][j]

        return res[m-1][n-1]

     def minpathsum2(self,grid):
        '''
           一维数组简化方法
        '''
        res = grid[0]
        m, n = len(grid), len(grid[0])
        for j in xrange(1, n):
            res[j] = res[j-1] + grid[0][j]
        for i in xrange(1, m):
            res[0] = res[0] + grid[i][0]
            for j in xrange(1, n):
                res[j] = min(res[j], res[j-1]) + grid[i][j]

        return res[-1]

总结:
对于62,63,64这一类问题,能很容易想到是用动态规划思想,但是要确保解的质量。常规方法是先构建一个二维数组,依次保存前面子问题的解,进而为最终答案铺垫,二维数组分析要考虑边界条件,即是一维横向量或者一维列向量。后面就可以直接利用递归方程。
常用的第二种方法就是减少空间复杂度,用一个一维数组保存前面子问题的解。

70、climbing stairs

# You are climbing a stair case. It takes n steps to reach to the top.
# Each time you can either climb 1 or 2 steps.
# In how many distinct ways can you climb to the top?

class Solution(object):
    def climbStairs(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n < 3:
            return n
        res = [0 for _ in xrange(n+1)]
        res[1] = 1
        res[2] = 2
        for i in xrange(3, n+1):
            res[i] = res[i-1] + res[i-2]
        return res[n]

    def climbStairs2(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n < 3:
            return n
        a = 1
        b = 2
        for i in xrange(3, n+1):
            a, b = b, a + b
        return b

    def climbStairs3(self, n):
        if n == 1:
            return 1
        if n == 2:
            return 2
        return self.climbStairs(n - 1) + self.climbStairs(n - 2)
91、decode ways
# A message containing letters from A-Z is being
# encoded to numbers using the following mapping:
# 'A' -> 1
# 'B' -> 2
# ...
# 'Z' -> 26
# Given an encoded message containing digits, determine the total number of ways to decode it.
# For example,
# Given encoded message "12", it could be decoded as "AB" (1 2) or "L" (12).
# The number of ways decoding "12" is 2.
class Solution(object):
    def numDecodings(self, s):
        """
        :type s: str
        :rtype: int
        """
        if s == '' or s[0] == '0':
            return 0
        if '00' in s:
            return 0
        res = [1 for _ in xrange(len(s))]
        for i, c in enumerate(s):
            if i == 0:
                continue
            if c == '0':
                if s[i-1] > '2':
                    return 0
                else:
                    res[i] = res[i-2]
            else:
                if s[i-1] > '2' or s[i-1] == '0':
                    res[i] = res[i-1]
                elif s[i-1] == '2' and c > '6':
                    res[i] = res[i - 1]
                else:
                    res[i] = res[i-1] + res[i-2]
        return res[-1]

    def numDecodings(self, s):
        """
        :type s: str
        :rtype: int
        """
        if len(s) == 0 or s[0] == '0':
            return 0
        prev, prev_prev = 1, 0
        for i in xrange(len(s)):
            cur = 0
            if s[i] != '0':
                cur = prev
            if i > 0 and (s[i - 1] == '1' or (s[i - 1] == '2' and s[i] <= '6')):
                cur += prev_prev
            prev, prev_prev = cur, prev
        return prev

写出的代码不够pythonic,第一种是自己的方法,基本主线是DP,和climbing stairs很像,当前decode的可能性是前面字符和前前面字符可能性之和。但是有限制条件,正常情况下,[12123],对于3,可能性是res[1212]+res[121],因为3和23都可以单独解码。但是考虑其他情况,对当前字符为0,则只能和前面结合才能解码,也就是对[121230],对于0来说,res=res[1212],同样还有前面字符大于2时,也只有一种结合情况。当然还要考虑其他情况,比如起始字符为0则无解等。这也是自己程序判断的准则。
第二种解法比较精妙,只使用三个变量,方法还是有点绕,MARK。

120、triangle

# Given a triangle, find the minimum path sum from top to
#  bottom. Each step you may move to adjacent numbers on the row below.
# For example, given the following triangle
# [
#      [2],
#     [3,4],
#    [6,5,7],
#   [4,1,8,3]
# ]
# The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).

class Solution(object):
    def minimumTotal(self, triangle):
        """
        :type triangle: List[List[int]]
        :rtype: int
        """
        depth = len(triangle)
        if depth == 1:
            return triangle[0][0]
        for i in xrange(1, depth):
            for j in xrange(0, i+1):
                if j == 0:
                    triangle[i][j] += triangle[i - 1][j]
                elif j == i:
                    triangle[i][j] += triangle[i - 1][j-1]
                else:
                    triangle[i][j] += min(triangle[i-1][j-1], triangle[i-1][j])         
        return min(triangle[depth-1])

121、best time sell and buy stocks

# Say you have an array for which the ith element is the price of a given stock on day i.
# If you were only permitted to complete at most one transaction (ie, buy one and sell 
# one share of the stock), design an algorithm to find the maximum profit.
# Example 1:
# Input: [7, 1, 5, 3, 6, 4]
# Output: 5
# 
# max. difference = 6-1 = 5 (not 7-1 = 6, as selling price needs to be 
# larger than buying price)
# Example 2:
# Input: [7, 6, 4, 3, 1]
# Output: 0
# In this case, no transaction is done, i.e. max profit = 0.
class Solution(object):
    def maxProfit(self, prices):
        """
        :type prices: List[int]
        :rtype: int
        """
        if len(prices) < 2:
            return 0
        max_pro, min_price = 0, float('inf')
        for price in prices:
            min_price = min(price, min_price)
            max_pro = max(max_pro, price-min_price)
        return max_pro

想一下,这个题简单在于只需要操作依次,也就是说买入一次和卖出一次,使获利最大。最简单就是暴力法,每次取出一个元素,然后后面的找差值最大的。
两个指针是可行的,从前往后和从后往前。可行吗,和11题是有不一样的。当方法不对时,所谓思考都是低质量,还是没有见多识广。问题表述为从所有的数字中找到差值最大的两个数,且保证小值在大值前面。可是为什么能想到这种方法。

139. Word Break

题意是能否将一个单词分成指定列表中的单词的拼接,最先想到的方法是递归。

# Given a non-empty string s and a dictionary wordDict containing 
# a list of non-empty words, determine if s can be segmented into 
# a space-separated sequence of one or more dictionary words. 
# You may assume the dictionary does not contain duplicate words.
# 
# For example, given
# s = "leetcode",
# dict = ["leet", "code"].
# 
# Return true because "leetcode" can be segmented as "leet code".
# 
# UPDATE (2017/1/4):
# The wordDict parameter had been changed to a list of strings 
# (instead of a set of strings). Please reload the code definition 
# to get the latest changes.
class Solution(object):
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: List[str]
        :rtype: bool
        """
        if len(s) == 1:
            if s in wordDict:
                return True
        if s == '':
            return True
        for i in xrange(1, len(s)+1):
            if s[:i] in wordDict:
                # if i == len(s)-1:
                #     return True
                res = self.wordBreak(s[i:], wordDict)
                if res is True:
                    return True
                else:
                    continue
        return False
   def wordBreak2(self, s, wordDict):
        dp = [False for _ in xrange(len(s)+1)]
        dp[-1] = True
        # print dp[0]
        for i in xrange(0, len(s)):
            if i == 0:
                if s[i] in wordDict:
                    dp[i] = True
            for j in xrange(0, i+1):
                if s[j:i+1] in wordDict and dp[j-1] is True:
                    dp[i] = True
                    continue
        return dp[len(s)-1]

递归做到超时了。看来还是不行。
动态规划,最重要的是找出子问题方程,类似于最少硬币问题,问题可以分解为dp(i) = dp(j) && s[j, i) in dict。
简单描述下思考过程:迭代整个字符串,对当前字符串,再次从头开始循环,找出前面break point,说明当前能够分解。

class Solution(object):
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: List[str]
        :rtype: bool
        """
        n = len(s)

        max_len = 0
        for string in wordDict:
            max_len = max(max_len, len(string))

        can_break = [False for _ in xrange(n + 1)]
        can_break[0] = True
        for i in xrange(1, n + 1):
            for l in xrange(1, min(i, max_len) + 1):
                if can_break[i-l] and s[i-l:i] in wordDict:
                    can_break[i] = True
                    break
        return can_break[-1]
       

152. Maximum Product Subarray

Find the contiguous subarray within an array (containing at least one number) which has the largest product.
For example, given the array [2,3,-2,4],
the contiguous subarray [2,3] has the largest product = 6.

怎么思考这个问题呢,注意是连续,而且有正负,当前元素保留到本元素的最大正值和最大负值,也就是保存最大值和最小值,根据这个想法得到的答案为:

class Solution(object):
    def maxProduct(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        length = len(nums)
        max_pro = [0 for _ in xrange(length)]
        min_pro = [0 for _ in xrange(length)]

        for i in xrange(length):
            if i == 0:
                max_pro[i] = nums[i]
                min_pro[i] = nums[i]
            else:
                max_pro[i] = max(max_pro[i - 1] * nums[i], min_pro[i - 1] * nums[i], nums[i])
                min_pro[i] = min(max_pro[i - 1] * nums[i], min_pro[i - 1] * nums[i], nums[i])
        return max(max_pro)

运行效率为22%,有点低,实际上可以优化,首先,两个列表根本没有这个必要,因为只需要用到前面的一个数即可,而且再者用一个局部变量保存全局的最大值,不用后面的max()操作。

    def maxProduct2(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        max_pro, min_pro = nums[0], nums[0]
        glo_max = nums[0]
        for num in nums[1:]:
            max_pro, min_pro = max(max_pro * num, min_pro * num, num), \
                               min(max_pro * num, min_pro * num, num)
            if max_pro > glo_max:
                glo_max = max_pro
        return glo_max

经过上面的精简,效率达到了73%
还有以下的参考答案:

class Solution2:
    # @param A, a list of integers
    # @return an integer
    def maxProduct(self, A):
        global_max, local_max, local_min = float("-inf"), 1, 1
        for x in A:
            local_max = max(1, local_max)
            if x > 0:
                local_max, local_min = local_max * x, local_min * x
            else:
                local_max, local_min = local_min * x, local_max * x
            global_max = max(global_max, local_max)
        return global_max

加上了人工判断,简化了调用 min和max函数。

198. House Robber

入室抢劫,问题描述比较简单,一个列表表示每家能够偷的钱财,现在限制为不能连续抢劫两家,否则会报警。如何安排才能使抢劫到的钱财最多。
在大框架下面知道要用动态规划,如何自己分析出最可能的最优方案,首先是求约束条件下整体的最优解,前面的选择会影响到后面,但是当前子问题的只与前面一个或者前面有限个子问题的解有关。问题思路慢慢浮出水面,对于每一家,都有两个选择,抢或者不抢,保存对应的目前最优解。而且两种情况一定要小心分析,当选择抢,则前一家肯定是不能抢的,也就是 rob[i] = notrob[i-1] + nums[i] ,当选择不抢时,前面一家可以抢,也可以不抢,一定要注意,比如[4,1,2,8],对于2来说,不抢2不一定代表前面1要抢,最优是notrob[i] = max(rob[i],notrob[i])。

class Solution(object):
    def rob(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        l = len(nums)
        if l == 0:
            return 0
        if l <= 2:
            return max(nums)
        rob, notrob = nums[1], nums[0]
        for i in xrange(2, l):
            rob, notrob = nums[i] + notrob, max(rob, notrob)
        return max(rob, notrob)

    def rob1(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        l = len(nums)
        if l == 0:
            return 0
        if l < 2:
            return nums[0]
        pre_max, pre_premax = max(nums[0], nums[1]), nums[0]
        for i in nums[2:]:
            rob, not_rob = i + pre_premax, pre_max
            pre_premax, pre_max = pre_max, max(rob, not_rob)
        return max(pre_max, pre_premax)
    
    def rob2(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        last, now = 0, 0
        for i in nums:
            last, now = now, max(last + i, now)
        return now

第二种解法符合常规逻辑,是典型的动态规划思想,
res[i] = max(res[i-1],res[i-2]+nums[i]),res保存的是到某个房屋是的最大收益。有以下两种情况:

  • 如果选择了抢劫上一个屋子,那么就不能抢劫当前的屋子,所以最大收益就是抢劫上一个屋子的收益
  • 如果选择抢劫当前屋子,就不能抢劫上一个屋子,所以最大收益是到上一个屋子的上一个屋子为止的最大收益,加上当前屋子里有的钱。

所以,我们只要判断一下两个里面哪个大就行了,同时也是我们的递推式。另外我们可以做一点优化,本来我们是要用一个dp数组来保存之前的结果的。但实际上我们只需要上一次和上上次的结果,所以可以用两个变量就行了。

第一种方法和第二种方法只是表述上不一样,本质是一样的,第一种方法保存的是每次抢和不抢的收益,从过程考虑。而第二种方法保存的是本次和前一次的最大收益,不在乎抢还是不抢,从结果考虑。
第三种解法确实比较逆天,咋看毫无逻辑可言,实际上,精髓在于now表示当前的最大值,管抢还是不抢,last保存上一次的最大值,有这个值就是max()中不抢的来源,而last+i则表示上一次不抢而这次抢了。有点绕,挺有道理的。

213、house robber II

在上一题上升级,就是加入了一个约束条件,就是说第一个和最后一个不能都抢,主要思想还是没有变,引入的难点在于当最后一个抢时结果最大,如何确定第一个抢了没。最容易想到的方法就是第一个house时就给定限制,抢时最终结果如何,以及不抢最终结果如何,对比出最大的。
可以预见到,分不同情况考虑会比较繁琐,实际上转化为俩个类似的问题,一是抢第一家到倒数第二家以及抢不抢第一家到最后一家。

class Solution(object):
    def rob(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        l = len(nums)
        if l == 0:
            return 0
        if l < 2:
            return nums[0]
        pre_max, pre_premax = nums[1], 0
        for i in nums[2:]:
            rob, not_rob = i + pre_premax, pre_max
            pre_premax, pre_max = pre_max, max(rob, not_rob)
        a = max(pre_max, pre_premax)

        pre_max, pre_premax = nums[0], nums[0]
        for i in nums[2:-1]:
            rob, not_rob = i + pre_premax, pre_max
            pre_premax, pre_max = pre_max, max(rob, not_rob)
        b = max(pre_max, pre_premax)            
        return max(a, b)

这道题只能从上一题的第二种方法出发比较好解,如何从抢和不抢定义收益,会引起连锁反应,导致第一种方法失效。

221. Maximal Square

Given a 2D binary matrix filled with 0's and 1's, find the largest square containing only 1's and return its area.
For example, given the following matrix:
1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0
Return 4.

二进制矩阵,找到最大的正方形,并返回其面积。
明显,求最终问题可以划分为一个个子问题, 一个子问题可以由上一个子问题推出。最重要的是找出递推公式,可以发现,一个正方形的最右下角的元素,如果它的上方、左方和左上方都是另一个正方形的右下角元素,则组成另一个正方行,边长加1.

class Solution(object):
    def maximalSquare(self, matrix):
        """
        :type matrix: List[List[str]]
        :rtype: int
        """
        if matrix == [] or matrix == [[]]:
            return 0
        m = len(matrix)
        n = len(matrix[0])
        maxsquare = 0
        for i in xrange(m):
            matrix[i][0] = int(matrix[i][0])
            if matrix[i][0] == 1:
                maxsquare = 1
        for j in xrange(n):
            matrix[0][j] = int(matrix[0][j])
            if matrix[0][j] == 1:
                maxsquare = 1
        
        for i in xrange(1, m):
            for j in xrange(1, n):
                matrix[i][j] = int(matrix[i][j])
                if matrix[i][j]:
                    matrix[i][j] = min(matrix[i][j-1], matrix[i-1][j], matrix[i-1][j-1]) + 1
                    if matrix[i][j] > maxsquare:
                        maxsquare = matrix[i][j]
        return maxsquare**2

264. Ugly Number II

找丑数,丑数的定义是只能被2,3,5整除,也就是因子只能是2,3,5. 1特殊算是第一个丑数,求出第n个丑数。
如何去思考这个问题,在知道DP求解的前提下,如何由上一个子问题推出。整个问题不是很难,容易被自己思维限制住,从最小的数开始依次乘2,3,5,并更新指针到下一个数,如何取出最小的数呢,

class Solution(object):
    def nthUglyNumber(self, n):
        """
        :type n: int
        :rtype: int
        """
        uglynum = [1 for _ in xrange(n)]
        i = 0
        k2 = k3 = k5 = 0
        while i < n-1:
            minnum = min(uglynum[k2]*2, uglynum[k3]*3, uglynum[k5]*5)
            i += 1
            uglynum[i] = minnum
            if minnum == uglynum[k2]*2:
                k2 += 1
            if minnum == uglynum[k3]*3:
                k3 += 1
            if minnum == uglynum[k5]*5:
                k5 += 1
        return uglynum[n-1]
                

上面是比较常规的方法,下面是借助堆思想,增加了复杂度,但是比较简洁。堆结构会自动将数据排序,保证堆顶为最小元素。

import heapq

class Solution:
    # @param {integer} n
    # @return {integer}
    def nthUglyNumber(self, n):
        ugly_number = 0

        heap = []
        heapq.heappush(heap, 1)
        for _ in xrange(n):
            ugly_number = heapq.heappop(heap)
            if ugly_number % 2 == 0:
                heapq.heappush(heap, ugly_number * 2)
            elif ugly_number % 3 == 0:
                heapq.heappush(heap, ugly_number * 2)
                heapq.heappush(heap, ugly_number * 3)
            else:
                heapq.heappush(heap, ugly_number * 2)
                heapq.heappush(heap, ugly_number * 3)
                heapq.heappush(heap, ugly_number * 5)

        return ugly_number

    def nthUglyNumber2(self, n):
        ugly = [1]
        i2 = i3 = i5 = 0
        while len(ugly) < n:
            while ugly[i2] * 2 <= ugly[-1]: i2 += 1
            while ugly[i3] * 3 <= ugly[-1]: i3 += 1
            while ugly[i5] * 5 <= ugly[-1]: i5 += 1
            ugly.append(min(ugly[i2] * 2, ugly[i3] * 3, ugly[i5] * 5))
        return ugly[-1]

    def nthUglyNumber3(self, n):
        q2, q3, q5 = [2], [3], [5]
        ugly = 1
        for u in heapq.merge(q2, q3, q5):
            if n == 1:
                return ugly
            if u > ugly:
                ugly = u
                n -= 1
                q2 += 2 * u,
                q3 += 3 * u,
                q5 += 5 * u,
279. Perfect Squares

Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, ...) which sum to n.
For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9.

首先想到的是硬币问题,从第一个数开始遍历到n,每个保存自己最小的完全平方数个数。

class Solution(object):
    def numSquares(self, n):
        """
        :type n: int
        :rtype: int
        """
        dp = [0 for _ in xrange(n+1)]
        for i in xrange(1, n+1):
            numlist = self.subnum(i)
            dp[i] = min(dp[i-num*num] for num in numlist) + 1
        return dp[n]

    def subnum(self, num):
        numlist = []
        for i in xrange(1, num+1):
            if i*i <= num:
                numlist.append(i)
            else:
                return numlist
        return numlist

这个方法比较普通,跑出来性能为33%,第二种方法比较高效

class Solution(object):
    _num = [0]
    def numSquares(self, n):
        """
        :type n: int
        :rtype: int
        """
        num = self._num
        while len(num) <= n:
            num += min(num[-i*i] for i in xrange(1, int(len(num)**0.5+1))) + 1,
        return num[n]

其实,受第二种方法的启发,第一种稍作变形为:

class Solution1(object):
    dp = [0]
    def numSquares(self, n):
        """
        :type n: int
        :rtype: int
        """
        length = len(self.dp)
        while length < n+1:
            numlist = self.subnum(length)
            temp = min(self.dp[length-num*num] for num in numlist) + 1
            length += 1
            self.dp.append(temp)
        return self.dp[n]

    def subnum(self, num):
        numlist = []
        for i in xrange(1, num+1):
            if i*i <= num:
                numlist.append(i)
            else:
                return numlist
        return numlist

性能提升到88%,通过缓存来提升效率。在编程中也经常用到这个思想。回头看第二种解法,其实本质一样,通过xrange(1, int(len(num)**0.5+1)))代替了第一种方法中的辅助函数。

300. Longest Increasing Subsequence

最长递增子序列,算是比较经典的一个题,在一串数字中找到最长的递增子序列。常规DP想法,问题的解可以由前面的子问题一步一步求出,对列表进行遍历,依次保存到目前为止比当前数据小的最大数据个数,并保存其中的最大值。

    def lengthOfLIS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        length = len(nums)
        if length < 2:
            return length
        res = [1 for _ in xrange(length)]
        maxLIS = 0
        for i in xrange(1, length):
            num = self.searchfirst(nums[:i], nums[i], res)
            res[i] = num + 1
            if res[i] > maxLIS:
                maxLIS = res[i]
        return maxLIS
# 找出前面比当前数字小的最大长度
    def searchfirst(self, nums, item, res):
        num = 0
        for i in xrange(len(nums)):
            if nums[i] < item:
                num = max(num, res[i])
        return num

更多其他的解法

# Binary search solution.
class Solution(object):
    def lengthOfLIS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        LIS = []
        def insert(target):
            left, right = 0, len(LIS) - 1
            # Find the first index "left" which satisfies LIS[left] >= target
            while left <= right:
                mid = left + (right - left) / 2;
                if LIS[mid] >= target:
                    right = mid - 1
                else:
                    left = mid + 1
            # If not found, append the target.
            if left == len(LIS):
                LIS.append(target);
            else:
                LIS[left] = target

        for num in nums:
            insert(num)

        return len(LIS)

# Time:  O(n^2)
# Space: O(n)
# Traditional DP solution.
class Solution2(object):
    def lengthOfLIS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        dp = []  # dp[i]: the length of LIS ends with nums[i]
        for i in xrange(len(nums)):
            dp.append(1)
            for j in xrange(i):
                if nums[j] < nums[i]:
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp) if dp else 0

第三种方法其实就是第一种,第一种稍微复杂化了点。二叉树搜索值得进一步深入。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容