算法刷题记录--python

欢迎来到我的频道

这是我个人学习算法刷题的笔记,仅供参考交流。

1.组合总和

题目:找出所有相加之和为n的k个数的组合,且满足条件:
1.只使用数字1~9
2.每个数最多使用一次
返回所有可能的有效组合列表

方法1:组合型回溯+剪枝

分析:
设还需要选d=k-m(m:已选数字数量)
设还需要选和为 c 的数字(初始为n,没选一个数字j,则下一个为c-j)
边界条件:
1.剩余数字i 数目不够:i<d
2. c < 0
3. 剩余数字选最大的,和也不够 c。例如:i=5,还需数字d=2,当c > i +…+ (i-d+1)时,直接返回; 公式可简化为: i +…+ (i-d+1) = i + (i-d+1) / 2 * d

class CombinedSum:
    def conbined_sum(self, n: int, k: int) -> list[int]:
        ans = []
        path = []

        def dfs(i, c):
            d = k - len(path)  # 获取仍需选数字,注意:当 d=0 时,边界条件3的公式等于0,即c=0的情况下,才不会return,结合判断len(path)==k同时成立,则结果正确
            if c < 0 or c > (i + i - d + 1) / 2 * d:  # 过滤边界条件2,3
                return
            if len(path) == k:   # 判定数字数量 k;  
                ans.append(path.copy())
                return
            for j in range(i, d - 1, -1):  # 过滤边界条件1
                path.append(j)
                dfs(j - 1, c - j)
                path.pop()
                
        dfs(9, n)
        return ans

2.括号生成

题目:数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

分析:
n对括号,即一共有2n个括号,其中左括号n个;
拆解为将n个左括号插入2n个位置中,其余位置为右括号;

第一步:枚举path[i] 是是左括号还是右括号
第二步:构造>=i 的部分
第三步:构造>=i+1 的部分

设 左括号个数为left,只要 left<n 就可以选择左括号;
则右括号数量为:i - left,只要i - left < left,就可以选择右括号
当i=2n时,导入结果集

class Solution:
    def parenthesisGeneration(self, n: int) -> list:
        m = n * 2
        ans = []
        path = [''] * m  # 可以默认为[]

        def dfs(i, left):
            if i == m:  # 判断为true则导入结果集
                ans.append(''.join(path))
                return
            if left < n:  #  left<n 就可以选择左括号
                path[i] = '('
                dfs(i + 1, left + 1)
            if i - left < left:  # i - left < left,就可以选择右括号
                path[i] = ')'
                dfs(i + 1, left)

        dfs(0, 0)
        return ans

3.全排列

题目:给定一个不含重复数字的数组nums,返回所有可能的全排列组合
分析:
加入nums的长度是n,则每个组合长度也是:n=len(nums)
设path记录已选数字,s记录未选数组
边界条件:当len(path)==len(nums)时,导入结果集

方法一(回溯算法1)

第一步:从s中获取枚举填入path的数字num
第二步:构造未选集合s,s = s- {num}

class Solution:
    def full_permutation(self, nums: list[int]) -> list:
        n = len(nums)
        ans = []
        path = []

        def dfs(s):
            if len(path) == n:
                ans.append(path.copy())
                return
            for num in s:
                path.append(num)
                dfs(s - {num})
                path.pop()

        dfs(set(nums))
        return ans

方法二(回溯算法2)

第一步:从s中获取枚举path[i]填入的数字num
第二步:构造排列>=i的数,未选集合为s
第三步:构造排列>=i+1的数,未选集合为s-{num}

class Solution:
    def full_permutation(self, nums: list[int]) -> list:
        n = len(nums)
        ans = []
        path = [0] * n

        def dfs(i, s):  # i:当前枚举指针, s:未选数字集合
            if i == n:  # 即len(path) == len(nums)
                ans.append(path.copy())
                return
            for num in s:
                path[i] = num
                dfs(i+1, s-{num})

        dfs(0, set(nums))
        return ans

方法三:

用一个布尔数组onPath记录在path中的数据
如果num[i]在path中,则onPath[i]为真

    def full_permutation_1(self, nums: list[int]) -> list:
        n = len(nums)
        ans = []
        path = [0] * n
        onPath = [False] * n
        def dfs(i):
            if i == n:
                ans.append(path.copy())
                return
            for j in range(n):
                if not onPath[j]:
                    path[j] = nums[j]
                    onPath[j] = True
                    dfs(i+1)
                    onPath[j] = False
        dfs(0)
        return ans

4.N皇后

题目:按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

分析:

不同行,不同列=》每行每列都只有一个皇后

用一个长为n的数组记录皇后的位置,即第i行的皇后在col[i]列
枚举col所有的排列,即每行只选一个,每列只选一个

最后判断右上和左上是否右其他皇后:

"""
通过for循环遍历所有小于i的行:for j in range(i)右上:i + col[i] == i+1 + col[i+1];  左上:i - col[i] == i-1 - col[i-1],相同则有其他皇后,不同则没有
"""
        def valid(r, c):  # r:行,c:行位置的值(对应棋盘的列)
            for R in range(r):
                C = col[R]
                if r + c == R + C or r - c == R - C:
                    return False
            return True

全部代码:

class Queen:
    def chess_queen(self, n: int) -> list[[str]]:
        ans = []
        col = [0] * n

        def valid(r, c):   # 用于判断左上和右上都没有皇后
            for R in range(r):
                C = col[R]
                if r + c == R + C or r - c == R - C:
                    return False
            return True

        def dfs(r, s):
            if r == n:
                ans.append(['.' * c + 'q' + '.' * (n - c - 1) for c in col])
                return
            for c in s:
                if valid(r, c):
                    col[r] = c
                    dfs(r+1, s-{c})

        dfs(0, set(range(n)))
        return ans

可以把valid方法去掉,判断加入dfs方法中的

class Queen:
    def chess_queen(self, n: int) -> list[[str]]:
        ans = []
        col = [0] * n

        def dfs(i, s):
            if i == n:
                ans.append(['.' * c + 'q' + '.' * (n - c - 1) for c in col])
                return
            for c in s:
                if all(r+c != R+col[R] and r-c != R-col[R] for R in range(r))   # 通过python的all方法更简便,但是也会更难以阅读
                    col[i] = c
                    dfs(i+1, s-{c})

        dfs(0, set(range(n)))
        return ans

5.分糖果(贪心算法)

题目:n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
*每个孩子至少分配到 1 个糖果。
*相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。

分析:
设两个参数,左边:left,右边:right。
当 left > right 时,ratings[left] = ratings[right] + 1;
当 right > left 时,ratings[right] = ratings[left] + 1;

步骤:
第一步:初始化数组,长度为len(ratings),数组内所有数为1;
第二步:循环所有 left > right 的情况;
第三步:循环所有 right > left 的情况;
最后:通过sum()方法求糖果总数并返回

class Solution:
    def candy(self, ratings: List[int]) -> int:
        n = len(ratings)
        path = [1] * n
        for i in range(1, n):

            if ratings[i] > ratings[i-1]:
                path[i] = path[i-1] + 1
            else:
                continue
        
        for i in range(1, n):
            if ratings[i] < ratings[i-1]:
                path[i-1] = path[i] + 1
            else:
                continue
        ans = sum(path)
        return ans

6.盛最多水的容器

题目:
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。
在这里插入图片描述

方法一:双指针

分析:
本题结果为求两数之间的容积,宽为:right - left;高为:min(height[left], height[right])
则面积:s = min(height[left], height[right]) * (right - left));
以数组左右两边为指针,每向内缩一步,则宽:right-left 必然-1;
设向内移动长板,则 min(height[left], height[right]) 可能变小或不变,面积必然减小;
设向内移动短板,则 min(height[left], height[right]) 可能变大,面积有可能增大。

步骤:
第一步:设置左,右和最大值的初始值;
第二步:判断 height[left] 和 height[right] 大小,并更新面积最大值,同时短板向内移(left+1 or right-1) ;
第三步:判断 left=right 时结束,并返回最大值

class Solution:
    def maxArea(self, height: List[int]) -> int:
        res, left, right = 0, 0, len(height)-1
        while left < right:
            wide = right - left
            if height[left] <= height[right]:
                res = max(res, height[left] * wide)
                left = left + 1
            else:
                res = max(res, height[right] * wide)
                right = right - 1
        return res

方法二:暴力

分析:
求面积等于两边中的短边,乘以两边的距离;
用一个数组,记录所有的面积,最后返回最大值即可

步骤:
第一步:声明一个空列表:path = [];
第二步:通过镶套for循环遍历所有两边的可能性,并将面积添加到path;
第三步:返回最大值,max(path)。

    # 注:全是暴力,没有一点点技巧,参考意义不大。
    def maxArea(self, height: list[int]) -> int:
        path = []
        for i in range(len(height)):
            for j in range(i + 1, len(height)):
                wide = j - i
                if height[i] >= height[j]:
                    hei = height[j]
                else:
                    hei = height[i]
                path.append(wide * hei)
        ans = max(path)
        return ans

7.数组中的第K个最大元素

题目:
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

方式一:python内置排序

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        nums.sort(reverse=True)
        return nums[k-1]

方法二:递归

分析:
在nums中随机一个数standard为基准,将nums中数字划分为三部分:small, equal, big ;
当k <= len(big) 时,目标在big列表中,递归big;
当k > len(nums) - len(samll)时,目标在small列表中,递归small;
当不属于以上两种情况,目标在equal列表中,返回standard。

    def findKthLargest(self, nums, k):
        def quich_select(nums, k):
            pivot = random.choice(nums)
            small, equal, big = [], [], []
            for num in nums:
                if num < pivot:
                    small.append(num)
                elif num > pivot:
                    big.append(num)
                else:
                    equal.append(num)
            if k <= len(big):
                return quich_select(big, k)
            if len(nums) - len(small) < k:
                return quich_select(small, k-len(nums)+len(small))
            return pivot
        return quich_select(nums, k)

8.最长公共子序列

题目:
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

方式一:回溯

分析:
要获取两个字符串的公共序列,先通过回溯的递归方法(见上面全排类,方法类似),获取两个字符串各自的子序列,最后通过集合的intersection获取公共部分,设最大数ans = 0,遍历公共集合,有长度大于ans时更新即可。
步骤:
第一步:设置初始值:字符串子序列res,子序列参数:path ,结果:ans;
第二步:确定子序列回溯的边界值,当字符串长度为0时返回;
第三步:循环text字符串,并赋值path;
第四步:构造剩余 i 部分text内容,dfs(text[ i: ])
最后:比较公共部分,更新字符串长度即可

"""
纯发疯作品,没什么优化,也没什么参考价值。
"""

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        res = []
        path = []
        
        def all_arrange(text):
            res.append(''.join(path.copy()))
            if len(text) == 0:
                return
            for i in range(len(text)):
                path.append(text[i])
                all_arrange(text[i+1:])   # 因为要保持相对顺序,因此下一个必须在当前枚举后面
                path.pop()
            return set(res)   # 返回集合,方便后续取交集

        text_path_1 = all_arrange(text1)  # text1字符串的子序列
        res.clear()
        text_path_2 = all_arrange(text2)  # text2字符串的子序列
        pub = text_path_1.intersection(text_path_2)  # 获取公共子序列
        ans = 0  # 初始化最大长度
        for i in pub:
            if len(i) > ans:
                ans = len(i)
        return ans

方式二:回溯-2

分析:
求字符串的子序列,本质是选或不选的问题;设字符串s和t。
当前操作:考虑s[i] 和 t[j] 选或不选;
子问题:s的前i个字符 和 t的前j个字符的LCS(两个序列中最长公共子序列的长度)
下一子问题:
s的前 i-1 个字符 和 t的前 j 个字符的LCS;
or s的前 i-1 个字符 和 t的前 j-1 个字符的LCS;
or s的前 i 个字符 和 t的前 j-1 个字符的LCS;

步骤:
第一步:判断边界值:当任意字符串为空时,返回 0;
第二步:判断 s[i] == t[j] 时,递归 (i-1 , j-1) +1;(注:+1是为了记录LCS长度+1)
第三步:判断 s[i] != t[j] 时,在(i-1, j)和 (i, j-1)中递归LCS大的一个。

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
    n = len(text1)
    m = len(text2)
    def dfs(i, j):
    	if i<0 or j<0:
    		return 0
    	if text1[i] == text2[j]:
    		return dfs(i-1, j-1) + 1
    	return max(dfs(i-1, j), dfs(i, j-1))
    return dfs(n-1, m-1)

方法三:动态规划

分析:
设一个二维数组f[n+1][m+1](注:n/m分别是两个字符串长度,+1是为了防止循环超出范围);
遍历text1[i] 是否等于text2[j],当等于时,更新二维数组f[i+1][j+1] = f[i][j] + 1;
不等于时,f[i+1][j+1] 更新为附近数字的最大值
初始状态:当n=0 时,f[0][j] =0 ;当m=0 时,f[i][0] =0;因此f[i][j]默认为0;
遍历方向:因为f[i][j]依赖于f[i-1][j-1],f[i-1][j],f[i][j-1];所以是从小到大;
最终结果:由于 f[i][j] 的含义是 text1[0:i-1] 和 text2[0:j-1] 的最长公共子序列。我们最终希望求的是 text1 和 text2 的最长公共子序列。因此返回结果f[n][m]

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
    n = len(text1)
    m = len(text2)
    f = [[0] * (m+1) for a in range(n+1)]
    for i, x in enumerate(text1):
    	for j, y in enumerate(text2):
    		if x == y:
    			f[i+1][j+1] = f[i][j] + 1
    		else:
    			f[i+1][j+1] = max(f[i+1][j], f[i][j+1] )
    return f[n][m]

9.最大子数组和

题目:
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组是数组中的一个连续部分。

方法1:动态规划

分析:
设一个数组db[],长度为len(nums),与目标数组保持一致;
db[i]是nums[i]为结尾的最大连续和
转移方程:当db[i-1] < 0 时,说明db[i-1]对nums[i]是负影响,不如仅保留nums[i]
得到判断:
当 db[i-1] <= 0 时, db[i] = nums[i];
当 db[i-1] > 0,db[i] = db[i-1] + nums[i]
结果:返回db数组中最大值
可以压缩在nums中直接修改,把空间复杂度压缩为O(1)

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

后续待添加,刷到哪加到哪!

  • 18
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值