TLP-Task06学习笔记


本篇为Datawhale组队学习计划第21期LeetCode精选题目组Task06学习笔记。
初学,时间有点仓促,很多解法没有详细分析,未来会修改,见谅。
所有题目均参考了Datawhale学习文档,开源地址:
https://github.com/datawhalechina/team-learning-program/tree/master/LeetCodeTencent

043 字符串相乘

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/multiply-strings

给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。

示例

输入: num1 = “2”, num2 = “3”
输出: “6”
输入: num1 = “123”, num2 = “456”
输出: “56088”

说明:

num1 和 num2 的长度小于110。
num1 和 num2 只包含数字 0-9。
num1 和 num2 均不以零开头,除非是数字 0 本身。
不能使用任何标准库的大数类型(比如 BigInteger)或直接将输入转换为整数来处理。

思路

题目不允许转换为数字,选择模拟【竖式乘法】来解决。num2分别与num1各位相乘后再对应累加。

如果使用数组代替字符串存储结果,则可以减少对字符串的操作。

确定数组长度:
令m和n分别表示num1和num2长度,并且它们均不为0,则乘积的长度为m+n−1或m+n。
证明:最小时为10^ ((m-1)+(n-1))=10^(m+n-2), 最大时各位都是9,即(10^m-1)* (10^n-1)。易知上述结论。

在计算时,先不考虑进位,直接将结果储存在数组中,对应位置ansArr[i + j + 1] ,数字范围0~81。
计算全部完成后,再统一处理进位问题,每个元素依次除10,将对应进位值加在前一位。

Python实现

来自官方题解,自加注释。

class Solution:
    def multiply(self, num1: str, num2: str) -> str:
        if num1 == "0" or num2 == "0":
            return "0" 
        
        m, n = len(num1), len(num2)
        ansArr = [0] * (m + n)	#初始化,数组长度最大为m+n
        for i in range(m - 1, -1, -1):
            x = int(num1[i])
            for j in range(n - 1, -1, -1):
        #num2各位数分别与num1相乘,直接储存在数组中,ansArr[i + j + 1] 
                ansArr[i + j + 1] += x * int(num2[j])
        #最后整体处理进位,大于10的把进位加上去
        for i in range(m + n - 1, 0, -1):
            ansArr[i - 1] += ansArr[i] // 10
            ansArr[i] %= 10
        #转换输出,去掉首位可能的0(若结果为m+n-1位会出现0)
        index = 1 if ansArr[0] == 0 else 0
        ans = "".join(str(x) for x in ansArr[index:])
        return ans


#作者:LeetCode-Solution
#链接:https://leetcode-cn.com/problems/multiply-strings/solution/zi-fu-chuan-xiang-cheng-by-leetcode-solution/
#来源:力扣(LeetCode)
#著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

046 全排列

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/permutations

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3]
输出:
[ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

思路

(主要参考官方题解,并且推荐下这个题解https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liweiw/,数据结构渣渣非常感动。)

回溯法 :一种通过探索所有可能的候选解来找出所有的解的算法。如果候选解被确认不是一个解的话(或者至少不是最后一个解),回溯算法会通过在上一步进行一些变化抛弃该解,即回溯并且再次尝试。

这个问题可以看作有n个排列成一行的空格,我们需要从左往右依此填入题目给定的n个数,每个数只能使用一次。那么很直接的可以想到一种穷举的算法,即从左往右每一个位置都依此尝试填入一个数,看能不能填完这n个空格,在程序中我们可以用「回溯法」来模拟这个过程。

优化(不要求字典序输出时),不使用标记数组:

将题目给定的n个数的数组划分成左右两个部分,左边的表示已经填过的数,右边表示待填的数,我们在递归搜索的时候只要动态维护这个数组即可。

举个简单的例子,假设我们有 [2, 5, 8, 9, 10] 这 5 个数要填入,已经填到第 3 个位置,已经填了 [8,9] 两个数,那么这个数组目前为 [8, 9 | 2, 5, 10] 这样的状态,分隔符区分了左右两个部分。假设这个位置我们要填 10 这个数,为了维护数组,我们将 2 和 10 交换,即能使得数组继续保持分隔符左边的数已经填过,右边的待填 [8, 9, 10 | 2, 5]

Python实现

官方题解

class Solution:
    def permute(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        def backtrack(first = 0):
            # 所有数都填完了
            if first == n:  
                res.append(nums[:])
            for i in range(first, n):
                # 动态维护数组
                nums[first], nums[i] = nums[i], nums[first]
                # 继续递归填下一个数
                backtrack(first + 1)
                # 撤销操作
                nums[first], nums[i] = nums[i], nums[first]
        
        n = len(nums)
        res = []
        backtrack()
        return res


#作者:LeetCode-Solution
#链接:https://leetcode-cn.com/problems/permutations/solution/quan-pai-lie-by-leetcode-solution-2/
#来源:力扣(LeetCode)
#著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

053 最大子序和

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximum-subarray

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

进阶:

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

思路

(这里不用分治法了)
动态规划,max[i] = Max(max[i-1] + nums[i], nums[i])。更新后的值若比之前大,则取加和后的新值,否则直接取当前值。

首先对数组进行遍历,当前最大连续子序列和为 sum,结果为 ans
如果 sum > 0,则说明 sum 对结果有增益效果,则 sum 保留并加上当前遍历数字
如果 sum <= 0,则说明 sum 对结果无增益效果,需要舍弃,则 sum 直接更新为当前遍历数字
每次比较 sum 和 ans的大小,将最大值置为ans,遍历结束返回结果
作者:guanpengchn
链接:https://leetcode-cn.com/problems/maximum-subarray/solution/hua-jie-suan-fa-53-zui-da-zi-xu-he-by-guanpengchn/

Python实现

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        max = nums[0]
        for i in range( 1,len(nums)):
            if nums[i] + nums[i-1] > nums[i]:
               nums[i] += nums[i-1]
            if nums[i] > max :
               max = nums[i]
        return max

补充知识

回溯与深度优先

作者:liweiwei1419https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liweiw/

以下是维基百科中「回溯算法」和「深度优先遍历」的定义。

回溯法 采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:
1.找到一个可能存在的正确的答案;
2.在尝试了所有可能的分步方法后宣告该问题没有答案。
深度优先搜索 算法(Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。这个算法会 尽可能深 的搜索树的分支。当结点 v 的所在边都己被探寻过,搜索将 回溯 到发现结点 v 的那条边的起始结点。这一过程一直进行到已发现从源结点可达的所有结点为止。如果还存在未被发现的结点,则选择其中一个作为源结点并重复以上过程,整个进程反复进行直到所有结点都被访问为止。

「回溯算法」与「深度优先遍历」

「回溯算法」强调了「深度优先遍历」思想的用途,用一个 不断变化 的变量,在尝试各种可能的过程中,搜索需要的结果。强调了回退操作对于搜索的合理性。而「深度优先遍历」强调一种遍历的思想(与之对应的遍历思想是「广度优先遍历」。前者用于搜索时的优点:状态容易回退,所需的状态信息少,可直接用系统栈等)。

回溯算法用于 搜索一个问题的所有的解 ,通过深度优先遍历的思想实现。

与动态规划的区别

共同点
用于求解多阶段决策问题。多阶段决策问题即:
求解一个问题分为很多步骤(阶段);
每一个步骤(阶段)可以有多种选择。
不同点
动态规划只需要求我们评估最优解是多少,最优解对应的具体解是什么并不要求。因此很适合应用于评估一个方案的效果
回溯算法可以搜索得到所有的方案(当然包括最优解),但是本质上它是一种遍历算法,时间复杂度很高。

其他

正在补数据结构知识,放一篇正在看的公众号文章递归详解,介绍了递归和动态、分治等的关系。

猜你喜欢

转载自blog.csdn.net/cosima0/article/details/112759078