第 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的排名:
这位同学的简历链接:https://www.linkedin.com/in/zerotrac/
看了他的代码,觉得“并不是我现在无法达到的水平”,收获了很多库的使用技巧与设计思想,下面对四道题一一分析。
很简单的排序问题,我的代码为:
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)。
我的算法超时了,看不到了,高手代码为:
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)
的使用,直接对个元素计数;most_common()
方法,也是基于collection.Counter
对象的。# 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
看懂这个代码着实花了我一点时间,但是以后遇到类似代码不会再让我感到陌生了:
s
值其实没有变过,作者之所以把其作为dfs()
的参数,应该是因为:在一般的dfs()
递归算法中,dfs()
传入的都是(根结点, 参数),其中参数是参与递归过程的;dfs()
的两个返回值分别是:惊了,大神前三题用的好好的 python3 ,第四题一下就换 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(,条件,)
中增加条件,以保证迭代数在操作体内的可执行性;*max_element()
:std
的max_element()
返回指针,操作数也是指针,*max_element
中的星号是取 指针指的值,而输入的操作数f.begin(), f.end()
表示查找区间范围。cpp 的结果为:
而使用 python 的结果为:
(可见,同结构的数据与相同的算法下,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
),而python3
的sklearn
库里有,于是入了python3
,编辑器用的PyCharm
,发现比matlab
好用多了。从此,抛弃“伪代码”,钻入底层的世界,学习“配置环境”、“操作系统”、“数据结构”、“编译与字节码”到底是怎么一回事,java
、c/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