微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

编写一个函数 lines(a) 来确定有多少球将被销毁

如何解决编写一个函数 lines(a) 来确定有多少球将被销毁

一些不同颜色的球排成一行。当形成连续的三个或更多相同颜色的球块时,将其从线中移除。在这种情况下,所有的球都相互移动,这种情况可能会重复。

编写一个函数lines(a) 来确定有多少球将被销毁。初始时刻最多可以有一个连续的三个或三个以上相同颜色的球块。

输入数据:

函数采用带有初始球配置的列表 a。球数小于等于1000,球的颜色可以从0到9,每种颜色都有自己的整数。

输出数据:

函数必须返回一个数字,即将被销毁的球的数量

Input:[2,2,1,1] Output:6

input : [0,0],output: 5

input:[2,3,4],output: 0

我尝试使用双指针方法,但不知道该怎么做。

def lines(a):
    left = 0
    right = len(a)-1
    s=[]
    while left < right :
        if a[left] == a[left:right+1]:
            s.extend(a[left: right+1])
            del a[left: right+1]
        else:
            left += 1
            right -= 1
    return len(s) 
test_a = [2,1]
print(lines(test_a))

我认为 if a[left] == a[left:right+1]: 不起作用,我尝试从左到右比较元素与从右到左的元素相同。 del a[left: right+1] 也不起作用,我尝试删除那些已经扩展到新列表的元素。

感谢您的建议。

解决方法

您可以为此使用堆栈(简单的列表实现)。检查最后3个球是否相同。如果是,则继续弹出所有相同类型的球。否则,将球添加到堆栈中并继续。每个球可以添加一次,删除一次到列表中,所以复杂度也应该是 O(N)。
最后销毁的球数=原球数-堆叠长度。

,

我们需要计算输入列表中出现的次数。

如果当前球颜色 (element) 等于前一个球颜色 (previous),则将计数器加一。

for i,element in enumerate(a):
    if element == previous:
        counter += 1

如果它们不相等,那么下一个颜色可能会重复超过三个。因此存储当前颜色的索引

for i,element in enumerate(a):
    if element == previous:
        counter += 1
    else:
        counter = 1
        previous = element
        previous_index = i

现在检查颜色是否重复超过 3 次。

如果是,我们需要从列表中删除球。

在移除的同时,我们还需要计算被破坏的球的数量。

if counter >= 3:
    for _ in range(previous_index,i+1):
        a.pop(previous_index)
        total_destroyed += 1

可能会有混淆,我为什么要a.pop(previous_index)

如果你在一个例子上调试这部分代码。例如:[2,2,1,1]

i== 4时,当前列表为[2,1],满足count >= 3

  • Iteration = 1,列表会变成
[2,1]

如果你说删除下一个元素,这将弹出最后一个元素

  • Iteration = 2,列表会变成
[2,1]

现在,在迭代 3 中,哪个元素将被弹出?索引越界。

  • Iteration = 3,列表不会改变
[2,1]

因此,在迭代过程中总是弹出当前元素。因为下一个元素将是当前元素。

现在我们需要再次调用该方法,看看是否还有剩余的球

if counter >= 3:
    for _ in range(previous_index,i+1):
        a.pop(previous_index)
        total_destroyed += 1
    lines(a)

但是,我们必须小心,因为我们已经将 previousprevious_indexcountertotally_destroyed 声明为局部变量。

如果我们将它们保留为局部属性,所有变量都将被重新初始化,因此算法结果将不成立。

因此我们必须将它们初始化为一个全局变量并返回被破坏的球的总数。

代码:

total_destroyed = 0
counter = 0
previous = -1
previous_index = -1


def lines(a):
    """
    Args:
        a (list): input array

    Returns:
        (int): total number of destroyed balls.
    """
    global total_destroyed
    global counter
    global previous
    global previous_index

    for i,element in enumerate(a):
        if element == previous:
            counter += 1
        else:
            counter = 1
            previous = element
            previous_index = i

        if counter >= 3:
            for _ in range(previous_index,i+1):
                a.pop(previous_index)
                total_destroyed += 1
            lines(a)

    return total_destroyed


test_a = [2,1]
test_b = [0,0]
test_c = [2,3,4]
print(lines(test_a))
total_destroyed = 0
counter = 0
previous = -1
previous_index = -1
print(lines(test_b))
total_destroyed = 0
counter = 0
previous = -1
previous_index = -1
print(lines(test_c))

结果:

6
5
0
,

我的解决方案有两个部分。按球的编号 grouping_balls 分组的一种方法。然后递归方法首先检查是否仍然存在大于 3 的组,如果是,则销毁它们并将其余的合并用于下一次迭代 destroy_balls

import itertools

# Split balls by type
# For 2211121 you would get: ["22","111","2","1"]
def grouping_balls(balls):
    return ["".join(g) for k,g in itertools.groupby(balls)]

# Keeps destroying balls as long as there are groups of 3 or more
def destroy_balls(list_balls,destroyed):
    if len(list_balls) < 1:
        return destroyed
    balls_grp = grouping_balls(list_balls)
    # No more balls to destroy
    if max(map(len,balls_grp)) < 3:
        return destroyed
    # Destroying and merging balls
    else:
        non_dest_balls = ""
        for l in balls_grp:
            if len(l) < 3:
                non_dest_balls += l
            else:
                destroyed += len(l)
        return destroy_balls(non_dest_balls,destroyed)

Input = [0,0]
destroy_balls(''.join(map(str,Input)),0)
,

这是一个使用 lohi 指针遍历输入的迭代解决方案。

请注意,通过附加一个保证不是作为输入颜色的结束标记意味着检查颜色运行的逻辑不需要在 while 循环之外重复。

代码

in_outs = [([2,1],6),([0,0],5),([2,4],0),]

def test(f):
    print(f"\nSTART Testing answer {f.__doc__}")
    for arg,ans in in_outs:
        try:
            out = f(arg.copy())
        except:
            ans = '<Exception thrown!>'
        if out != ans:
            print(f" {f.__name__}({arg}) != {ans} # instead gives: {out}")
        else:
            print(f" {f.__name__}({arg}) == {out}")
    print(f"STOP  Testing answer {f.__doc__}\n")

#%% From me,Paddy3118

def lines(a):
    "From Paddy3118"
    a = a.copy() + [-1]         # Add terminator
    d = lo = hi = 0             # delete count,lo & hi pointers
    lo_c = hi_c = a[0]          # Colours at pointer positions
    while hi +1 < len(a):
        hi += 1; hi_c = a[hi]
        if lo_c != hi_c:
            if hi - lo > 2:
                d += hi - lo
                del a[lo: hi]
                lo,hi,lo_c,hi_c = 0,a[0],a[0]
            else:
                lo,lo_c = hi,hi_c
    return d

test(lines)

输出

START Testing answer From Paddy3118
 lines([2,1]) == 6
 lines([0,0]) == 5
 lines([2,4]) == 0
STOP  Testing answer From Paddy3118

检查其他示例

使用以下工具扩展上述内容以辅助测试

#%% IA from Ignacio Alorre

import itertools
def grouping_balls(balls):
    return ["".join(g) for k,g in itertools.groupby(balls)]

def destroy_balls(list_balls,destroyed)

def lines_IA(a):
    "From Ignacio Alorre"
    return destroy_balls(''.join(map(str,a)),0)

test(lines_IA)

#%% AHX from Ahx

total_destroyed = 0
counter = 0
previous = -1
previous_index = -1


def _lines_ahx(a):
    """
    Args:
        a (list): input array

    Returns:
        (int): total number of destroyed balls.
    """
    global total_destroyed
    global counter
    global previous
    global previous_index

    for i,i+1):
                a.pop(previous_index)
                total_destroyed += 1
            _lines_ahx(a)

    return total_destroyed

def lines_AHX(a):
    "From Ahx"
    global total_destroyed
    global counter
    global previous
    global previous_index

    total_destroyed = 0
    counter = 0
    previous = -1
    previous_index = -1

    return _lines_ahx(a)


test(lines_AHX)

完整输出

所有三个示例都适用于给定的测试。由于测试非常小,因此没有给出时间。

START Testing answer From Paddy3118
 lines([2,4]) == 0
STOP  Testing answer From Paddy3118


START Testing answer From Ignacio Alorre
 lines_IA([2,1]) == 6
 lines_IA([0,0]) == 5
 lines_IA([2,4]) == 0
STOP  Testing answer From Ignacio Alorre


START Testing answer From Ahx
 lines_AHX([2,1]) == 6
 lines_AHX([0,0]) == 5
 lines_AHX([2,4]) == 0
STOP  Testing answer From Ahx
,

我想到了另一种算法,在使用单个指针遍历列表并删除内容之前,首先对整个输入进行运行长度编码。它在更大的输入上效果更好。

为了测试它,我编写了一个例程 downto_zero,它生成最终都被删除的 1 和 0 序列。

我通过定时运行所有示例来跟进。

注意:在 Spyder IDE 的 Ipython shell 中运行以进行计时。

注意:Ahx 的示例充分利用了递归,但输入长度为 12 时失败,更不用说 7500。

RLE 代码和测试生成器

#%% Test Generator for long tests

in_outs = [([2,]

import sys

def test(f,in_outs=in_outs):
    print(f"\nSTART Testing answer {f.__doc__}")
    for arg,ans in in_outs:
        arg_txt = arg if len(arg) <= 8 else f"[<{len(arg)} terms>]"
        try:
            out = f(arg.copy())
        except:
            out = f'<Exception thrown! {sys.exc_info()[0]}>'
        if out != ans:
            print(f" {f.__name__}({arg_txt}) != {ans} # instead gives: {out}")
        else:
            print(f" {f.__name__}({arg_txt}) == {out}")
    print(f"STOP  Testing answer {f.__doc__}\n")

def downto_zero(n=3):
    "Test generator that reduces all input"
    if n == 0:
        return []
    x = ([0,1] * ((n+1)//2))[:n]       # 0,1 ... of length n
    e = x[-1]                           # end item
    ne = 0 if e == 1 else 1             # not end item
    r = ([e,e,ne,ne] * n)[:2*n]
    return x + r


#%% RLE Runlengh encoded from me,Paddy

from itertools import groupby

def lines_RLE(a):
    "From Paddy3118 using run-length encoding"
    a = a.copy() + [-1]         # Add terminator
    a = [[key,len(list(group))] for key,group in groupby(a)] # RLE
    d = pt = 0                  # delete count,pointer
    while pt +1 < len(a):
        i0,n0 = a[pt]          # item,count at pt
        if n0 > 2:
            d += n0
            del a[pt]
            if pt > 0:
                if a[pt - 1][0] == a[pt][0]:    # consolidate
                    a[pt - 1][1] += a[pt][1]
                    del a[pt]
                    pt -= 1
            continue
        else:
            pt += 1

    return d

test(lines_RLE,in_outs)

#%% Timed testing

print("TIMED TESTING\n=============")
n = 2_500
for f in (lines,lines_RLE,lines_IA,lines_AHX):
    dn = downto_zero(n)
    %time test(f,[(dn,len(dn))])

定时输出

START Testing answer From Paddy3118 using run-length encoding
 lines_RLE([2,1]) == 6
 lines_RLE([0,0]) == 5
 lines_RLE([2,4]) == 0
STOP  Testing answer From Paddy3118 using run-length encoding

TIMED TESTING
=============

START Testing answer From Paddy3118
 lines([<7500 terms>]) == 7500
STOP  Testing answer From Paddy3118

Wall time: 2.44 s

START Testing answer From Paddy3118 using run-length encoding
 lines_RLE([<7500 terms>]) == 7500
STOP  Testing answer From Paddy3118 using run-length encoding

Wall time: 19 ms

START Testing answer From Ignacio Alorre
 lines_IA([<7500 terms>]) == 7500
STOP  Testing answer From Ignacio Alorre

Wall time: 10.9 s

START Testing answer From Ahx
 lines_AHX([<7500 terms>]) != 7500 # instead gives: <Exception thrown! <class 'RecursionError'>>
STOP  Testing answer From Ahx

Wall time: 16 ms
,

我的第三个也是最后一个答案建立在我的第二个 RLE 条目上,通过交换使用双向链表数据结构来删除可能代价高昂的数组删除操作。与我的其他两个解决方案相比,新代码看起来具有更好的大 O 特性,因此对于更大的输入可能会更快。

双向链表代码:

#%% LL Linked-list and Runlengh encoded from me,Paddy

from itertools import groupby

def lines_LL(a):
    "From Paddy3118 using a linked-list and run-length encoding"
    a = a.copy() + [-1]         # Add terminator

    # a is list of [item,reps,prev_pointer,next_pointer]
    a = [[key,len(list(group)),i - 1,i + 1]
         for i,(key,group) in enumerate(groupby(a))] # linke-list RLE
    a[0][-2] = None                     # No previous item
    a[-1][-1] = None                    # No next item

    d = pt = 0                          # delete count,pointer
    while pt is not None:
        i0,n0,pre_pt,nxt_pt = a[pt]          # item,count,next at pt
        if n0 > 2:
            d += n0
            deleted_pt = pt
            if pre_pt is not None:
                a[pre_pt][-1] = pt = nxt_pt     # del a[pt] & pt to next
                if a[pre_pt][0] == a[pt][0]:    # consolidate same items in pre
                    a[pre_pt][1] += a[pt][1]
                    a[pre_pt][-1] = a[pt][-1]   # del a[pt]
                    pt = pre_pt                 # ... & pt to previous
            else:
                pt = nxt_pt
        else:
            pt = nxt_pt

    return d

print("SIMPLE FUNCTIONAL TESTING\n=============")
test(lines_LL,in_outs)

#%% Timed testing

if 1:
    print("TIMED TESTING\n=============")
    n = 20_000
    for f in (lines_LL,lines):  #,lines_AHX):
        dn = downto_zero(n)
        %time test(f,len(dn))])

输出和时间

SIMPLE FUNCTIONAL TESTING
=============

START Testing answer From Paddy3118 using a linked-list and run-length encoding
 lines_LL([2,1]) == 6
 lines_LL([0,0]) == 5
 lines_LL([2,4]) == 0
STOP  Testing answer From Paddy3118 using a linked-list and run-length encoding

TIMED TESTING
=============

START Testing answer From Paddy3118 using a linked-list and run-length encoding
 lines_LL([<60000 terms>]) == 60000
STOP  Testing answer From Paddy3118 using a linked-list and run-length encoding

Wall time: 104 ms

START Testing answer From Paddy3118 using run-length encoding
 lines_RLE([<60000 terms>]) == 60000
STOP  Testing answer From Paddy3118 using run-length encoding

Wall time: 387 ms

START Testing answer From Paddy3118
 lines([<60000 terms>]) == 60000
STOP  Testing answer From Paddy3118

Wall time: 2min 31s

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。