Leetcode刷题笔记-dp

本文精选多道经典动态规划题目,深入浅出地讲解每道题目的解题思路与算法实现,涵盖背包问题、最短路径、最长公共子序列等多种类型。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2896. Apply Operations to Make Two Strings Equal

from functools import cache
class Solution:
    def minOperations(self, s1: str, s2: str, x: int) -> int:
        '''
        选i,j flip s1[i], s1[j] cost x
        只选i, flip s1[i] s1[i+1] cost 1
        store the list for s1 which needs to change [0, 3, 5, 8] for example 1
        Now we can either:
        1. move any index left or right for cost 1 (e.g. [0, 3, 5, 8] => [0, 4, 5, 8] for cost 1)
        2. delete any two adjacent indexes for cost 1
        3. delete any two non-adj indexes for cost x
        add any two non-existing indexes for cost 1, but we wouldn't ever do this

        Treat this problem like the classic house robbers DP problem.
        1. Start from the last element in diffs: we can either pair this with the second to last element 
        and remove both of those for cost diffs[-1] - diffs[-2] (the distance between them),
        or we can take out the last element for cost x / 2. Now repeat this process until you've removed all the elements in diffs!

        Note that if there are an odd number of diffs, then it's impossible to correct them all, since every operation in the problem operates on exactly two bits. That means there must be an even number of diffs for any answer.

        Thus, for the zeroth element, if we end up there, we have to pay the x / 2, since there's nothing to pair it with, and this is how we ensure that we always fully make pairs. (we never end up paying only x / 2 -- we'll always pay the x / 2 cost an even number of times)
        '''
        # DP O(n)
        
        # indexes where the bits differ
        diffs = [i for i, a, b in zip(range(len(s1)), s1, s2) if a != b]
        # if there are an odd number of differences, it's impossible
        if len(diffs) % 2 == 1: return -1

        @cache
        def lowestCostToIthDiff(i):
            if i == 0:
                return x / 2  # 和下面的x/2 匹配,一次是pair 2个去消掉, 为什么如果走了下面那个x/2的匹配,这里的x/2一定会用上?因为总共有偶数个diffs, 下面用到了1个,剩下odd个,这个0th也要拿出来,让rest成为even个数
            if i == -1:
                return 0
            return min(
                lowestCostToIthDiff(i - 1) + x / 2,  # 和上面的x/2匹配, 便成x,因为一次是消掉2个
                lowestCostToIthDiff(i - 2) + diffs[i] - diffs[i-1]  # 这个也是匹配2个,但是是倒数两个
            )

        return int(lowestCostToIthDiff(len(diffs) - 1))

418. Sentence Screen Fitting

class Solution:
    def wordsTyping(self, sentence: List[str], rows: int, cols: int) -> int:
        '''
        1. Based on the above observation, in the first for loop we compute the number of words that can be placed in the row if ith word is used as the starting word. This is saved as dp[i]. Note that this value can be greater than n.
        
        2.In the next for loop we calculate how many words are placed in each row based on dp[i]. Imagine placing the 0th word in the row-0, then this row will hold dp[0] words. Next, which word will be placed on the start of next row? We calculate that using dp[k] % n (Remember dp[i] can be greater than n).
        '''
        n = len(sentence)
        dp = [0 for _ in range(n)]  # the number of workds can be fit in 1 line if start with word i
        
        for i in range(n):  # loop i to calcuate the dp[i] which starts with word i
            cur_total_length = 0
            number_of_words = 0
            cur_index = i
            while (cur_total_length + len(sentence[cur_index % n]) <= cols):
                cur_total_length += len(sentence[cur_index % n])
                cur_total_length += 1  # space  
                cur_index += 1
                number_of_words += 1
            dp[i] = number_of_words
        
        # calcualte how many words can be fit into all rows
        count = 0
        index = 0
        for r in range(rows):
            count += dp[index]
            index = (dp[index] + index) % n
            
        return count // n
            
                

818. Race Car

class Solution:

    def racecar(self, T: int, depth = 0) -> int:
        # T > 0
        q = collections.deque([[ 0, 1 ]])
        seen = set('0,1')
        while True:
            k = len(q)
            while k:
                [ pos, vel ] = q.popleft()
                if pos == T:
                    return depth  # 🎯 target T found
                cand = []  # next layer
                if pos + vel > 0:  # prune, check the dp idea,  go back to somewhere>=0 
                    cand.append([ pos + vel, 2 * vel ])  # A
                cand.append([ pos, 1 if vel < 0 else -1 ])  # R
                for pos, vel in cand:
                    if f'{pos},{vel}' not in seen:
                        q.append([ pos, vel ])
                        seen.add(f'{pos},{vel}')
                k -= 1
            depth += 1
        return -1
    
    
  
'''
https://leetcode.com/problems/race-car/discuss/227415/Figures-to-make-the-DP-solution-more-straightforward  
    dp:
       1.  either go forward until pass the target and then go back
        2. Or go forward until one step before target, then go back to somewhere >=0, then go forward again to the target.
      
'''
 
    dp = {0: 0}
    def racecar(self, t):
        if t in self.dp:
            return self.dp[t]
        n = t.bit_length()
        if 2**n - 1 == t:  # 0, 1, 3, 7 = 2**n-1
            self.dp[t] = n
        else:
            self.dp[t] = self.racecar(2**n - 1 - t) + n + 1  # n step + 1 R
            for m in range(n - 1):
                self.dp[t] = min(self.dp[t], self.racecar(t - 2**(n - 1) + 2**m) + n + m + 1)  # link  里面的case1
        return self.dp[t] 

1014. Best Sightseeing Pair

class Solution:
    def maxScoreSightseeingPair(self, values: List[int]) -> int:
        #  dp idea, we want to get the max value of A[i]+i + A[j]-j, where i<j, so we need to keep record of the previous best index which can make the max A[i]+i.
        re = cur_max = 0
        for j, n in enumerate(values):
            re = max(re, values[j] - j + cur_max)
            cur_max = max(cur_max, values[j] + j)
        return re

 继承了上题的思路的:

1937. Maximum Number of Points with Cost

class Solution:
    def maxPoints(self, points: List[List[int]]) -> int:
        '''
        # dp[i][j] pick up i,j  and the max points 
        # dp[i][j] = max(point[i, j] + previous row's dp[r][c] - diff)
        R,C = len(points), len(points[0])
        dp = [[points[i][j] for j in range(C)] for i in range(R)]
        re = points[0][0]
        for i in range(R):
            for j in range(C):   # O(N^3) time limited 
                if i != 0:
                        dp[i][j] = max(dp[i][j], points[i][j] + dp[i-1][pre_c] - abs(j-pre_c))
                re = max(re, dp[i][j])
        return re
        '''
        
        dp = [0] * len(points[0])
        for line in points:
            for i in range(1, len(line)):
                dp[i] = max(dp[i], dp[i - 1] - 1)  # 左边 最大的上一行的点-距离
                dp[-i - 1] = max(dp[-i - 1], dp[-i] - 1)  # 右边 最大的上一行的点 减去距离
            for i in range(len(line)): #  这行的循环结束后 dp 给到 上面那个for循环,就相当于上一行的dp
                dp[i] += line[i]  
            
        return max(dp)
        

1043. Partition Array for Maximum Sum

class Solution:
    def maxSumAfterPartitioning(self, arr: List[int], k: int) -> int:
        #  dp[i]  will be the answer for array A[0], ..., A[i-1].
        #  For j = 1 .. k that keeps everything in bounds, dp[i] is the maximum of dp[i-j] # + max(A[i-1], ..., A[i-j]) * j .
        N = len(arr)
        dp = [0 for _ in range(len(arr) + 1)]
        for i in range(N + 1):
            cur_max = 0
            for j in range(1, min(i, k)+1):
                cur_max = max(cur_max, arr[i-j])
                dp[i] = max(dp[i], dp[i-j] + cur_max*j)
 
        return dp[N]

354. Russian Doll Envelopes  

class Solution:
    def maxEnvelopes(self, envelopes: List[List[int]]) -> int:
        '''
        先按照宽度排序,再按照长度排序,如果宽度一样,我们把长度长的排在前面,然后根据长度做longest increasement sequence 
        因为等宽的不能放进一个信封,所以选了4,8 就不能选4,6了。 如果4,6在4,8前面,LIS就会计算4,6和4,8

        [2,3]
        [3,7]
        [4,8]
        [4,6]
        '''
        env = sorted(envelopes, key=lambda x: (x[0], -x[1]))
        dp = [1] * len(env)
        for i, (_, y) in enumerate(env):
            for j in range(i):
                if env[j][1] < y:
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp) if dp else 0
        
        ''' 一样也是LIS 但是更好的方法
        envs = sorted(envelopes, key=lambda x: (x[0], -x[1]))
        res =[float('inf')] * len(envs)
        for _, h in envs:
            res[bisect.bisect_left(res, h)] = h
        return bisect.bisect_left(res, float('inf'))
        '''

44. Wildcard Matching

func isMatch(s string, p string) bool {
    dp := make([][]bool, len(s)+1)  // dp[s_idx][p_idx]
    for i := range dp {
        dp[i] = make([]bool, len(p)+1)
    }
    
    // init dp start from the table initiated as False everywhere but dp[0][0] = True.
    dp[0][0] = true
    
    
     // above miss to compute the situation when s is '' but p is not
    for j := 1; j <= len(p); j++ {
        if p[j-1] == '*' {
            dp[0][j] = dp[0][j-1]
        }
    } 
    // apply two rules
    // 1. if s[s_idx-1] = p[p_idx-1]   dp[i][j] i: idx of s, j: idx of p   = dp[i-1][j-1]
    // 2. if p[p_idx-1] = '*' and dp[i][j-1] = true or dp[i-1][j-1], dp[idx][j] = true for all idx >= i-1
    // case:   s = a p = a* dp[1][2] = dp[1][1], s = a p = * dp[1][1] = dp[0][1]
    for i := 1; i <= len(s); i++ {
        for j := 1; j <= len(p); j++ {
            if dp[i][j] == true {  // because of 下面的 rule2
                continue
            }
            if s[i-1] == p[j-1] || p[j-1] == '?'{  // rule1
                dp[i][j] = dp[i-1][j-1]
            }else if p[j-1] == '*' { // rule2  
                if dp[i][j-1] == true || dp[i-1][j] == true {
                    for idx := i; idx <= len(s); idx++ {
                        dp[idx][j] = true
                    }
                }
            }
        }
    }
    
    return dp[len(s)][len(p)]
    
}
func isMatch(s string, p string) bool {
    // mem[i][j] means isMatch(s[:i], p[:j])
    mem := make([][]bool, len(s)+1)
    for i := range mem {
        mem[i] = make([]bool, len(p)+1)
    }
	
	// init bound, mem[n][0] is false while n > 0
    mem[0][0] = true
    for j := 1; j <= len(p); j++ {
        if p[j-1] == '*' {
            mem[0][j] = mem[0][j-1]
        }
    }
    
    for i := 1; i <= len(s); i++ {
        for j := 1; j <= len(p); j++ {
            if p[j-1] == '*' {
// mem[i][j] = mem[i][j-1] 则* 匹配空字符串, =mem[i-1][j]则j匹配任意当前字符串
                mem[i][j] = mem[i][j-1] || mem[i-1][j]
            } else if p[j-1] == '?' || p[j-1] == s[i-1] {
                mem[i][j] = mem[i-1][j-1]
            }
        }
    }
    
    return mem[len(s)][len(p)]
}

410. Split Array Largest Sum

参考

Loading...

Loading...

func splitArray(nums []int, m int) int {
    // dp
    length := len(nums)
    dp := make([][]int, length+1)
    // init dp[][]
    for i := 0; i <= length; i++ {
        dp[i] = make([]int, m+1)
        for j := 0; j <= m; j++ {
            dp[i][j] = math.MaxInt32
        }
    }
    
    preSum := make([]int, length+1)
    for i, v := range nums {
        preSum[i+1] = preSum[i] + v
    }
    
    dp[0][0] = 0
    for i:= 1; i <= length; i++ {
        for j := 1; j <= m; j++ {
            for k := 0; k < i; k++ {  //  0 <= k < i
                // dp[i][j] nums[0]~nums[i] split to j parts 
                dp[i][j] = min(dp[i][j], max(dp[k][j-1], preSum[i] - preSum[k]))  
            }
        }
    }
    return dp[length][m]
        
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

func max(a, b int) int {
    if a < b {
        return b
    }
    return a
}

312. Burst Balloons

https://www.youtube.com/watch?v=z3hu2Be92UA&ab_channel=HuaHua

dp[i][j] in here means, the maximum coins we get after we burst all the balloons between i+1 and j-1 in the array 

class Solution(object):
    def maxCoins(self, nums):
        nums = [1] + nums + [1]
        n = len(nums)
        dp = [[0] * n for _ in xrange(n)]
        print n
        for gap in range(2, n):
            for left in range(0, n-gap):
                right = left + gap
                for i in range(left+1, right):  # left, right are not included for the last burst, they are the edges 
                    dp[left][right] = max(dp[left][right], 
                                         nums[left]*nums[i]*nums[right] + dp[left][i] + dp[i][right])
        return dp[0][-1]

1000. Minimum Cost to Merge Stones

花花酱 LeetCode 1000. Minimum Cost to Merge Stones – Huahua’s Tech Road

class Solution(object):
    def mergeStones(self, stones, K):
        n = len(stones)
        if (n-1) % (K-1): return -1
        inf = float('inf')
        preSum = [0] * (n+1)
        for i in xrange(n):
            preSum[i+1] = stones[i] + preSum[i]
        # dp[i][j][k] := min cost to merge subarray i~j into k piles.
        dp = [[[inf] * (K+1) for _ in xrange(n)] for _ in xrange(n)]
        for i in xrange(n):
            dp[i][i][1] = 0
            
        for l in xrange(2, n+1):  # subproblem length
            for i in xrange(n-l+1):
                j = i + l - 1 
                for k in xrange(2, K+1):
                    for m in xrange(i, j):
                        dp[i][j][k] = min(dp[i][j][k], dp[i][m][1] + dp[m+1][j][k-1])
                    if dp[i][j][K] < inf:
                        dp[i][j][1] = dp[i][j][K] + preSum[j+1] - preSum[i]
        
        return dp[0][-1][1]

446. Arithmetic Slices II - Subsequence

class Solution(object):
    def numberOfArithmeticSlices(self, A):
        # dp[i][j] the number of arithmetic ending with idx ith, increment is j 
        dp = [collections.defaultdict(int) for _ in A]
        re = 0
        for i in xrange(len(A)):
            for j in xrange(i):
                dp[i][A[i]-A[j]] += 1  # 把2个的也加入
                if A[i]-A[j] in dp[j]:
                    dp[i][A[i]-A[j]] += dp[j][A[i]-A[j]]  # dp[j]是至少两个的, dp[i]是比dp[j]多一个的, 因为上面加1了,所以这里不加1
                    re += dp[j][A[i]-A[j]] # dp[j][A[i]-A[j]] + 1 - 1 
        return re

329. Longest Increasing Path in a Matrix

class Solution(object):
    def longestIncreasingPath(self, matrix):
        def dfs(i, j):
            if not dp[i][j]:
                val = matrix[i][j]
                dp[i][j] = 1 + max(dfs(i-1, j) if i > 0 and matrix[i-1][j] < val else 0,  # dfs不是dp
                                   dfs(i+1, j) if i+1 < len(matrix) and matrix[i+1][j] < val else 0,
                                   dfs(i, j-1) if j > 0 and matrix[i][j-1] < val else 0,
                                   dfs(i, j+1) if j+1 < len(matrix[0]) and matrix[i][j+1] < val else 0)
                
            return dp[i][j]
        
        if not matrix or not matrix[0]: return 0
        dp = [[0] * len(matrix[0]) for _ in xrange(len(matrix))]
        return max(dfs(i, j) for i in xrange(len(matrix)) for j in xrange(len(matrix[0])))

518. Coin Change 2

class Solution(object):
    def change(self, amount, coins):
        """
        :type amount: int
        :type coins: List[int]
        :rtype: int
        """
        dp = [0] * (amount+1)
        dp[0] = 1  # so that dp[amou-coin=0] = 1
        for c in coins:
            for amou in xrange(1, amount+1):
                if amou >= c:
                    dp[amou] += dp[amou-c]
        
        return dp[amou]

322. Coin Change 

class Solution(object):
    def coinChange(self, coins, amount):
        """
        :type coins: List[int]
        :type amount: int
        :rtype: int
        """
        
        dp = collections.defaultdict(int)
        for money in xrange(1, amount+1):
            minCoins = 5000
            for coin in coins:
                if money-coin>=0 and dp[money-coin] != -1:
                    minCoins = min(dp[money-coin], minCoins)
            if minCoins == 5000:
                dp[money] = -1
            else:
                dp[money] = minCoins + 1
        return dp[amount]

174. Dungeon Game

第一次解,超时:

class Solution(object):       
     def dfs(i, j, val, minVal):
            val += d[i][j]
            
            if i == len(d) - 1 and j == len(d[0])-1:
                return minVal
            
            rightward = float('-inf')
            if j + 1 <= len(d[0]) - 1 :
                rightward = dfs(i, j+1, val, minVal)
            
            downward = float('-inf')
            if i + 1 <= len(d) - 1:
                downward = dfs(i+1, j, val, minVal)
            
            return max(rightward, downward) 
        
        re = dfs(0, 0, 0, 0)
        
        return -re + 1 if re <= 0 else 1

DP, 自底向上 

class Solution(object):
    def calculateMinimumHP(self, d):
        # hp[i][j] to store the min hp needed at position (i, j)
        # adding dummy row and column to hp would make the code cleaner
        r, c = len(d), len(d[0])
        hp = [[sys.maxint] * (c+1) for _ in xrange(r+1)]
        hp[r][c-1] = 1
        hp[r-1][c] = 1
        for i in xrange(r-1, -1, -1):
            for j in xrange(c-1, -1, -1):
                need = min(hp[i+1][j], hp[i][j+1]) - d[i][j]
                # 这里负数要设为1,因为如左下角10往上走的时候,才会是1+5+2
                hp[i][j] = need if need > 0 else 1
        return hp[0][0]

213. House Robber II

dp, 首尾是环,转换成2部分

  1. Rob houses 0 to n - 2;
  2. Rob houses 1 to n - 1.

class Solution(object):
    def rob(self, nums):
        if len(nums) == 1:
            return nums[0]
    
        def robHouse(nums, low, high):
            pre2 = pre1 = 0
            cur = 0
            for i in xrange(low, high):
                cur = max(pre1, pre2+nums[i])
                pre2 = pre1
                pre1 = cur
            return cur
        
        return max(robHouse(nums, 0, len(nums)-1), robHouse(nums, 1, len(nums)))

368. Largest Divisible Subset 

LIS算法的变形,原理一样

class Solution(object):
    def largestDivisibleSubset(self, nums):
        if not nums:
            return []
        nums = sorted(nums)
        dp = [[nums[0]] for i in nums]
        res = [nums[0]]
        for i in xrange(1, len(nums)):
            dp[i] = [nums[i]]
            for j in xrange(0, i):
                if nums[i] % nums[j] == 0:
                    dp[i] = dp[j] + [nums[i]] if len(dp[j]) >= len(dp[i]) else dp[i]
            res = dp[i] if len(res) < len(dp[i]) else res
        return res

263. Ugly Number

给ugly Number 2做铺垫

class Solution(object):
    def isUgly(self, num):
        rest = num
        if rest == 0:
            return False
        while rest != 1:
            if rest % 2 == 0:
                rest /= 2
            elif rest % 3 == 0:
                rest /= 3
            elif rest % 5 == 0:
                rest /= 5
            else:
                return False
            
        return True

别人的写法:汗颜,学习了。 num%p == 0 < num 相当于  Num%p == 0 and num%p < num

for p in 2, 3, 5:
    while num % p == 0 < num:
        num /= p
return num == 1

264. Ugly Number II

Explanation:

The key is to realize each number can be and have to be generated by a former number multiplied by 2, 3 or 5
e.g.
1 2 3 4 5 6 8 9 10 12 15..
what is next?
it must be x * 2 or y * 3 or z * 5, where x, y, z is an existing number.

How do we determine x, y, z then?
apparently, you can just traverse the sequence generated by far from 1 ... 15, until you find such x, y, z that x * 2, y * 3, z * 5 is just bigger than 15. In this case x=8, y=6, z=4. Then you compare x * 2, y * 3, z * 5 so you know next number will be x * 2 = 8 * 2 = 16.
k, now you have 1,2,3,4,....,15, 16,

Then what is next?
You wanna do the same process again to find the new x, y, z, but you realize, wait, do I have to
traverse the sequence generated by far again?

NO! since you know last time, x=8, y=6, z=4 and x=8 was used to generate 16, so this time, you can immediately know the new_x = 9 (the next number after 8 is 9 in the generated sequence), y=6, z=4.
Then you need to compare new_x * 2, y * 3, z * 5. You know next number is 9 * 2 = 18;

And you also know, the next x will be 10 since new_x = 9 was used this time.
But what is next y? apparently, if y=6, 6*3 = 18, which is already generated in this round. So you also need to update next y from 6 to 8.

Based on the idea above, you can actually generated x,y,z from very beginning, and update x, y, z accordingly. It ends up with a O(n) solution.

class Solution(object):
    def nthUglyNumber(self, n):
        dp = [1]*(n+1)
        n2 = n3 = n5 = 1

        for i in xrange(2, n+1):
            dp[i] = min(dp[n2]*2, dp[n3]*3, dp[n5]*5)
            
            if dp[n2]*2 == dp[i]:
                n2 += 1
            if dp[n3]*3 == dp[i]:
                n3 += 1
            if dp[n5]*5 == dp[i]:
                n5 += 1
        return dp[n]

467. Unique Substrings in Wraparound String

思路:

  1. The max number of unique substring ends with a letter equals to the length of max contiguous substring ends with that letter. Example "abcd", the max number of unique substring ends with 'd' is 4, apparently they are "abcd", "bcd", "cd" and "d".
  2. If there are overlapping, we only need to consider the longest one because it covers all the possible substrings. Example: "abcdbcd", the max number of unique substring ends with 'd' is 4 and all substrings formed by the 2nd "bcd" part are covered in the 4 substrings already.
  3. No matter how long is a contiguous substring in p, it is in s since s has infinite length.
  4. Now we know the max number of unique substrings in p ends with 'a', 'b', ..., 'z' and those substrings are all in s. Summary is the answer, according to the question.
class Solution(object):
    def findSubstringInWraproundString(self, p):
        dp = [0] * 26
        re = 0
        pre = 0
        for i in xrange(len(p)):
            if (ord(p[i]) - ord(p[i-1])) in (1, -25):
                cur = pre + 1
            else:
                cur = 1
            pre = cur
            val = ord(p[i])-ord('a')
            dp[val] = max(dp[val], cur)
        return sum(dp)

139. Word Break

思路:

class Solution(object):
    def wordBreak(self, s, wordDict):
        dp = [False] * len(s)
        for i in xrange(0, len(s)):
            for w in wordDict:
                if i-len(w)+1 >= 0:
                    if w == s[i-len(w)+1: i+1] and (dp[i-len(w)] or i-len(w) == -1):
                        dp[i] = True
        return dp[-1]

BFS

I use BFS to avoid useless states calculation like someone did in Coin Change. I do not check every substring but I check the substring whose length is possible (I store all distinct length of words in a list). Thus, no need to check backward from the current position one by one.

class Solution(object):
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: Set[str]
        :rtype: bool
        """
        queue = [0]
        slen = len(s)
        lenList = [l for l in set(map(len,wordDict))]
        visited = [0 for _ in range(0, slen + 1)]
        while queue:
            tmpqueue = []
            for start in queue:
                for l in lenList:
                    if s[start:start+l] in wordDict:
                        if start + l == slen:
                            return True
                        if visited[start + l] == 0:
                            tmpqueue.append(start+l)
                            visited[start + l] = 1
            queue, tmpqueue = tmpqueue, []
        return False

673. Number of Longest Increasing Subsequence


Given an unsorted array of integers, find the number of longest increasing subsequence.

Example 1:

Input: [1,3,5,4,7]
Output: 2
Explanation: The two longest increasing subsequence are [1, 3, 4, 7] and [1, 3, 5, 7].

Example 2:

Input: [2,2,2,2,2]
Output: 5
Explanation: The length of longest continuous increasing subsequence is 1, and there are 5 subsequences' length is 1, so output 5.

思路:

The idea is to use two arrays len[n] and cnt[n] to record the maximum length of Increasing Subsequence and the coresponding number of these sequence which ends with nums[i], respectively. That is:

len[i]: the length of the Longest Increasing Subsequence which ends with nums[i].
cnt[i]: the number of the Longest Increasing Subsequence which ends with nums[i].

Then, the result is the sum of each cnt[i] while its corresponding len[i] is the maximum length.

class Solution(object):
    def findNumberOfLIS(self, nums):
        if not nums:
            return 0
        count, dp = [0 for i in nums], [1 for i in nums]
        
        for i in xrange(len(nums)):
            for j in xrange(i):
                if nums[j] < nums[i]:
                    dp[i] = max(dp[i], dp[j]+1)
                    
            for j in xrange(i):
                if nums[j] < nums[i] and dp[i]-1 == dp[j]:
                    count[i] += count[j] if count[j] else 1
    
        res = 0
        for i in xrange(len(nums)):
            if dp[i] == max(dp):
                res += count[i] if count[i] else 1
        return res

576. Out of Boundary Paths

Note:

  1. Once you move the ball out of boundary, you cannot move it back.
  2. The length and height of the grid is in range [1,50].
  3. N is in range [0,50].
  1. 这道题乍一看很像一个标准的bfs,因为限定最多只能移动N次,我们只要bfs依次遍历发现出界就+1,当bfs的深度大于N的时候break。当然理论上是没有任何问题的,确实能得出正确答案,但是这里N的取值范围达到了50,我们对任意一个点bfs有四个方向(可以走回头路),那么复杂度达到了4^N,显然会超时。当然我会在文章后面给出bfs的做法,毕竟这是可以处理N比较小的情况的解法,让大家更熟悉bfs的套路。
  2. 我不知道你们有没有这种感觉,一般看到这个mod 1e9+7,这道题8成就是dp了,而且就是那种每个dp值你都得mod一下再去进行运算的那种。我觉得这算一个小技巧吧,看到mod 1e9+7就要想到dp。
  3. 显然,这里dp很好定义,我们定义dp[(i,j,N)]表示从i,j出发,最多走N步情况下满足题意的路径数量,那么我们所求也就是dp[(i,j,N)]。根据我们上面说的bfs的思路,递推式可得:
    dp[(i,j,N)] = dp[(i+1,j,N-1)] + dp[(i-1,j,N-1)] + dp[(i,j+1,N-1)] + dp[(i,j-1,N-1)]
class Solution(object):
    def findPaths(self, m, n, N, i, j):
        """
        :type m: int
        :type n: int
        :type N: int
        :type i: int
        :type j: int
        :rtype: int
        """
        mod = 10**9 + 7
        cache = collections.defaultdict(int)
        
        def helper(N, i, j, cache):
            if (i, j, N) in cache:
                return cache[(i, j, N)]
            
            if 0<=i<m and 0<=j<n:
                if N == 0:
                    cache[(i, j, N)] = 0
                    return 0
                
                for x, y in ((i-1, j), (i+1, j), (i, j-1), (i, j+1)):
                    cache[(i, j, N)] += helper(N-1, x, y, cache)
                
                return cache[(i, j, N)] % mod
            else:
                cache[(i, j, N)] = 1
                return 1
            
        return helper(N, i, j, cache)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def findPaths(m,n,N,i,j):
    mod = 10**9 + 7
    Q = collections.deque([(i,j,0)])
    res = 0
    while Q:
        x,y,step = Q.popleft()
        if step > N: break
        if 0<=x<m and 0<=y<n:
            Q.append((x+1,y,step+1))
            Q.append((x-1,y,step+1))
            Q.append((x,y+1,step+1))
            Q.append((x,y-1,step+1))
        else:
            res += 1
    return res % mod

 787. Cheapest Flights Within K Stops

Dijkstra's algorithm 每次更新剩余点到起始点的价格。

There are n cities connected by m flights. Each fight starts from city and arrives at v with a price w.

Now given all the cities and fights, together with starting city src and the destination dst, your task is to find the cheapest price from src to dst with up to k stops. If there is no such route, output -1.

Example 1:
Input: 
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 1
Output: 200
Explanation: 
The graph looks like this:


The cheapest price from city 0 to city 2 with at most 1 stop costs 200, as marked red in the picture.
Example 2:
Input: 
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 0
Output: 500
Explanation: 
The graph looks like this:


The cheapest price from city 0 to city 2 with at most 0 stop costs 500, as marked blue in the picture.
class Solution(object):
    def findCheapestPrice(self, n, flights, src, dst, K):
        """
        :type n: int
        :type flights: List[List[int]]
        :type src: int
        :type dst: int
        :type K: int
        :rtype: int
        """
        prices = collections.defaultdict(dict)
        
        for a, b, p in flights:
            prices[a][b] = p
        
        heap = [(0, src, K+1)]
        while heap:
            price, curPlace, steps = heapq.heappop(heap)
            if curPlace == dst:
                return price
            
            if steps > 0:
                for arr in prices[curPlace]:
                    heapq.heappush(heap, (price+prices[curPlace][arr], arr, steps-1))
        return -1
    

304. Range Sum Query 2D - Immutable 

Given a 2D matrix matrix, find the sum of the elements inside the rectangle defined by its upper left corner (row1, col1) and lower right corner (row2, col2).

Range Sum Query 2D
The above rectangle (with the red border) is defined by (row1, col1) = (2, 1) and (row2, col2) = (4, 3), which contains sum = 8.

Example:

Given matrix = [
  [3, 0, 1, 4, 2],
  [5, 6, 3, 2, 1],
  [1, 2, 0, 1, 5],
  [4, 1, 0, 1, 7],
  [1, 0, 3, 0, 5]
]

sumRegion(2, 1, 4, 3) -> 8
sumRegion(1, 1, 2, 2) -> 11
sumRegion(1, 2, 2, 4) -> 12
class NumMatrix(object):

    def __init__(self, matrix):
        """
        :type matrix: List[List[int]]
        """
        dpSum = [[0]*len(matrix[0]) for i in matrix]
        
        for i in xrange(len(matrix)):
            for j in xrange(len(matrix[0])):
                if j-1>=0:
                    dpSum[i][j] += dpSum[i][j-1]
                for row in xrange(0, i+1):
                     dpSum[i][j] += matrix[row][j]
        self.dpSum = dpSum

    def sumRegion(self, row1, col1, row2, col2):
        """
        :type row1: int
        :type col1: int
        :type row2: int
        :type col2: int
        :rtype: int
        """
        
        res = self.dpSum[row2][col2] 
        if col1 - 1 >= 0:
            res -= self.dpSum[row2][col1-1]
        if row1 -1 >= 0:
            res -= self.dpSum[row1-1][col2]
        if col1 - 1 >= 0 and row1 -1 >= 0:
            res += self.dpSum[row1-1][col1-1]
        return res

# Your NumMatrix object will be instantiated and called as such:
# obj = NumMatrix(matrix)
# param_1 = obj.sumRegion(row1,col1,row2,col2)

464. Can I Win

游戏的可以整理一篇,都是暴力破解(递归)加记忆。DP用于记录计算过的路径避免重复计算。

class Solution(object):
    def canIWin(self, maxChoosableInteger, desiredTotal):
        choose = tuple(i for i in xrange(1, maxChoosableInteger+1))
        visited = {}
        if sum(choose) < desiredTotal: return False  

        def helper(target, choose, visited):
            if len(choose) == 0:
                return False
            if choose in visited: return visited[choose]
            if target - max(choose) <= 0:
                visited[choose] = True
                return True
            for n in choose:
                leftChoose = tuple(x for x in choose if x!=n)
                # 对方输,我方就赢
                if not helper(target-n, leftChoose, visited):
                    visited[choose] = True
                    return True
                 
            visited[choose] = False
            return False
        
        return helper(desiredTotal, choose, visited)

523. Continuous Subarray Sum 

思路:

暴力破解就不说了,

在讨论里有个大神给出了时间复杂度是O(nn)的解法,他的思路非常巧妙,用了数学上的知识,下面给出他的解法的原理: 
假设: 

a[i]+a[i+1]+...+a[j]=n1k+q;a[i]+a[i+1]+...+a[j]=n1k+q;


如果存在一个n 

n>j且a[i]+a[i+1]+...+a[j]+...+a[n]=n2k+q;n>j且a[i]+a[i+1]+...+a[j]+...+a[n]=n2k+q;


那么 

a[j+1]+...+a[n]=(n2−n1)ka[j+1]+...+a[n]=(n2−n1)k


因此利用这一结果,可以从序列第一个元素开始遍历,不断累加上当前的元素,并求出当前和除以k后的余数,用一个映射记录该余数出现时的下标,如果同一个余数出现了两次,并且两次出现的下标之差大于1,那么就表示在这两个坐标之间的元素之和是k的倍数,因此就可以返回true,否则最后返回false。 
需要注意的两个地方: 
1. k可能取0,所以只有当k不为0时才对当前的和求余,同时针对于nums = [0, 0], k = 0的情况,需要添加一个初始映射(0, -1)来确保结果的正确。 
2. 下标之差至少为2才算是正确的情况,因为题目要求子序列长度至少为2,以上面的例子就是n至少等于j+2。 
具体实现见下面参考代码。

class Solution(object):
    def checkSubarraySum(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: bool
        """
        if len(nums) < 2:
            return False
        
        if k == 0:
            for i in range(0, len(nums) - 1):
                if nums[i] == 0 and nums[i+1] == 0:
                    return True
            return False
        
        k = abs(k)
        if len(nums) >= 2*k:
            return True
        
        Sum = [0]
        for x in nums:
            Sum.append((Sum[-1]+x)%k)
            
        Dict = {}
        for i in xrange(len(Sum)):
            if Dict.has_key(Sum[i]):
                if i - Dict[Sum[i]] >= 2:
                    return True
            else:
                Dict[Sum[i]] = i
            
        return False
        

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 a non-empty string containing only digits, determine the total number of ways to decode it.

Example 1:

Input: "12"
Output: 2
Explanation: It could be decoded as "AB" (1 2) or "L" (12).

Example 2:

Input: "226"
Output: 3
Explanation: It could be decoded as "BZ" (2 26), "VF" (22 6), or "BBF" (2 2 6).

i指的是字符串长度。

class Solution(object):
    def numDecodings(self, s):
        """
        :type s: str
        :rtype: int
        """
        if s[0] == '0':
            return 0
        
        dp = [0 for i in xrange(len(s)+1)]
        dp[0] = 1
        dp[1] = 0 if s[0] == '0' else 1
        for i in xrange(2, len(s)+1):
            one = int(s[i-1])
            two = int(s[i-2:i])
            
            if 1 <= one:
                dp[i] = dp[i-1]
                
            if 10 <= two <= 26:
                dp[i] += dp[i-2]
            
        return dp[-1]

639. Decode Ways II

 等于切出最后一位和最后2位,看1位的几种和2位的几种 分别乘以对应的dp[i-1], dp[i-2]

这题的解法上一题也可以用。

** = 11-19, 21-26.  如果** 是比如 37的话,应该是在one里面

class Solution(object):
    def numDecodings(self, s):
        one = {'0': 0, '1': 1, '2': 1, '3': 1, '4': 1, '5': 1, '6': 1, '7': 1, '8': 1, '9': 1, '*': 9}
        two = {'10': 1, '11': 1, '12': 1, '13': 1, '14': 1, '15': 1, '16': 1, '17': 1, '18': 1, '19': 1, '20': 1, '21': 1,
       '22': 1, '23': 1, '24': 1, '25': 1, '26': 1, '*0': 2, '*1': 2, '*2': 2, '*3': 2, '*4': 2, '*5': 2, '*6': 2,
       '*7': 1, '*8': 1, '*9': 1, '1*': 9, '2*': 6, '**': 15}
        
        dp = [1, one.get(s[0])]  # dp[i-2], dp[i-1]
        
        for i in xrange(1, len(s)):
            dp = dp[1], (one.get(s[i]) * dp[1] + two.get(s[i-1: i+1], 0) * dp[0]) % 1000000007
        return dp[-1]

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值