LeetCode Top 100(二)

64. Minimum 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.

Example:

Input:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
Output: 7
Explanation: Because the path 1→3→1→1→1 minimizes the sum.

思路分析:

详细的分析过程参见: https://leetcode.com/problems/minimum-path-sum/discuss/23457/C%2B%2B-DP

方法是动态规划,即 dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j] ,但是这里要特别注意的是对边界条件的处理(即对 dp[0][0] 的处理、对 dp[0][j] 的处理以及对 dp[i][0] 的处理)。

代码如下:

class Solution(object):
    def minPathSum(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        if not grid:
            return None
        dp = [grid[0][0]] * len(grid)
        for i in range(1, len(grid)):
            dp[i] = dp[i-1] + grid[i][0]
        for j in range(1, len(grid[0])):
            dp[0] += grid[0][j]
            for i in range(1, len(grid)):
                dp[i] = min(dp[i-1], dp[i]) + grid[i][j]
        
        return dp[-1]

代码说明如下:

  • 第 9 行体现的是对 dp[0][0] 这个边界条件的处理;
  • 第 10 ~ 11 行体现的是对 dp 矩阵第 1 列的处理,这里是固定 j=0 ,让 i 从 1 增加到 len(grid)
  • 然后第 12 ~ 15 行是从第 1 列开始逐列地向右推进,直至到达 grid 的最后一列。在这期间,第 13 行首先进行 dp[0] += grid[0][j] 是要先计算出 dp 最上面的一个元素,这实际上是对 dp[0][j] 这个边界条件的处理。第 15 行便是动态规划的核心。
  • 时间复杂度:O(m \times n)mn 分别是 grid 的行数和列数),空间复杂度: O(1)

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?

Note: Given n will be a positive integer.

Example 1:

Input: 2
Output: 2
Explanation: There are two ways to climb to the top.
1. 1 step + 1 step
2. 2 steps

Example 2:

Input: 3
Output: 3
Explanation: There are three ways to climb to the top.
1. 1 step + 1 step + 1 step
2. 1 step + 2 steps
3. 2 steps + 1 step

思路分析:

参见: https://leetcode.com/problems/climbing-stairs/discuss/25299/Basically-it's-a-fibonacci.

本题的实质是一个斐波那契数列问题,解决方法是动态规划,核心是 dp[n] = dp[n-1] + dp[n-2] 。参照《剑指offer》上的斐波那契数列问题,可以写出如下代码:

class Solution(object):
    def climbStairs(self, n):
        """
        :type n: int
        :rtype: int
        """
        pre, cur = 1, 2
        for i in range(n-1):
            pre, cur = cur, pre + cur
        
        return pre

72. Edit Distance

题目描述:

Given two words word1 and word2, find the minimum number of operations required to convert word1 to word2.

You have the following 3 operations permitted on a word:

  1. Insert a character
  2. Delete a character
  3. Replace a character

Example 1:

Input: word1 = "horse", word2 = "ros"
Output: 3
Explanation: 
horse -> rorse (replace 'h' with 'r')
rorse -> rose (remove 'r')
rose -> ros (remove 'e')

Example 2:

Input: word1 = "intention", word2 = "execution"
Output: 5
Explanation: 
intention -> inention (remove 't')
inention -> enention (replace 'i' with 'e')
enention -> exention (replace 'n' with 'x')
exention -> exection (replace 'n' with 'c')
exection -> execution (insert 'u')

思路分析:

参见: https://leetcode.com/problems/edit-distance/discuss/25846/C%2B%2B-O(n)-space-DP

基于上述链接中作者的分析,个人觉得在这个问题中,各状态之间的转移轨迹和机器人在二维方格中的运动过程有点类似(只不过这一次除了可以向右走和向下走之外,还可以沿斜向右下 45 ° 角的方向走)。状态转移方程如下:
\text{dp[i][j]} = \left\{ \begin{aligned} & \text{dp[i-1][j-1]}, & \text{if word1[i-1] = word2[j-1]} \\ & \min \left\{ \text{dp[i-1][j-1], dp[i-1][j], dp[i][j-1]} \right\} + 1, & \text{if word1[i-1]}\ \ne \text{word2[j-1]} \end{aligned} \right. \tag 1
如果这个问题换一个角度来思考:假设初始时给定的两个单词分别为 word1word2 ,由于总共允许的操作方法只有三种:插入、删除、替换,将它们分别记为 O_iO_dO_r (其中 O_i = O_d = O_r = 1 ),则从 word1word2 必是由以上三种操作经过若干种排列组合而得到的。记总的操作数为 S ,则有
S = k \cdot O_i + m \cdot O_d + n \cdot O_r \tag 2
其中,k \in \mathbb{N}m \in \mathbb{N}n \in \mathbb{N}

现在我们要求的是 \underset{k, m, n}{\arg \min S} ,如果一下子要求出 k,m,n 的值肯定不好求。那怎么办呢?我们可以将这个大问题分解一下,假设如下是针对某一个具体问题的 dp 矩阵:

[[ 0  1  2  3  4  5  6  7  8  9]
 [ 1  1  2  3  4  5  6  7  8  9]
 [ 2  1  2  2  3  4  5  6  7  8]
 [ 3  2  1  2  3  4  5  6  7  8]
 [ 4  3  2  1  2  3  4  5  6  7]
 [ 5  4  3  2  1  2  3  4  5  6]
 [ 6  5  4  3  2  1  2  3  4  5]
 [ 7  6  5  4  3  2  1  2  3  4]
 [ 8  7  6  5  4  3  2  1  2  3]
 [ 9  8  7  6  5  4  3  2  1  2]
 [10  9  8  7  6  5  4  3  2  1]]

这个矩阵向下是 i 轴,向右是 j 轴。考察矩阵中的任意一个元素 dp[i][j] ,它是怎么得来的呢?首先一种最省操作数的方式是什么也不做,即 dp[i][j] = dp[i-1][j-1] ;另外一种是必须要做一步,这个时候 dp[i][j] 可以由它正上方、正左方或左上角的元素加 1 得到,具体是哪个元素加 1 要看哪个元素的值最小。这样,我们从两个单词都为空的状态开始,逐个地添加字母到 word1word2 中,每添加一个字母,我们都计算一次最小操作数。当两个单词都添加完所有的字母后,一定能在上述的 dp 矩阵中找到一条路径,使得不仅最终总的操作数是最少的,甚至每新增一个字母时,这条路径的操作数始终都是最少的(如上述矩阵中恒为 1 的那条主对角线路径)。

上述的思路本质上还是穷举,只不过每添加一个字母都会进行一次动态规划,而且会将历史最优路径记录下来,避免重复地对已经规划过的路径重新进行规划,因此时间复杂度必为 O(m \times n)m,n 分别是两单词的长度)。可以优化的地方是空间上的消耗,只需要上述矩阵中的一列就可以了,没必要保存整个 dp 矩阵。

空间优化前的代码:

class Solution(object):
    def minDistance(self, word1, word2):
        """
        :type word1: str
        :type word2: str
        :rtype: int
        """
        if not word1 or not word2: # 这里是为了避免后面出现索引越界的情况
            return len(word1) or len(word2)
        l1, l2 = len(word1), len(word2)
        dp = [[0] * (l2+1) for _ in range(l1+1)] # 注意dp矩阵是多一行和一列
        for i in range(1, l1+1): # i要往后多遍历一位,下同
            dp[i][0] = i
        for j in range(1, l2+1): # j也要往后多遍历一位,下同
            dp[0][j] = j
        for i in range(1, l1+1):
            for j in range(1, l2+1):
                if word1[i-1] == word2[j-1]:
                    dp[i][j] = dp[i-1][j-1]
                else:
                    dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1
        
        return dp[l1][l2]

空间优化后的代码:

class Solution(object):
    def minDistance(self, word1, word2):
        """
        :type word1: str
        :type word2: str
        :rtype: int
        """
        if not word1 or not word2:
            return len(word1) or len(word2)
        l1, l2 = len(word1), len(word2)
        dp = [0] * (l2+1)
        for j in range(1, l2+1): # 初始化第一行
            dp[j] = j
        for i in range(1, l1+1): # 索引必须要到l1+1
            upleft = dp[0] # 由于要用到左上角的值,所以要单独记下来
            dp[0] = i # 初始化第一列
            for j in range(1, l2+1): # 索引必须要到l2+1
                upleft_dynamic = dp[j] # 左上角的值必须要动态更新
                if word1[i-1] == word2[j-1]: # 这里是word1[i-1]和word2[j-1]比,而不是word1[i]和word2[j]比
                    dp[j] = upleft
                else:
                    dp[j] = min(upleft, dp[j], dp[j-1]) + 1
                upleft = upleft_dynamic
        
        return dp[l2] # 或者 return dp[-1]

75. Sort Colors

题目描述:

Given an array with n objects colored red, white or blue, sort them in-place so that objects of the same color are adjacent, with the colors in the order red, white and blue.

Here, we will use the integers 0, 1, and 2 to represent the color red, white, and blue respectively.

Note: You are not suppose to use the library's sort function for this problem.

Example:

Input: [2,0,2,1,1,0]
Output: [0,0,1,1,2,2]

Follow up:

  • A rather straight forward solution is a two-pass algorithm using counting sort.
    First, iterate the array counting number of 0's, 1's, and 2's, then overwrite array with total number of 0's, then 1's and followed by 2's.
  • Could you come up with a one-pass algorithm using only constant space?

思路分析: https://leetcode.com/problems/sort-colors/discuss/26472/Share-my-at-most-two-pass-constant-space-10-line-solution

代码:

class Solution(object):
    def sortColors(self, nums):
        """
        :type nums: List[int]
        :rtype: None Do not return anything, modify nums in-place instead.
        """
        left, right = 0, len(nums)-1
        for i in range(len(nums)):
            while nums[i] == 2 and i < right:
                nums[i], nums[right] = nums[right], nums[i]
                right -= 1
            while nums[i] == 0 and i > left:
                nums[i], nums[left] = nums[left], nums[i]
                left += 1
            if i == right:
                break
        
        return nums

说明:上述代码中,left 是 0 的右边界,right 是 2 的左边界,如下:

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

推荐阅读更多精彩内容

  • 1.蜂蜜是唯一不会腐烂的食物,保质期可以持续三零零四年。 2.辣只是身体的一种痛...
    小小西城阅读 505评论 0 0
  • 中秋假三天,妈妈几天前就打电话询问回家过节的事,妈妈期待我们回家的急切心情可想而知。年年回家过节,是毋庸置疑,离家...
    独孤草原狼阅读 239评论 0 0
  • 我有一个丰富多彩的五一假期。 一天看海,一天逛街,一天学习。 有着家人的陪伴,感觉也好了不少呢----...
    王jioer鹏阅读 249评论 0 0