第六周LeetCode记录

Posted To Be survivor

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第六周LeetCode记录相关的知识,希望对你有一定的参考价值。

10.19 27. 01矩阵

给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。

两个相邻元素间的距离为 1 。

输入
0 0 0
0 1 0
0 0 0

输出
0 0 0
0 1 0
0 0 0

输入
0 0 0
0 1 0
1 1 1

输出
0 0 0
0 1 0
1 2 1

思路

如果该元素不为0,则距离为min(上,下,左,右) + 1。

最优解

解法一:bfs
class Solution:
    def updateMatrix(self, matrix: list) -> list:
        import collections
        m, n = len(matrix), len(matrix[0])
        dist = [[0] * n for _ in range(m)]
        zeroes_pos = [(i, j) for i in range(m) for j in range(n) if matrix[i][j] == 0]
        # 将所有的 0 添加进初始队列中
        q = collections.deque(zeroes_pos)
        seen = set(zeroes_pos)

        # 广度优先搜索
        while q:
            i, j = q.popleft()
            for ni, nj in [(i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1)]:
                if 0 <= ni < m and 0 <= nj < n and (ni, nj) not in seen:
                    dist[ni][nj] = dist[i][j] + 1
                    q.append((ni, nj))
                    seen.add((ni, nj))

        return dist
总结

刚开始没有写下去是因为不知道一个坐标到两个0点的距离不一致怎么处理,没有体会到bfs的特点。

解法二:dp

class Solution:
    def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]:
        m, n = len(matrix), len(matrix[0])
        # 初始化动态规划的数组,所有的距离值都设置为一个很大的数
        dist = [[10**9] * n for _ in range(m)]
        # 如果 (i, j) 的元素为 0,那么距离为 0
        for i in range(m):
            for j in range(n):
                if matrix[i][j] == 0:
                    dist[i][j] = 0
        # 只有 水平向左移动 和 竖直向上移动,注意动态规划的计算顺序
        for i in range(m):
            for j in range(n):
                if i - 1 >= 0:
                    dist[i][j] = min(dist[i][j], dist[i - 1][j] + 1)
                if j - 1 >= 0:
                    dist[i][j] = min(dist[i][j], dist[i][j - 1] + 1)
        # 只有 水平向左移动 和 竖直向下移动,注意动态规划的计算顺序
        for i in range(m - 1, -1, -1):
            for j in range(n):
                if i + 1 < m:
                    dist[i][j] = min(dist[i][j], dist[i + 1][j] + 1)
                if j - 1 >= 0:
                    dist[i][j] = min(dist[i][j], dist[i][j - 1] + 1)
        # 只有 水平向右移动 和 竖直向上移动,注意动态规划的计算顺序
        for i in range(m):
            for j in range(n - 1, -1, -1):
                if i - 1 >= 0:
                    dist[i][j] = min(dist[i][j], dist[i - 1][j] + 1)
                if j + 1 < n:
                    dist[i][j] = min(dist[i][j], dist[i][j + 1] + 1)
        # 只有 水平向右移动 和 竖直向下移动,注意动态规划的计算顺序
        for i in range(m - 1, -1, -1):
            for j in range(n - 1, -1, -1):
                if i + 1 < m:
                    dist[i][j] = min(dist[i][j], dist[i + 1][j] + 1)
                if j + 1 < n:
                    dist[i][j] = min(dist[i][j], dist[i][j + 1] + 1)
        return dist
总结

只要有一个点,就可以确定距离矩阵,以0点作为左上角,右上角,左下角,右下角,分别计算可以得到矩阵。遍历每一个点,取min即可。

10.22 28. 地图分析

你现在手里有一份大小为 N x N 的 网格 grid,上面的每个 单元格 都用 0 和 1 标记好了。其中 0 代表海洋,1 代表陆地,请你找出一个海洋单元格,这个海洋单元格到离它最近的陆地单元格的距离是最大的。

我们这里说的距离是「曼哈顿距离」( Manhattan Distance):(x0, y0) 和 (x1, y1) 这两个单元格之间的距离是 |x0 - x1| + |y0 - y1| 。

如果网格上只有陆地或者海洋,请返回 -1。

输入:[[1,0,1],[0,0,0],[1,0,1]]
输出:2
解释: 
海洋单元格 (1, 1) 和所有陆地单元格之间的距离都达到最大,最大距离为 2。

输入:[[1,0,0],[0,0,0],[0,0,0]]
输出:4
解释: 
海洋单元格 (2, 2) 和所有陆地单元格之间的距离都达到最大,最大距离为 4。

思路

和上一题一样,如果用bfs,把所有为1的点左边放入queue,然后pop遍历。

如果用dp,遍历每一个点,分四块区域获取min。

我的解

class Solution:
    @classmethod
    def maxDistance(self, grid: list) -> int:
        m, n = len(grid),len(grid[0])

        if [[0]*n for _ in range(m)] == grid or [[1]*n for _ in range(m)] == grid:
            return -1

        dist = [[0] * n for _ in range(m)]

        import collections
        one_matrix = [(i,j) for i in range(m) for j in range(n) if grid[i][j]==1]
        q = collections.deque(one_matrix)
        seen = set(one_matrix)

        while q:
            j,k = q.popleft()
            for nj,nk in [[j,k-1],[j,k+1],[j-1,k],[j+1,k]]:
                if 0<=nj<m and 0<=nk<n and (nj,nk) not in seen:
                    dist[nj][nk] = dist[j][k] + 1
                    q.append((nj,nk))
                    seen.add((nj,nk))
        
        total_list = []
        for i in dist:
            total_list += i
        res = max(total_list)

        return res

10.23 29. 另一个树的子树

给定两个非空二叉树 st,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。

思路

将 是否是子树问题 转化为 是否相等问题,判断两个树是否相等的三个条件是与的关系:

  1. 当前两个树的根节点值相等
  2. 并且s的左子树和t的左子树相等
  3. 并且s的右子树和t的右子树相等

判断t是否为s的子树的三个条件是或的关系

  1. 当前两棵树相等
  2. 或者,t是s的左子树
  3. 或者,t是s的右子树

最优解:暴力解法

class Solution:
    def isSubtree(self, s: TreeNode, t: TreeNode) -> bool:
        if not s and not t:
            return True
        elif not s or not t:
            return False
        return self.isSameTree(s,t) or self.isSubtree(s.left,t) or self.isSubtree(s.right,t)

    def isSameTree(self,s,t):
        if not s and not t:
            return True
        elif not s or not t:
            return False
        return s.val == t.val and self.isSameTree(s.left,t.left) and self.isSameTree(s.right,t.right)

解法二:KMP

待完善

10.28 30. 环形链表

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

思路

设两个s(low),q(uick)两个指针,s步幅为1,q的步幅为2。在遍历的过程中如果遇到了null节点,说明不是环。

如果某一时刻两节点相同,则说明环的存在,设环入口的位置为len,两者相遇的位置离环入口为x,则s指针走的距离为len + x, 设环的大小为y,q行走的距离为len + n*y + x, q走的距离是s的两倍即:

2 * (len + x) = len + n*y + x => len + x = ny => len = ny-x

取两个速度为1的指针,一个放在交点处x,一个放在起点处y,并前进

则y下面第len个节点,为len + n*y + x + len 将len=ny-x代入,len + ny + x + ny-x = len + 2ny 说明到达了环的入库,此时起点处的也到达了入口。

最优解

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode slow = head, fast = head;
        while (fast != null) {
            slow = slow.next;
            if (fast.next != null) {
                fast = fast.next.next;
            } else {
                return null;
            }
            if (fast == slow) {
                ListNode ptr = head;
                while (ptr != slow) {
                    ptr = ptr.next;
                    slow = slow.next;
                }
                return ptr;
            }
        }
        return null;
    }
}

10.29 31. 推多米诺

一行中有 N 张多米诺骨牌,我们将每张多米诺骨牌垂直竖立。

在开始时,我们同时把一些多米诺骨牌向左或向右推。

每过一秒,倒向左边的多米诺骨牌会推动其左侧相邻的多米诺骨牌。

同样地,倒向右边的多米诺骨牌也会推动竖立在其右侧的相邻多米诺骨牌。

如果同时有多米诺骨牌落在一张垂直竖立的多米诺骨牌的两边,由于受力平衡, 该骨牌仍然保持不变。

就这个问题而言,我们会认为正在下降的多米诺骨牌不会对其它正在下降或已经下降的多米诺骨牌施加额外的力。

给定表示初始状态的字符串 "S" 。如果第 i 张多米诺骨牌被推向左边,则 S[i] = ‘L‘;如果第 i 张多米诺骨牌被推向右边,则 S[i] = ‘R‘;如果第 i 张多米诺骨牌没有被推动,则 S[i] = ‘.‘。

返回表示最终状态的字符串。

输入:".L.R...LR..L.."
输出:"LL.RR.LLRRLL.."

输入:"RR.L"
输出:"RR.L"
说明:第一张多米诺骨牌没有给第二张施加额外的力。

思路

用栈存储状态,从左到右遍历:

  1. 如果是L开头,遇到L或点直接入栈,遇到R出栈,遇到R或点入栈,遇到L出栈,一半为L一半为R,中间为点。如此操作
  2. 如果是R开头,点和R入栈,L出栈,一半为L一半为R,中间为点。如此操作。
  3. 如果是点开头,点入栈,遇到R或L出栈。后面如1或2.

最优解

class Solution:
    @classmethod
    def pushDominoes(self, dominoes: str) -> str:
        import operator
        symbols = [(i, x) for i, x in enumerate(dominoes) if x != ‘.‘]
        symbols = [(-1, ‘L‘)] + symbols + [(len(dominoes), ‘R‘)]

        ans = list(dominoes)
        for (i, x), (j, y) in zip(symbols, symbols[1:]):
            if x == y:
                for k in range(i + 1, j):
                    ans[k] = x
            elif x > y:  # RL
                for k in range(i + 1, j):
                    # ans[k] = ‘.LR‘[operator.eq(k - i, j - k)]
                    if k-i==j-k:
                        eq = 0
                    elif k-i > j-k:
                        eq = 1
                    else:
                        eq = -1
                    ans[k] = ‘.LR‘[eq]

        return "".join(ans)

总结:

如果我们有 "A....B",当 A = B,那么就变成 "AAAAAA"。
如果我们有 "R....L",那么结果会变成 "RRRLLL" 或者 "RRR.LLL" 如果点的个数是奇数。如果初始标记的坐标是 i 和 j,我们可以检查距离 k-i 和 j-k 来决定位置 k 的形态是 ‘L‘,‘R‘ 还是 ‘.‘。
如果我们有 "L....R",就什么都不做,跳过。

此解法巧妙在用同一个数组,错位构造,然后比较。值得借鉴学习。然后分析上述的几种情况。



以上是关于第六周LeetCode记录的主要内容,如果未能解决你的问题,请参考以下文章

Android Studio学习记录-第六周

ARTS打卡计划第六周

第六周--冒泡排序

java学习第六周

老男孩Linux运维第41期20171016第六周学习重点课堂记录

第六周学习分析