第一次LeetCode周赛心得(力扣-cn周赛,使用python3)

第一次力扣参赛:第 174 场力扣周赛

第 174 场力扣周赛赛题:
https://leetcode-cn.com/circle/discuss/lEfEkb/view/OrAJAh/

第 174 场力扣周赛国内排名(点击小图标可查看选手代码):
https://leetcode-cn.com/contest/weekly-contest-174/ranking/1/

总体心得

使用python3,做成了第一道题,第二道题的解超时,便结束了第一次周赛。

下午看了排行榜靠前的参赛者代码,最快的在17分钟内就完赛了(使用python3),使用python3的和c++的数量几乎对半分。

先来说个问题:使用python算作弊吗?

我觉得不算,尽管python中有collections库这种强大的操作集,但是对不懂数据结构、或没有赛题解决思路的人来说,终究还是没有帮助的。python只是帮助我们把精力全部集中在算法的流程上,而非程序设计与具体实现上(python封装了太多常用操作,减少实现压力)。

况且,c++中也有vector<>()、java中也有hashtable<>()这种类似的可调用的数据结构对象,来简化操作。因此,python不算作弊。

我目前的编程,都是为了实现强化学习算法,没有涉及到底层系统或者企业级开发,因此选择用python3来刷力扣。

使用c/cpp来对每个指针进行操作,可能会在算法的时间空间复杂度上有一定优势,但是,实现起来会比较麻烦,而周赛比的是速度。况且,python3中封装的代码都是方法较优的。

这次最快的同学是 zerotrac2 ,看到了其领英上的简历,很强,高中就有极好的竞赛基础,在清华软工有9/74的排名:

第一次LeetCode周赛心得(力扣-cn周赛,使用python3)_第1张图片

这位同学的简历链接:https://www.linkedin.com/in/zerotrac/

看了他的代码,觉得“并不是我现在无法达到的水平”,收获了很多库的使用技巧与设计思想,下面对四道题一一分析。


[1341] 方阵中战斗力最弱的 K 行

很简单的排序问题,我的代码为:

class Solution(object):
    def kWeakestRows(self, mat, k):
        """
        :type mat: List[List[int]]
        :type k: int
        :rtype: List[int]
        """
        fighter_dict = dict()
        r = -1
        for _ in mat:
            r += 1
            # key: row number, value: soldiers_count
            fighter_dict[r] = sum(mat[r])
            
        returns = list()
        while k > len(returns):
            current_min_row = fighter_dict.keys()[0]
            for row in fighter_dict:
                if fighter_dict[row] < fighter_dict[current_min_row]:
                    current_min_row = row
            returns.append(current_min_row)
            del fighter_dict[current_min_row]
        return returns

高手的代码为:

class Solution:
    def kWeakestRows(self, mat: List[List[int]], k: int) -> List[int]:
        c = [(sum(x), i) for i, x in enumerate(mat)]
        c.sort(key=lambda x: (x[0], x[1]))
        return [x[1] for x in c[:k]]

解决思路差不多,但是我没有很坚决地把问题抽象为一个二维排序问题,因此产生了犹豫,并且复杂化了代码实现过程,除此之外,上述代码值得学习的地方还有:

  • enumerate(list)返回(索引, 值)
  • 列表的方法sort(key=lambda x: expression)用法(升序);
  • list[:k]灵活运用切片。

我还存在一个问题,就是我的排序是我自己写的(在while里),我期望达到在线处理的效果,可是时间复杂度还是有 O ( k × n ) O(k \times n) O(k×n)之多。以后遇到需要排序的问题,直接使用list.sort()算法,在网上查过,python3封装的排序算法应该是最优的(二分法),时间复杂度为 O ( n × log ⁡ 2 n ) O(n \times \log_2 n) O(n×log2n)


[5329] 数组大小减半

我的算法超时了,看不到了,高手代码为:

class Solution:
    def minSetSize(self, arr: List[int]) -> int:
        c = collections.Counter(arr)
        s = 0
        ans = 0
        for k, v in c.most_common():
            s += v
            ans += 1
            if s * 2 >= len(arr):
                return ans
        return -1

我学到了:

  • collection.Counter(arr)的使用,直接对个元素计数;
  • top-n 问题使用most_common()方法,也是基于collection.Counter对象的。

[5330] 分裂二叉树的最大乘积

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

class Solution:
    def maxProduct(self, root: TreeNode) -> int:
        def tot(node):
            if not node:
                return 0
            return tot(node.left) + tot(node.right) + node.val

        def dfs(node, s):
            if not node:
                return 0, 0
            p1, q1 = dfs(node.left, s)
            p2, q2 = dfs(node.right, s)
            p = p1 + p2 + node.val
            return p, max(q1, q2, p * (s - p))

        s = tot(root)
        return dfs(root, s)[1] % 1000000007

看懂这个代码着实花了我一点时间,但是以后遇到类似代码不会再让我感到陌生了:

  • 就是一个深度优先搜索(bfs)而已;
  • 注意上述算法中的s值其实没有变过,作者之所以把其作为dfs()的参数,应该是因为:在一般的dfs()递归算法中,dfs()传入的都是(根结点, 参数),其中参数是参与递归过程的;
  • dfs()的两个返回值分别是:
    • 该子树的结点和;
    • (1)计算以该子树作为断点,独立成根,其产生的和与剩下部分产生的和的乘积的值,(2)与两个子树的该值比较,取最大的,保留,(3)子树的该值是子树与子树的子树的该值进行比较得到的。
  • 巧妙地运用递归进行了搜索,没有半行废话。

[5331] 跳跃游戏 V

惊了,大神前三题用的好好的 python3 ,第四题一下就换 cpp 了,是觉得太无聊了,想玩玩花样吗…

我觉得也有这种可能:

  • 大神们因为刷题太多,已经有了一个自己的轮子库;
  • 遇到新问题,马上抽象总结成一起遇到的问题;
  • 把以前的轮子稍加修改便可套用,这道题的兄弟题可能是大神以前使用从 cpp 做过的。

或者,此题使用 python3 可能超时。

class Solution {
private:
    vector<bool> used;
    vector<int> f;
    
public:
    void dfs(vector<int>& arr, int id, int d, int n) {
        // 传址调用 arr
        if (used[id]) {
            return;
        }
        used[id] = true;
        f[id] = 1;
        // 看着像二分法,实际上不是
        // 就是把小人投放到每个“柱子上”,然后递归跳跃
        for (int i = id - 1; i >= 0 && id - i <= d; --i) {
            // i可能等于-1,因此加入i>=0这个判断条件
            if (arr[id] > arr[i]) {
                dfs(arr, i, d, n);
                f[id] = max(f[id], f[i] + 1);
            }
            else {
                break;
            }
        }
        for (int i = id + 1; i < n && i - id <= d; ++i) {
            if (arr[id] > arr[i]) {
                dfs(arr, i, d, n);
                f[id] = max(f[id], f[i] + 1);
            }
            else {
                break;
            }
        }
    }
    
    int maxJumps(vector<int>& arr, int d) {
        int n = arr.size();
        // cpp 中的vertor.size() .resize() 方法库值得积累
        used.resize(n);
        f.resize(n);
        for (int i = 0; i < n; ++i) {
            dfs(arr, i, d, n);
        }
        return *max_element(f.begin(), f.end());
        // std 的 max_element() 返回指针,操作数也是指针
        // *max_element 中的星号是取 指针指的值
        // 而输入的操作数 f.begin() f.end() 表示查找区间范围
    }
};

我学到了:

  • dfs()实际上是一种“挖到最低层的状态结点,然后向上回溯”的过程。在本例中,小人只被投放到了各个结点一次,没有重复计算,当其被投放到最底层结点时,开始计算其步数,并累加至上一层。
  • for(,条件,)中增加条件,以保证迭代数在操作体内的可执行性;
  • cpp 中的vertor.size() .resize() 方法库值得积累;
  • *max_element()stdmax_element()返回指针,操作数也是指针,*max_element中的星号是取 指针指的值,而输入的操作数f.begin(), f.end()表示查找区间范围。

cpp 的结果为:

  • 127/127 cases passed (68 ms)
  • Your runtime beats 100 % of cpp submissions
  • Your memory usage beats 100 % of cpp submissions (10.5 MB)

而使用 python 的结果为:

  • 127/127 cases passed (1060 ms)
  • Your runtime beats 100 % of python3 submissions
  • Your memory usage beats 100 % of python3 submissions (14.2 MB)

(可见,同结构的数据与相同的算法下,python 比 cpp 慢很多)

python 的代码是我自己写的,如下:

class Solution:
    def maxJumps(self, arr: List[int], d: int) -> int:
        f = [0 for _ in range(len(arr))]

        def dfs(arr, index, d, n):
            if f[index]:
                return None
            f[index] = 1
            # left indexes
            i = index - 1
            while i >= 0 and index - i <= d:
                if arr[index] > arr[i]:
                    dfs(arr, i, d, n)
                    f[index] = max(f[index], f[i] + 1)
                else:
                    break
                i -= 1
            # right indexes
            i = index + 1
            while i < n and i - index <= d:
                if arr[index] > arr[i]:
                    dfs(arr, i, d, n)
                    f[index] = max(f[index], f[i] + 1)
                else:
                    break
                i += 1
    
        length = len(arr)
        for i in range(length):
            dfs(arr, i, d, length)
        return max(f)

后记心得

看完了这四道题的“高手”解法,认为明确易懂,且总结了不少规律。我对我自己的实力还是很有信心的。

我不知道我说的对不对:数据结构与算法的基础题,实际上就是一个字“找”,找什么?“解”。解都是找出来的。比的就是谁的方法又巧妙又易于实现(通常,易于实现的算法他人也一看就懂)。有什么好的办法来提升自己吗?刷题、总结、积累。所谓“找”之策,万变不离其宗(数据结构基础),能把问题抽象为已知的经典问题,就可快速解决。

今天周日,下周我想刷刷 LeetCode ,就当娱乐了。下周日的周赛再参加一次,我已经等不及了,说实话。

再后记: 步入“编程的正道”正好一年。去年这时跟着学长实现数学模型,要用到“孤立森林”(不是随机森林,是周志华等人提出的一种无监督离群点判断方法),matlab里没有封装好的孤立森林方法(2019年1月份前我只会用matlab),而python3sklearn库里有,于是入了python3,编辑器用的PyCharm,发现比matlab好用多了。从此,抛弃“伪代码”,钻入底层的世界,学习“配置环境”、“操作系统”、“数据结构”、“编译与字节码”到底是怎么一回事,javac/c++c#这种以前或许接触过的却根本不了解的编程语言也被我重新拾起。总之,读了很多很棒的文章,很重视学习编程的规范与底层知识的学习。

这一年里,我写了130+的博文,包括:操作系统&数据结构等等课程的笔记、python3的debug心得、编译器配置等等,我的CSDN:https://blog.csdn.net/weixin_42815609。

我还开了不少 GitHub repo,包括强化学习笔记、计算机核心课笔记:

  • GitHub 个人主页:https://github.com/PiperLiu
  • [更新中…]基于c/c++实现的计算机核心课笔记:https://github.com/PiperLiu/CS-courses-notes
  • [更新中…]强化学习笔记:https://github.com/PiperLiu/Reinforcement-Learning-practice-zh

对了,强烈推荐 VS Code 的插件 LeetCode for VS Code ,今天下午刚刚安装,简直神器。

链接:LeetCode for VS Code: 程序员 Offer 收割利器 - 知乎

PiperLiu
2020-2-2 21:18:04

你可能感兴趣的:(数据结构,leetcode,python)