2022年 11月 16日

经典面试题:数字拆分消减问题与单链路村庄销售代价问题[python]

文章目录

  • 数字拆分消减问题
    • 题目
    • 分析
    • 代码
  • 单链路村庄销售代价问题
    • 题目
    • 分析
    • 代码

数字拆分消减问题

题目

给出两个数字

n

,

k

n,k

n,k (

n

,

k

N

+

n,k \in \N^+

n,kN+)。

n

n

n是需要拆分消减的数字序列(初始情况序列仅包含一个数字),

k

k

k是最多可拆分的次数,其中:

  • 拆分是指将一个数字的拆分序列中的每个数字

    a

    a

    a拆分成两个正整数

    a

    1

    a_1

    a1

    a

    2

    a_2

    a2

    a

    1

    +

    a

    2

    =

    a

    a_1+a_2=a

    a1+a2=a

  • 效减是秩将所有拆分得到的数字减去1;

e.g : 对数字序列[7]先拆分两次再消减一次——第一次拆分得到[4, 3]、第二次拆分得到[2, 2, 1, 2],消减后得到[1, 1, 0, 1]。
问:输入

n

,

k

n,k

n,k,请输出能把序列变为全0序列的最少次数。

分析

  1. 要使得其最快为0,显然应进行尽量多的平均拆分再进行消减。由于数字2先拆分后消减与先消减再拆分到0都需要2步,因此平均到数列中出现2即可停止。
  2. 在任意一次平均拆分结束后,如果出现奇数,则意味着下一次拆分必然会出现

    a

    a

    a

    a

    +

    1

    a+1

    a+1的情况,如[14]拆为[7 7],再拆为[3 4 3 4],若此时拆分结束,至少还需要4次消减才能将其降为0。即拆分完毕后的消减次数为:

  • 若出现奇数:消减次数 =

    m

    i

    n

    +

    1

    min+1

    min+1(

    m

    i

    n

    min

    min是拆分得到的序列最小值)

  • 若未出现奇数或本身未进行拆分:消减次数 =

    m

    i

    n

    min

    min

代码

is_odd = lambda x: x % 2 == 1

for nk in nk_list:
    n, k = nk[0], nk[1]
    _min = n  #
    found_odd = is_odd(n)
    divide_done = 0  # 拆分进行次数
    for _ in range(k):
        if _min <= 2:
            break
        found_odd = is_odd(_min)
        _min = _min // 2
        divide_done += 1

    if divide_done > 0 and found_odd:
        _min += 1

    print(divide_done+_min)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

测试数据:

# 输入数据
# 输出应为 0 1 1 2 2 5 6 16 8
nk_list = [[0, 7], [1, 0], [1, 2], [2, 1], [2, 3], [15, 3], [16, 2], [100, 3], [100, 7]]
  • 1
  • 2
  • 3

单链路村庄销售代价问题

题目

在一条直线公路上,等距分布

n

n

n个村庄(n为大于1的自然数),第

i

i

i个村庄有

s

i

s_i

si的水果要销售或购入,当

s

i

&lt;

0

s_i&lt;0

si<0则意味着要购入

s

i

s_i

si个水果,当

s

i

&gt;

0

s_i&gt;0

si>0则意味着要卖出

s

i

s_i

si个水果。已知每个水果每向旁别的存在运输一次,就产生

1

1

1个单位的代价。先给出一组数据,保证整条公路上的水果供求关系的和为0,问要使每个村庄完成购入/卖出指标,最小代价为多少。
e.g 例如输入[1, 0, -1],则最小代价为

4

4

4

分析

由于所有村庄等距分布在一条公路上,因此当我们从左往右遍历数组,会发现无论左边是正是负,必然需要被“抹平”。

  • 假设有这么一个堆(heap),存放着“水果债卷”,一个商人从走往右走,对这个堆进行分配和移动。
  • 每到一个村庄,商人先按照供需关系修改堆中的债券关系,如果

    s

    i

    &gt;

    0

    s_i&gt;0

    si>0,则堆中债券变多,否则变少。

  • 随后,商人根据债卷的价值产生移动水果的代价。如果

    s

    i

    &gt;

    0

    s_i&gt;0

    si>0,则意味着左边有数量为

    s

    i

    s_i

    si的水果要移动到右边,否则意味着右边有数量为

    s

    i

    s_i

    si的水果要移动到左边。

  • 无论向左还是向右移动,最终都会产生

    a

    b

    s

    (

    h

    e

    a

    p

    )

    abs(heap)

    abs(heap)的代价。

代码

def count(data):
    heap, cost = 0, 0
    for i in data:
        heap += i
        cost += abs(heap)
    return cost
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

测试:

s1 = [1, -1, 2, 0, -2, -4, 1, 3]  # 12
s2 = [-1, -2, -3, 0, 1, 2, 3]  # 24
s3 = [1, 2, 3, 0, -1, -2, -3]  # 24
s4 = [1, -1, 0]  # 1
s5 = [-1, 1, 0]  # 1
s6 = [2, 0, -2]  # 4

print(count(s1))
print(count(s2))
print(count(s3))
print(count(s4))
print(count(s5))
print(count(s6))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13