Exercise4 - Algorithmic skills
FirstUnique: Find the first unique number in a given sequence.
问题概述:输入是一个包含了N个整数的Array A,需要的返回值,是其中按顺序的第一个unique number。unique number的定义,即Array A中仅出现过一次的元素。
解法概述:根据题意,因为N最大是10w,而每个N的取值最大可能10e,因此我们选择使用dictionary,将A中每个元素的值作为key,将出现过的次数记录为key对应的value,每出现一次就+1。最后再重新从头循环一次,并不断检查count_dict[a]==1时,符合条件则直接返回a,否则在全部循环结束后返回-1。
def solution(A):
count_dict = {}
for a in A:
count_dict[a] = count_dict.get(a,0)+1
for a in A:
if count_dict[a]==1:
return a
return -1
StrSymmetryPoint: Find a symmetry point of a string, if any.
问题概述:本题输入是一个字符串S,需要返回的值,是是否存在一个index,index左侧的子字符串,正好是index右侧的子字符串的镜像。并且空字符串的reverse还是空字符串,也就是单字符的字符串应该返回index=0。而如果S中不存在符合条件的index,则返回-1。
解法概述:结合题意不难推出,如果字符串的字符数是偶数,则不可能找到符合条件的index,因为任意index下左侧和右侧的字符个数都不一致,直接返回-1即可。而字符串个数是奇数时,则可以用双指针的思路,从S的首尾不断判断是否字符相等,然后不断向中间靠拢直到两指针相遇。期间如果发现字符不等则直接返回-1,反之如果成功相遇,则begin=end=需要返回的值。
def solution(S):
result = 0
begin, end = 0, len(S)-1
if end%2==1: # len(S)是偶数
return -1
while begin<end:
if S[begin]==S[end]:
result += 1
begin += 1
end -= 1
else:
return -1
return result
TreeHeight: Compute the height of a binary tree.
问题概述:输入是一个Tree实例,需要返回的,是Tree的最长深度。Tree的每个node都有三个属性,x,l和r。其中x是node的值,l是左子树,r是右子树。
解法概述:因为最大深度是500,直接简单按递归思路进行最大深度的计算即可,具体如下:
from extratypes import Tree # library with types used in the task
def solution(T):
if T:
if not T.l and not T.r:
return 0
return max(solution(T.l), solution(T.r)) + 1
return 0
ArrayInversionCount: Compute number of inversion in an array.
问题概述:给定一个由整数组成的数组A,数组A的逆序对定义为满足i<j且A[i]>A[j]的(i, j)对数。计算数组A的逆序对数。
解法概述:最开始想到的解题思路是维护一个cnt_dict记录每种数值出现过的次数,然后从左到右循环数组A,每一步判断cnt_dict中符合逆序对的value个数有多少,加进总result中。并且这个过程中会不断在cnt_dict中减掉循环中上一个value的出现个数。
def solution(A):
v_cnt_dict = {}
for a in A:
v_cnt_dict[a] = v_cnt_dict.get(a, 0) + 1
# 从头统计一遍
result = 0
for a in A:
v_cnt_dict[a] -= 1 # 当前a从v_cnt_dict中减掉
for v, cnt in v_cnt_dict.items():
if v<a:
result += cnt
if result > 1000000000:
return -1
return result
最终发现这个双循环思路,并非performance最优的解法,虽然Correctness达到了100%,但是Performance只有惨淡的20%。
借鉴了网上的解题思路,最优解法是用Merge Sort或树状数组来解决该问题,时间复杂度可以下降到O(NlogN)。
我这里后来选择利用的是归并排序思想的解法:将数组A不断拆分为较小的两个数组,计算其逆序对数目并同时排序,最后将两个排好序的数组合并为一个排好序的数组,将其逆序对数目加入总计数器中。借鉴的比较清晰的github上这个项目的代码:github.com/OSerHuang/C…
def merge(nums1, count1, nums2, count2):
if count1==-1 or count2==-1:
return [], -1
merged_nums = []
count = count1 + count2
i = j = 0
while i<len(nums1) or j<len(nums2):
if j==len(nums2) or (i<len(nums1) and nums1[i]<=nums2[j]):
merged_nums.append(nums1[i])
i += 1
else:
merged_nums.append(nums2[j])
j += 1
count += len(nums1) - i
if count > 1000000000: return [], -1
return merged_nums, count
def merge_sort(nums):
if len(nums) <= 1:
return nums, 0
return merge(*merge_sort(nums[:len(nums) // 2]), *merge_sort(nums[len(nums) // 2:]))
def solution(A):
# write your code in Python 3.6
return merge_sort(A)[-1]
DisappearingPairs: Reduce a string containing instances of the letters "A", "B" and "C" via the following rule: remove one occurrence of "AA", "BB" or "CC".
问题概述:输入是一个只包含小写英文字母的字符串S,迭代地将字符串中相邻且重复的字符对(例如aa)删除,直到不能再删除为止。需求的输出,是经过上述操作后最终的结果字符串。
解法概述:本身这道题应该是考核可以利用栈的结构实现不断的匹配消除判断,然后在最后顺序出栈,获得需要返回的结果字符串,但是我们在solution中,实现上没有利用栈的结构,而是在循环中随时维护pre_s(循环中倒数第二个字符)和afterS(最终的返回值)两个字符串变量。
pre_s==s可以判断确定是否需要删除重复字符,如果确定需要删除重复字符,那么就再用pre_s=afterS[-1]更新下pre_s的值即可。而为了避免开头两个字符就是重复字符,无法实现pre_s=afterS[-1]的操作,这里把初始化的afterS就设定为了"X",并在最终返回时跳过这个打头的X。
def solution(S):
# 初始化一个非ABC字符,并在最后返回afterS时从第二位开始返回
# 主要防止removing过程中afterS变成空字符后afterS[-1]部分报错
afterS = 'X'
pre_s = ''
for s in S:
if s==pre_s: # 2个相同字符,销掉
afterS = afterS[:-1]
pre_s = afterS[-1]
else:
afterS += s
pre_s = s
return afterS[1:]
PolygonConcavityIndex: Check whether a given polygon in a 2D plane is convex; if not, return the index of a vertex that doesn't belong to the convex hull.
问题概述:
解法概述:这个部分涉及了几个Step获取到最终的解:
- 凸多边形的判断
- 多边形方向确定规则:确定非凸多边形后,对输入的Array中点是沿多边形的顺时针还是逆时针的点进行判断
- 多边形凹点确定规则:确定了点的多边形方向后,可以顺序计算点组成的前后向量的叉乘,确定每个点是否为凹点,确定即可立刻返回当时的对应index
具体的算法,其实也是参考了网上的一些论文,在上边的三个Step中,每一部分其实都有不同的一些算法,我这里给出的solution只是选取了我比较好理解的一种。
【多边形方向的确定】参考了这一篇:www.shcas.net/jsjyup/pdf/…
【多边形凹点的确定】参考了这一篇:blog.csdn.net/u014622197/…
from extratypes import Point2D # library with types used in the task
def solution(A):
# Step1: 判断给定的多边形是否为凸多边形
# 可以通过计算每个连续的三个点所组成的向量的叉乘来判断,若所有的向量叉积的符号都一致,则该多边形为凸多边形。
result=0
is_convex=True
for p0, p1, p2 in zip(A, A[1:]+A[:1], A[2:]+A[:2]):
p0p1_x, p0p1_y = p1.x-p0.x, p1.y-p0.y
p0p2_x, p0p2_y = p2.x-p0.x, p2.y-p0.y
p0p1_p0p2 = p0p1_x * p0p2_y - p0p1_y * p0p2_x
if p0p1_p0p2<0:
if result>0:
is_convex=False
break
result -= 1
if p0p1_p0p2>0:
if result<0:
is_convex=False
break
result += 1
# 多边形是凸多边形,返回-1;不是凸多边形,则它包含至少一个凹点
if is_convex:
return -1
# Step2: 判断输入Array中的点顺序,是沿多边形顺时针还是逆时针的。
# 具体算法,是找到所有点里y最小(y相同最小,则再取x最小)的点,因为可以证明这个点两侧的边组成的一定是凸点,所以可根据这个点左右两个边的向量叉乘,确定哪个点是顺时针的下一个点,
min_point_i = 0
min_x, min_y = A[0].x, A[0].y
for i,a in enumerate(A[1:]):
if a.y<min_y:
min_x, min_y = a.x, a.y
min_point_i = i+1
elif a.y==min_y:
if a.x<min_x:
min_x = a.x
min_point_i = i+1
is_clockwise = True # 默认初始化为顺时针
tmp_A = [A[-1]] + A + [A[0]]
p0, p1, p2 = tmp_A[min_point_i], tmp_A[min_point_i+1], tmp_A[min_point_i+2]
p0p1_x, p0p1_y = p1.x-p0.x, p1.y-p0.y
p1p2_x, p1p2_y = p2.x-p1.x, p2.y-p1.y
p0p1_p0p2 = p0p1_x * p1p2_y - p0p1_y * p1p2_x
if p0p1_p0p2>0: # P0~P1是沿着多边形逆时针走的,反之则是顺时针,不需要修改is_clockwise
is_clockwise = False
# Step3: 找到凹点
# 在已确定点是按多边形的顺时针还是逆时针后,就可以使用以下算法来找到凹点了:
# 1. 从多边形上任选一个点P作为起点,顺序走到下一个点Q,假设PQ组成边A
# 2. 若定义从Q继续走向下一个点R得到的边为B,则计算A、B的叉乘,
# 3. 顺时针走的话,则每次两个边的叉乘为负,代表凸点;叉乘为负,代表凹点;逆时针走完,则判断条件相反
for i, (p0, p1, p2) in enumerate(zip(A[-1:]+A[:-1], A, A[1:]+A[:1])):
p0p1_x, p0p1_y = p1.x-p0.x, p1.y-p0.y
p1p2_x, p1p2_y = p2.x-p1.x, p2.y-p1.y
p0p1_p0p2 = p0p1_x * p1p2_y - p0p1_y * p1p2_x
if is_clockwise and p0p1_p0p2>0:
return i
if not is_clockwise and p0p1_p0p2<0:
return i