Leetcode刷题(慢慢记录)


记录下最近刷题的情况。

DFS算法适用

DFS算法会用到辅助栈(python中用list),相应的BFS算法则用到队列(python中队列主要用deque、queue或list)。个人偏爱DFS,故而刷这一类型的题都是用DFS算法。

图像渲染

链接: 图像渲染
说明:给你一个初始坐标 (sr, sc) 表示图像渲染开始的像素值(行 ,列)和一个新的颜色值 newColor,让你重新上色这幅图像。
其实就是将图像中与初始坐标像素值相等且连通的区域进行染色

class Solution(object):
    def floodFill(self, image, sr, sc, newColor):
        """
        :type image: List[List[int]]
        :type sr: int
        :type sc: int
        :type newColor: int
        :rtype: List[List[int]]
        """
        ##思路,dfs
        ##递归寻找与oldcolor相同的且连通的像素点
        def dfs(sr, sc):
            # if image[sr][sc] != oldcolor:
            #     image[sr][sc] = newColor
            #以上if语句会与下面冲突
            #应先判断边界条件以及像素,再进行填充
            if sr < 0 or sr >= r or sc < 0 or sc >= c or image[sr][sc] != oldcolor:
                return
            #染色后递归就会避免重复遍历
            image[sr][sc] = newColor
            dfs(sr+1, sc)
            dfs(sr-1, sc)
            dfs(sr, sc+1)
            dfs(sr, sc-1)
        
        oldcolor = image[sr][sc] 
        ##特列处理
        if oldcolor == newColor:
            return image

        r, c = len(image), len(image[0])
        dfs(sr, sc)
        return image
        ##调用
        ##solved = Solution()
        ##res = solved.floodFill(image, sr, sc, newColor)

被围绕的区域

链接: 被围绕的区域.
说明:给定一个二维的矩阵,包含 ‘X’ 和 ‘O’(字母 O)。找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。
补充:边界上的且与之连通的不算被围绕
思路:首先把边界的O以及与它连通的进行替换,避免后面dfs递归遍历进去

class Solution(object):
    def solve(self, board):
        """
        :type board: List[List[str]]
        :rtype: None Do not return anything, modify board in-place instead.
        """
        ##首先将边界为o的点且与其相连的o点进行替换   
        ##这里用b进行替换
        ##故而dfs算法的if语句需要找到是O的位置且不超过边界   
        def dfs(i, j):
            if i < 0 or i >= r or j < 0 or j >= c or board[i][j] == 'X' or board[i][j] == 'b':
                return
            board[i][j] = 'b'
            dfs(i-1, j)
            dfs(i+1, j)
            dfs(i, j-1)
            dfs(i, j+1)
       ##特例
        if not board:return
        ##以下是边界处理
        ##用isedge表示是否在边界
        ##在边界上则dfs搜索与之连通的区域,进行替换
        r, c = len(board), len(board[0])
        i, j = 0, 0
        for i in range(r):
            for j in range(c):
                isedge = bool(i == 0 or j == 0 or i == r-1 or j == c-1)
                if isedge and board[i][j] == 'O':
                    dfs(i, j)
		#以下进行被围绕O点的进行X填充,其他则返回原值O
        for i in range(r):
            for j in range(c):
                if board[i][j] == 'O':
                    board[i][j] = 'X'
                if board[i][j] == 'b':
                    board[i][j] = 'O'
        return board

岛屿数量

链接: 岛屿数量.
说明:类似被围绕区域,统计有几个岛屿(连通的算一个)
思路:dfs算法,然后计数

class Solution(object):
    def numIslands(self, grid):
        """
        :type grid: List[List[str]]
        :rtype: int
        """
        ##特例
        if not grid: return 0
        r, c = len(grid), len(grid[0])
        nums = 0

		##边界条件以及岛屿与否
		##这里也需要进行替换是避免重复遍历
        def dfs(i, j):
            if i < 0 or i >= r or j < 0 or j >= c or grid[i][j] != '1' :
                return
            grid[i][j] = '2'
            dfs(i-1, j)
            dfs(i+1, j)
            dfs(i, j-1)
            dfs(i, j+1)
        
        for i in range(r):
            for j in range(c):
                if grid[i][j] == '1':
                    dfs(i, j)
                    nums += 1
        
        return nums

还有一个方法是通过辅助矩阵记录路径,避免重复遍历(评论里面一个老哥一开始写的代码有bug,对其进行了修改后通过,注释的代码是原代码)。他的问题是只在dfs里用辅助矩阵避免重复遍历,然后将辅助矩阵返回原值,这样就不能体现出dfs的搜索路径,最后出来的结果就是在统计一个个岛屿(忽略了连通的岛屿是个整体)。

class Solution:
    def __init__(self):
        self.num_islands = 0

    def numIslands(self, grid):
        nr = len(grid)
        if nr == 0:
            return 0
        nc = len(grid[0])

        visited = [[False for _ in range(len(grid[0]))] for _ in range(len(grid))]

        def dfs(grid, r, c):
            # if r < 0 or r >= len(grid) or c < 0 or c >= len(grid[0]):
            #     return
            visited[r][c] = True
            # nr, nc = len(grid), len(grid[0])
            
            for x, y in [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)]:
                if 0 <= x < nr and 0 <= y < nc and grid[x][y] == "1" and not visited[x][y]:
                    dfs(grid, x, y)
            # visited[r][c] = False
        
        
        for r in range(nr):
            for c in range(nc):
            ##与辅助矩阵判断,将连通的岛屿当作整体
            ## 	if grid[r][c] == "1":
                if grid[r][c] == "1" and not visited[r][c]:
                    dfs(grid, r, c)
                    self.num_islands += 1
                    # dfs(grid, r, c)
        return self.num_islands

岛屿周长

此题也是典型的dfs算法,也有大佬用数学的思路解题
链接: 元素区间
思路:观察规律,dfs算法
根据nettee大佬的想法写的python版本

class Solution(object):
    def islandPerimeter(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        r, c = len(grid), len(grid[0])
        def dfs(i, j):
            ##岛屿走向边界周长+1
            ##注意边界条件
            if i < 0 or i > r-1 or j < 0 or j > c-1:
                return 1
            
            ##走向水域则周长+1
            if grid[i][j] == 0:
                return 1
            ##重复的路径则表示岛屿相连,返回0
            if grid[i][j] == 2:
                return 0
            ##避免重复路径
            grid[i][j] = 2

            return dfs(i+1, j) + dfs(i-1, j) + dfs(i, j+1) + dfs(i, j-1)

        for i in range(r):
            for j in range(c):
                ##只有一个岛屿直接返回dfs计算值
                if grid[i][j] == 1:
                    return dfs(i, j)

下面是Jam大佬数学法求解,注释了下为什么这样做(个人理解)。

class Solution:
    def islandPerimeter(self, grid: List[List[int]]) -> int:
        res=0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j]==1:
                    res+=4
                    ##if语句只考虑左上的情况是因为从左上开始遍历
                    ##且后面的格子会给前面的格子的右下情况擦屁股
                    ##减2的原因是因为一个格子四个结点,有重合的边则重合两个结点
                    ##格子的周长也等于结点数*边长长度
                    if i-1>=0 and grid[i-1][j]==1:
                        res-=2
                    if j-1>=0 and grid[i][j-1]==1:
                        res-=2
        return res

矩阵最长递增路径

此题在同学笔试中出现过,也是适用dfs算法。笔试中有些题目会要求自己输入,在刷题的过程中也要考虑下怎么输入。
链接: 递增路径
思路:dfs算法+逻辑判断(具体问题具体分析)
笔试要求自己输入的情况下,python一般用input()或sys.stdin.readline().这边的输入是矩阵维度以及矩阵的值。

##输入
"""
3 3
1 9 0
2 8 5
6 3 8   
"""
h, w = map(int, input().split())
m = []
for i in range(h):
	m.append(list((map(int, input().split()))))

二叉树系列

二叉树常用的方法还是dfs和bfs,结构是树状的。上面dfs算法适用的题都是矩阵网格状。一般对二叉树来说可以用递推或者迭代,迭代就会用到队列、栈,一般二叉树的遍历用栈。
二叉树前序、中序、后序、层序遍历代码参考链接: 二叉树.;二叉树遍历.
直观的理解二叉树三种遍历→理解二叉树遍历.
前序:根节点→左子树→右子树
中序:左子树→根节点→右子树
后序:左子树→右子树→根节点
层序:每层从左往右

二叉搜索树第K大节点

链接:二叉搜索树
思路:二叉搜索树的性质:其节点大小为左子树<根节点<右子树,采用中序遍历就可以得到一个有序递增数组

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def kthLargest(self, root: TreeNode, k: int) -> int:
        ##二叉搜索树性质节点值左子树<根节点<右子树
        ##中序遍历则是左子树→根节点→右子树
        ##通过中序遍历则能得到递增数列
        ##或者中序倒序遍历
        def middle(root):
            if not root:
                return []
            return middle(root.left) + [root.val] + middle(root.right)
        res = middle(root)
        return res[-k]
 
 '''
 ##大佬的解法则是中序遍历倒序,同时统计序号也就是K进行递减,记录K=0时候的序号
 def middle(root):
 	if not root:return
 	middle(root.right)
 	if self.k == 0:return
 	self.k -= 1
 	if self.k == 0:self.res = root.val
 	middle(root.left)
 
 self.k = k
 middle(root)
 return self.res
 '''

还有就是你可以采用其他的二叉树遍历,缺点就是需要进行排序,可以用list.sort(),也可以写快排、归并进行排序。

class Solution(object):
    def kthLargest(self, root, k):
        ##层序遍历,排序
        def bfs(root, level):
            if not root:
                return
            res[level-1].append(root.val)
            if len(res) == level:
                res.append([])
            bfs(root.left, level+1)
            bfs(root.right, level+1)

        ##快排
        def fast(lis, l, r):
            if l < r:
                base_ind = partition(lis, l, r)
                fast(lis, l, base_ind-1)
                fast(lis, base_ind+1, r)

        def partition(lis, l, r):
            base = lis[r]
            i = l-1
            for j in range(l, r):
                if lis[j] >= base:   ##从大到小
                    i += 1
                    lis[i], lis[j] = lis[j], lis[i]
            lis[i+1], lis[r] = lis[r], lis[i+1]
            return i+1
        
        res = [[]]
        bfs(root, 1)
        lis = sum(res, [])
        fast(lis, 0, len(lis)-1)
        return lis[k-1]

二叉树深度

链接:二叉树深度
思路:此题可以根据层序遍历得到的二维数组维度len(res)来确定树的深度
层序遍历可以说是广度优先搜索(bfs算法)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        def bfs(root, level):
            if not root:return
            else:
                res[level-1].append(root.val)
                if len(res) == level:
                    res.append([])
                bfs(root.left, level+1)
                bfs(root.right, level+1)
        
        res = [[]]
        bfs(root, 1)
        ##最后res会多一个空行[]
        return len(res)-1

Krahets大佬的dfs解法思路则是树的深度=max(左子树深度,右子树深度)+1.

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root: return 0
        return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1

平衡二叉树

链接: 平衡二叉树.
思路:平衡二叉树的性质是左右子树相差不超过1,这题跟二叉树深度类似

class Solution(object):
    def isBalanced(self, root):
        if not root:return True
        left_d = self.treehigh(root.left)
        right_d = self.treehigh(root.right)
        if abs(left_d - right_d) > 1:
            return False
        else:
            # return True
            #这里return True错误的原因在于只判断了左右子树
            #没有考虑到左右子树中自己的左右子树是否满足
            #故而需要进行递推
            return self.isBalanced(root.left) and self.isBalanced(root.right)

    def treehigh(self, root):
            if not root: return 0
            return max(self.treehigh(root.right), self.treehigh(root.left)) + 1

二分法

一般有序数组就采用二分法。二分法要注意左右边界问题。
链接: 元素区间
说明:这题是查找目标值在有序数组的位置(start-end)
思路:二分查找法

class Solution(object):
    def searchRange(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        if not nums:return [-1, -1]
        i, j = 0, len(nums)-1
        ##以下是找右边界
        ##一般来说查完某一边界,就需要将边界初始化,此写法直接沿用查完右边界后的j值,此时j=i-1
        ##终止条件i>j
        while i <= j:
            m = (i + j)//2  ##防止溢出就i + (j - i)/2
            if nums[m] <= target:
                i = m + 1
            else: j = m - 1
        right = i
        ##此时j=right-1,若j<0(越界)或j的值不等于target则说明数组中不存在target(target存在于(left,right)区间)
        if j < 0 or nums[j] != target:return [-1, -1]
        i = 0
        while i <= j:
            m = (i + j)//2
            if nums[m] < target:
                i = m + 1
            else: j = m - 1
        left = j
        ##可省略
        ##左边界判断同上,此时i=left+1,若i>len(nums)(越界)或i的值不等于target则说明数组中不存在target(target存在于(left,right)区间)
        return [left+1, right-1]

桶排序

排序这边算法很多,一般掌握快排跟归并就行(非科班小白常备)。但是昨天室友在某一面试中的手撕代码就遇到了桶排序,题目是《算法导论》8.4.4单位圆均匀分布这题,要求O(N)时间复杂度。本小白第一反应就是分区域画同心圆,但也就此打住。后面去百度才知道用桶排序。C++\C以及java版本参考链接: 桶排序

def insertionSort(b): 
    for i in range(1, len(b)): 
        up = b[i] 
        j = i - 1
        while j >=0 and b[j] > up:  
            b[j + 1] = b[j] 
            j -= 1
        b[j + 1] = up      
    return b      
              
def bucketSort(x): 
    arr = [] 
    slot_num = 10 # 10 means 10 slots, each 
                  # slot's size is 0.1 
    for i in range(slot_num): 
        arr.append([]) 
          
    # Put array elements in different buckets  
    for j in x: 
        index_b = int(slot_num * j)  
        arr[index_b].append(j) 
      
    # Sort individual buckets  
    for i in range(slot_num): 
        arr[i] = insertionSort(arr[i]) 
          
    # concatenate the result 
    k = 0
    for i in range(slot_num): 
        for j in range(len(arr[i])): 
            x[k] = arr[i][j] 
            k += 1
    return x 

动态规划

摆动序列

定义:序列两两差值正负交替出现,如[1,7,4,9]
链接:摆动序列.
思路:状态规划,dp[i][j]其中i是位置,j只有两个值0和1,表示差值正负。分析状态:
1.nums[i] > nums [i-1]:此时dp[i][j]表示正的状态与dp[i-1][0]有关

2.nums[i] < nums [i-1]:此时dp[i][j]表示负的状态与dp[i-1][1]有关

3.nums[i] = nums [i-1]:此时dp[i][j]状态与dp[i-1][j]相等

base case:dp[0] = [1, 1]

可以这样理解,第一个差值不管正负,都假设前面已经有差值的正负满足摆动序列,也就是说如果nums只有一个值的话,不管正负,前面都有了负(正)

class Solution:
    def wiggleMaxLength(self, nums: List[int]) -> int:
        if len(nums) < 2:return len(nums)
        ##状态规划
        ##dp[i][j]其中i是位置,j只有两个值0和1,表示差值正负
        ##分析状态
        ##1.nums[i] > nums [i-1]:此时dp[i][j]表示正的状态与dp[i-1][0]有关
        ##2.nums[i] < nums [i-1]:此时dp[i][j]表示负的状态与dp[i-1][1]有关
        ##3.nums[i] = nums [i-1]:此时dp[i][j]状态与dp[i-1][j]相等
        ##base case
        ##dp[0] = [1, 1]
        ##可以这样理解,第一个差值不管正负,都假设前面已经有差值的正负满足摆动序列
        ##也就是说如果nums只有一个值的话,不管正负,前面都有了负(正)
        dp = [[0, 0] for i in range(len(nums))]
        dp[0] = [1, 1]
        for i in range(1, len(nums)):
            if nums[i] > nums[i-1]:
                dp[i][1] = dp[i-1][0] + 1
                dp[i][0] = dp[i-1][0]
            elif nums[i] < nums[i-1]:
                dp[i][0] = dp[i-1][1] + 1
                dp[i][1] = dp[i-1][1]
            elif nums[i] == nums[i-1]:
                dp[i][0] = dp[i-1][0]
                dp[i][1] = dp[i-1][1]
        return max(dp[-1][1], dp[-1][0])

打家劫舍

打家劫舍有三道,都是属于动态规划。三道题都放一起。
链接: 打家劫舍.
思路:根据题意写出状态转移方程,容易写出 dp[i] = max(dp[i-1], dp[i-2]+nums[i-1]).
1.当前房子i偷,则i-1的房子不能偷,此时偷到的钱是i-2的金额加上当前i的金额
2.当前房子i不偷,则此时偷到的钱是i-1的金额

class Solution(object):
    def rob(self, nums):
        if not nums:return 0
        dp = [0]*(1 + len(nums))
        dp[1] = nums[0]
        for i in range(2, len(nums)+1):
            dp[i] = max(dp[i-1], dp[i-2]+nums[i-1])
        return dp[-1]

打家劫舍Ⅱ
思路:这题就是房子围成一个圈,这样第一个和最后一个房子不能同时抢。那么可以分成分别包含第一个和最后一个的序列,然后求最大金额,最后两个序列最大金额进行比较

class Solution:
		##分成两个排列
        ##不偷第一家以及偷第一家
        ##考虑nums的长度能不能分成两个排列
    def rob(self, nums: List[int]) -> int:
        if len(nums) == 1:return nums[0]
        nums_last, nums_fir = nums[1:], nums[:-1]
        dp_l = [0] * (len(nums_last)+2)
        dp_f = [0] * (len(nums_fir)+2)
        for l in range(len(nums_last)-1, -1, -1):
            dp_l[l] = max(dp_l[l + 1], dp_l[l + 2] + nums_last[l])
        for f in range(len(nums_fir)-1, -1, -1):
            dp_f[f] = max(dp_f[f + 1], dp_f[f + 2] + nums_fir[f])
        return max(dp_f[0], dp_l[0])

打家劫舍Ⅲ
思路:这题就是房子变成二叉树结构,根节点偷了则左右孩子不能偷,孙子节点可以偷。状态定义则为:
1.当前节点选择不偷:当前节点能偷到的最大钱数 = 左孩子能偷到的钱 + 右孩子能偷到的钱;
2.当前节点能偷到的最大钱数 = 左孩子选择自己不偷时能得到的钱 + 右孩子选择不偷时能得到的钱 + 当前节点的钱数

class Solution(object):
    def rob(self, root):
        def dfs(node):
            if not node: return 0, 0
            l = dfs(node.left)
            r = dfs(node.right)
            selected = node.val + l[1] + r[1]
            notSelected = max(l[0], l[1]) + max(r[0], r[1])
            return selected, notSelected
        return max(dfs(root))

蛇形矩阵(顺逆交替)实现

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值