Codility刷题之旅 - Exercise7 - Data Structures

392 阅读5分钟

Exercise7 - Data Structures

ArrListLen: Compute length of a single-link list encoded in an array.

image.png

问题概述:输入是一个包含N个整数的Array A,其本身代表了一个链表,每个正整数就是一个node,而正整数的值则是代表了该node指向的下一个node(可以指向自己,-1代表最后一个node)。需要返回的,是该Array代表的链表的长度。

解法概述:链表的起始node,就是Array中的一个数字代表的node,而末尾node,则是顺着链表的指向到达value=-1时对应的node。因此我们简单通过一个while循环即可获得需求解,本题算是最基础的数据结构链表题了,就不多赘述了。

def solution(A):
    res = 1
    i = 0
    while A[i]!=-1:
        i = A[i]
        res += 1
    return res

image.png

CountBoundedSlices: Calculate the number of slices in which (maximum - minimum <= K).

image.png

问题概述:给定一个整数数组A和非负整数K,计算A中有多少个子数组能够满足子数组的max()-min()是小于等于K的,需要返回符合条件的子数组的数量,如果数量大于10e,返回10e即可。

解法概述:不难想到要用Caterpillar方法:begin先走,begin停止的条件,是当继续走会出现max-min>K,或begin已经走到字符串末尾。此时将end-begin累加入result,并移动end+1。然后再重新判断begin能否能继续向前走,如此反复直到最后begin=end=len(A)-1。这样实现下来,计算的时间复杂度为O(N)。

def solution(K, A):
    N = len(A)
    result = 0
    begin = end = 0
    max_v, min_v = A[begin], A[end]
    # end一直走到N-1
    while end<N-1: 
        # A[end+1]符合K条件,则一直向后移动end
        if A[end+1]-min_v<=K and max_v-A[end+1]<=K:
            end += 1
            max_v = max([max_v, A[end]])
            min_v = min([min_v, A[end]])
        
        # A[end+1]不符合K条件,要移动begin了
        else:
            next_v = A[end+1]
            if next_v>max_v: # next_v是新的max_v
                while next_v - min_v > K:
                    result += end-begin+1
                    if result>1000000000:
                        return 1000000000
                    if begin<end:
                        begin += 1
                        if A[begin-1]==min_v:
                            min_v = min(A[begin:end+1])
                    else:
                        begin = end = end+1
                        min_v = A[begin]
                max_v = next_v
            elif next_v<min_v: # A[end+1]是新的min_v
                while max_v - next_v > K:
                    result += end-begin+1
                    if result>1000000000:
                        return 1000000000
                    if begin<end:
                        begin += 1
                        if A[begin-1]==max_v:
                            max_v = max(A[begin:end+1])
                    else:
                        begin = end = end+1
                        max_v = A[begin]
                min_v = next_v
    # end==N-1时,不断移动begin并增加result
    while begin<=end:
        result += end-begin+1
        if result>1000000000:
            return 1000000000
        begin += 1
    return result

然而我自己写出来的上边这个解法,最终只能实现90%的Performance分数,在一个长数组的Case下会超时,最终还是参考了官方的Solution PDF,一共介绍了O(N^2),O(NlogN),O(N)三种复杂度的解法,还是很值得读一读的:Official Solution

def solution(K, A):
    N = len(A)
    maxINT = 1000000000
    
    maxQ = [0] * (N + 1)
    posmaxQ = [0] * (N + 1)
    minQ = [0] * (N + 1)
    posminQ = [0] * (N + 1)

    firstMax, lastMax = 0, -1
    firstMin, lastMin = 0, -1
    j, result = 0, 0

    for i in range(N):
        while (j < N):
            # added new maximum element
            while (lastMax >= firstMax and maxQ[lastMax] <= A[j]):
                lastMax -= 1
            lastMax += 1
            maxQ[lastMax] = A[j]
            posmaxQ[lastMax] = j

            # added new minimum element
            while (lastMin >= firstMin and minQ[lastMin] >= A[j]):
                lastMin -= 1
            lastMin += 1
            minQ[lastMin] = A[j]
            posminQ[lastMin] = j
            
            if (maxQ[firstMax] - minQ[firstMin] <= K):
                j += 1
            else:
                break
        result += (j - i)
        if result >= maxINT:
            return maxINT
        if posminQ[firstMin] == i:
            firstMin += 1
        if posmaxQ[firstMax] == i:
            firstMax += 1
    return result

image.png

TreeLongestZigZag: Given a tree, find a downward path with the maximal number of direction changes.

image.png image.png

问题概述:本题输入为一棵二叉树,需要返回的是二叉树中的最长ZigZag路径长度。而ZigZag路径长度,为从根节点开始,走到最后的叶子节点过程中发生turn的次数,而turn的定义就比如当前节点是父节点的右子树,但路径的下一步是当前节点的左子树,还是比较容易理解的。

解法概述:可以使用递归的方式来解决该问题。对每个节点,我们可以分别计算其左子树和右子树的最长ZigZag路径长度,因此max(左子树最长ZigZag路径长度,右子树最长ZigZag路径长度)即为当前节点的最长ZigZag路径长度。而每个节点的左子树和右子树最长ZigZag路径长度,则需要依赖再维护一个direction变量来判断是否turn实现,综合上述解题思路最终实现的solution如下:

from extratypes import Tree  # library with types used in the task

def get_max_zigzag(T, direct, now_zigzag):
    # direct: 0-begin, 1-left, 2-right
    if T.l:
        if direct==2:
            l_zigzag = get_max_zigzag(T.l, 1, now_zigzag+1)
        else:
            l_zigzag = get_max_zigzag(T.l, 1, now_zigzag)
    else:
        l_zigzag=now_zigzag
    if T.r:
        if direct==1:
            r_zigzag = get_max_zigzag(T.r, 2, now_zigzag+1)
        else:
            r_zigzag = get_max_zigzag(T.r, 2, now_zigzag)
    else:
        r_zigzag=now_zigzag
    # print(T.x, now_zigzag)
    return max(l_zigzag, r_zigzag)

def solution(T):
    # Implement your solution here
    return get_max_zigzag(T, 0, 0)

image.png

CountriesCount: Count the number of different countries that a map contains.

image.png

问题概述:本题输入是一个由二维数组表示的矩阵,其中的一个单元表示一片土地,然后值代表了这个单元格的颜色。如果两个同色单元格相邻,则归属同一个国家。最终需要返回的,是计算该矩阵中有多少个不同的国家。

解法概述:本题解法其实不难思考,本身最低的时间复杂度也要达到M*N的程度,只是我们可以在【上->下+左->右】的循环搜索过程中,对于每个countries的area进行DFS,然后单独维护一个visited数组来记录每个单元是否已经被访问过了,这样就可以计算出最终的countries的数量。 所以主要的难点其实是上述思路的代码实现上,具体步骤如下:

  1. 初始化一个所有元素都为false的visited数组,用于记录单元是否已被访问。
  2. 定义一个countries变量,该变量用于计算不同国家的数量,并初始化为0。
  3. 遍历所有单元,并对尚未访问的单元进行DFS(深度优先搜索),此时标注countries+1。
  4. DFS搜索过程中,标记已访问的单元,如果周边搜索单元的color和起始搜索单元color相同,则继续搜索。
  5. 完成3的遍历后,返回countries。
# import sys 
# sys.setrecursionlimit(10000000)

def dfs(A, i, j, visited):
    color = A[i][j]
    visited[i][j]=True
    if j>0 and not visited[i][j-1] and A[i][j-1]==color: # left 
        visited = dfs(A, i, j-1, visited)
    if i>0 and not visited[i-1][j] and A[i-1][j]==color: # up
        visited = dfs(A, i-1, j, visited)
    if j<len(A[0])-1 and not visited[i][j+1] and A[i][j+1]==color: # right
        visited = dfs(A, i, j+1, visited)
    if i<len(A)-1 and not visited[i+1][j] and A[i+1][j]==color : # down
        visited = dfs(A, i+1, j, visited)
    return visited

def solution(A):
    visited = [[False for v in a ] for a in A]
    countries = 0
    for i,a in enumerate(A):
        for j,v in enumerate(a):
            if not visited[i][j]: # 未被访问过的area
                countries += 1
                # 广度搜索,在visited标记上该coutry对应的所有area
                visited = dfs(A, i, j, visited)
    return countries 

本身正确解法确实是DFS(考虑Performance),但是如果按照Python默认的语法来做,则有两个case会报exceed maximal recursion limit的错。这里我先是改成了BFS的思路(见下方第二部分的solution函数),结果发现有一个case改成bfs之后就会超时了。

image.png

最后在DFS这部分的solution最前边,加上了import sys和sys.setsetrecursionlimit()后(注释掉的部分),可以顺利运行达到Performance 100%。 image.png

def bfs(A, i, j, visited):
    color = A[i][j]
    queue = [(i,j)]
    while len(queue)>0:
        i,j = queue.pop(0)
        visited[i][j]=True
        if j>0 and not visited[i][j-1] and A[i][j-1]==color: # left
            queue.append((i,j-1))
        if i>0 and not visited[i-1][j] and A[i-1][j]==color: # up
            queue.append((i-1,j))
        if j<len(A[0])-1 and not visited[i][j+1] and A[i][j+1]==color: # right
            queue.append((i,j+1))
        if i<len(A)-1 and not visited[i+1][j] and A[i+1][j]==color : # down
            queue.append((i+1,j))    
    return visited

def solution(A):
    visited = [[False for v in a ] for a in A]
    countries = 0
    for i,a in enumerate(A):
        for j,v in enumerate(a):
            if not visited[i][j]: # 未被访问过的area
                countries += 1
                # 广度搜索,在visited标记上该coutry对应的所有area
                visited = bfs(A, i, j, visited)
    return countries 

image.png