当某些点被阻挡时到达终点的最小跳跃

Posted

技术标签:

【中文标题】当某些点被阻挡时到达终点的最小跳跃【英文标题】:Minimum jumps to reach end when some points are blocked 【发布时间】:2019-12-14 15:24:35 【问题描述】:

一个人目前在(0,0),想到达(X,0),他必须跳几步才能到达他的房子。从某个点说(a,0),他可以跳到(a + k1,0),即向前k1 步或他可以跳(a-k2,0) 即向后跳k2 步。他第一次跳必须是向前跳,而且他不能连续向后跳两次。但是他可以跳任意一个连续向前跳。有n个点a1,a2 upto an他不能跳。

我必须确定到达他家的最少跳跃次数或得出他无法到达他家的结论。如果他可以到达房子打印是并指定否。如果不打印,跳转次数。

这里

X = location of persons house.
N = no. of points where he cannot jump.
k1 = forward jump.
k2 = backward jump.

例子

对于输入

X=6 N=2 k1=4 k2=2

Blocked points = 3 5

the answer is 3 (4 to 8 to 6 or 4 to 2 to 6)

用于输入

6 2 5 2

1 3

这个人无法到达他的房子

N can be upto 10^4 and X can be upto 10^5

我想过使用动态编程,但我无法实现它。有人可以帮忙吗?

【问题讨论】:

如果这些点总是0,为什么它们有y坐标? @kaya3 没关系。忽略他们 可以分享问题链接吗? 【参考方案1】:

我认为您使用动态编程的方向可行,但我将展示另一种解决问题的方法,该方法具有与动态编程相同的渐近时间复杂度。

这个问题可以描述为图表中的问题,其中您将X 节点索引为1X,这些是每个aa + k1bb - k2 之间的一条边,在此处删除 N 中的节点。 如果您可以向后跳多少次,但您不能连续跳两次,这就足够了,因此您可以添加以下修改:复制图形的节点,也复制向前的边,但让它们从复制到原来的,现在使所有向后的边缘都转到复制的图上。现在,每个后向边缘都会将您发送到重复的边缘,并且您将无法再次使用后向边缘,直到您使用前进边缘转到原始边缘。这将确保在后退边缘之后您将始终采取前进边缘 - 因此您将无法向前跳两次。

现在找到从1X 的最短路径就像找到最小数量的跳转,因为边是跳转。

在有向未加权图中找到最短路径需要O(|V|+|E|) 时间和内存(使用BFS),您的图有2 * X|V|,边的数量也将是2 * 2 * X 所以时间和内存O(X) 的复杂度。

如果你可以向后跳转两次,你可以使用 python 中的networkx 库进行简单的演示(你也可以使用 if 进行复杂的演示):

import matplotlib.pyplot as plt
import networkx as nx

X = 6
N = 2
k1 = 4
k2 = 2

nodes = [0, 1, 2, 4, 6]

G = nx.DiGraph()
G.add_nodes_from(nodes)

for n in nodes:
    if n + k1 in nodes:
        G.add_edge(n, n + k1)
    if n - k2 in nodes:
        G.add_edge(n, n - k2)

nx.draw(G, with_labels=True, font_weight='bold')
plt.plot()
plt.show()

path = nx.shortest_path(G, 0, X)
print(f"Number of jumps: len(path) - 1. path: str(path)")

【讨论】:

这个答案是否考虑了 k1=3、k2=4、X=2 之类的示例,其中解决方案可能是先回到 -1,然后再回到 2? 没有@גלעדברקן,但假设k1k2小于XN只包含0X之间的节点,你可以从@987654346添加节点左右。 这个答案如何考虑限制,“不能连续向后跳两次?” @Yonlif 我如何复制节点?你能在 c++ 或 c 中添加一个实现吗? @Klasen 您可能应该使用 גלעדברקן 的代码。如果你愿意,我可以用 C++ 编写。【参考方案2】:

广度优先搜索是否足够有效?

这样的? (Python代码)

from collections import deque

def f(x, k1, k2, blocked):
  queue = deque([(k1, 0, None, None)])

  while (queue):
    (p, depth, direction, prev) = queue.popleft()

    if p in blocked or (x + k2 < p < x - k1): # not sure about these boundaries ... ideas welcome
      continue

    if p == x:
      return depth

    blocked.add(p) # visited

    queue.append((p + k1, depth + 1, "left", direction))

    if prev != "right":
      queue.append((p - k2, depth + 1, "right", direction))

X = 6
k1 = 4
k2 = 2
blocked = set([3, 5])
print f(X, k1, k2, blocked)

X = 2
k1 = 3
k2 = 4
blocked = set()
print f(X, k1, k2, blocked)

【讨论】:

【参考方案3】:

这里是 גלעדברקן 在 c++ 中的代码:

#include <iostream>
#include <queue>

using namespace std;

struct node 
    int id;
    int depth;
    int direction; // 1 is left, 0 is right
;

int BFS(int start, int end, int k1, int k2, bool blocked[], int length)

    queue<node> q;
    blocked[0] = true;
    q.push(start, 0, 0);

    while(!q.empty())
    
        node f = q.front();
        q.pop();

        if (f.id == end) 
            return f.depth;
        

        if(f.id + k1 < length and !blocked[f.id + k1])
        
            blocked[f.id + k1] = true;
            q.push(f.id + k1, f.depth + 1, 0);
        
        if (f.direction != 1)  // If you just went left - don't go left again
            if(f.id - k2 >= 0 and !blocked[f.id - k2])
            
                blocked[f.id - k2] = true;
                q.push(f.id - k2, f.depth + 1, 1);
            
        
    
    return -1;



int main() 
    bool blocked[] = false, false, false, false, false, false, false;
    std::cout << BFS(0, 6, 4, 2, blocked, 7) << std::endl;
    return 0;

您可以控制步骤的长度、开始和结束以及阻塞的节点。

【讨论】:

以上是关于当某些点被阻挡时到达终点的最小跳跃的主要内容,如果未能解决你的问题,请参考以下文章

7月好题记录

使用向右或向下跳跃或单位步长在网格中的最小成本路径

跳跃游戏

在有向图中找到可以到达其他顶点的最小顶点数[关闭]

跳跃游戏II

45. 跳跃游戏 II