线性时间解决方案

时间:2015-09-19 01:56:47

标签: algorithm language-agnostic

在面试中我被问到这个问题。

你站在0,你必须到达一个位置X.你可以跳到D(1到D)。如果X> D,很明显你在初始跳跃时无法到达位置X.

现在,每秒从1到N出现在随机位置的瓦片。这是作为零索引阵列A [k]给出的,其中A [k]表示出现在第k秒的瓦片的位置。你必须找到,在那时你可以到达(或越过)目的地X.

如果可能在初始时或在A [0]之后,则返回0,或返回最小秒。如果即使在所有瓷砖之后也不可能,则返回-1。

约束: 1 <= N <= 100,000

1 <= D <= 100,000

1 <= X <= 100,000

1&lt; = A [i]&lt; = X

例如

X = 7,D = 3

A = {1,3,1,4,2,5}

然后回答是3.因为在第3个第二个图块出现在位置4并且可以达到X = 7。在此之前的任何一秒都不可能。

我知道这是一个措辞太多的问题,但如果我无法沟通,我绝对可以清除任何问题。

问题在于,预期的时间复杂度为O(N),您可以使用额外的空间O(X)。

我找到了一个O(n * log n * log n)的解决方案。这是二次搜索并获得第一个[1..mid]元素,按位置排序并验证解决方案。它似乎通过了测试用例,但它不是线性的。

我努力但找不到任何O(N)解决方案。你能帮我么?

5 个答案:

答案 0 :(得分:0)

线性是指瓷砖数量的线性,对吗?

如果是这样,这个解决方案(在Java中)只迭代一次tile数组。

在每次迭代中,它还需要迭代D和X次,但它相对于tile数组的大小是线性的。

如果它听起来与您正在寻找的相似,请告诉我。

注意:为简化起见,我假设位置“0”的图块在第二个数字“0”处可用,因此有效地将第二个“0”视为仅存在您所在图块的时间,然后其他图块出现在秒1,2等处

    public class TestTiles {

        public static int calculateSecond(Integer x, Integer d, int[] tiles) {
            // start by making all positions unreachable (false)
            boolean[] posReachable = new boolean[x+1];
            // iterate each second only once
            for (int second = 0; second < tiles.length; second++) {
                int tile = tiles[second];  // this tile is available now
                // so mark all positions from "tile" to "tile + d" reachable
                for (int pos = tile; (pos <= tile + d) && pos <= x; pos++) {
                    posReachable[pos] = true;
                }
                // are all positions reachable now? if so, this is the second to return
                boolean reachable = true;
                for (int pos = 0; pos <= x; pos++) {
                    reachable &= posReachable[pos];
                }
                if (reachable) return second;
            }
            // we can't reach the position
            return -1;
        }

        public static void main(String[] args) {
            System.out.println(calculateSecond(7, 3, new int[]{0,1,3,1,4,2,5}));
            System.out.println(calculateSecond(20, 3, new int[]{0,1,3,1,4,2,5}));
            System.out.println(calculateSecond(2, 3, new int[]{0,1,3,1,4,2,5}));
            System.out.println(calculateSecond(4, 3, new int[]{0,1,3,1,4,2,5}));
            System.out.println(calculateSecond(15, 3, new int[]{0,12,3,9,6}));
        }

    }

答案 1 :(得分:0)

以下提案应花费时间O(N * log(min(N,X / D)))。请注意,特别是在O(N * log(N))中,因此具有比您提出的算法或mcdowella提出的优先级队列算法更好的界限;在O(N *(X + D))中,因此具有比eugenioy提出的算法更好的界限; 随着D的增加而增加(如mcdowella的数组算法,eugenioy的算法和coproc的算法那样);而且对于固定的X是O(N)。

我们的想法是保留一组我们仍然需要找到路径的间隔。我们将这个集合存储在一个平衡树中,其中键是区间的下限,其值是上限。当我们看到一个新的图块时,我们会找到包含此图块的间隔(如果有的话),并在图块周围分割间隔,丢弃任何小于D的结果间隔。当我们的地图为空时,我们就完成了。 / p>

Haskell中的完整实现如下。

import Data.Ix
import Data.Map
import qualified Data.Map as M

-- setup: the initial set of intervals is just the singleton from 0 to x
search :: Int -> Int -> [Int] -> Maybe Int
search d x = search_ d (insertCandidate d 0 x empty)

search_ :: Int -> Map Int Int -> [Int] -> Maybe Int
search_ d = go where
    -- first base case: we've found all the paths we care about
    go intervals _ | M.null intervals = Just 0
    -- second base case: we're out of tiles, and still don't have all the paths
    go _ [] = Nothing
    -- final case: we need to take a time step. add one, and recursively search the remaining time steps
    go intervals (tile:tiles) = (1+) <$> go newIntervals tiles where
        newIntervals = case lookupLE tile intervals of
            Just (lo, hi) | inRange (lo, hi) tile
                -> insertCandidate d lo tile
                .  insertCandidate d tile hi
                .  delete lo
                $  intervals
            _ -> intervals

-- only keep non-trivial intervals
insertCandidate :: Int -> Int -> Int -> Map Int Int -> Map Int Int
insertCandidate d lo hi m
    | hi - lo <= d = m
    | otherwise    = insert lo hi m

在ghci中运行此示例的一些示例(我在其他答案中无耻地删除了示例):

> search 3 7 [1,3,1,4,2,5]
Just 4
> search 3 20 [1,3,1,4,2,5]
Nothing
> search 3 2 [1,3,1,4,2,5]
Just 0
> search 3 4 [1,3,1,4,2,5]
Just 1
> search 3 15 [12,3,9,6]
Just 4

答案 2 :(得分:0)

我会逐个处理磁贴,因为它们在数组中,跟踪最大可达位置,并保持优先级队列&#34; pending&#34;瓦片。

  1. 如果瓷砖大于X,请扔掉。
  2. 如果图块已经位于可到达区域内,请将其丢弃。
  3. 如果您目前无法访问该磁贴,请将其添加到待处理队列中。
  4. 如果磁贴扩展了可到达区域,则重新处理挂起队列中现在可以访问的最近磁贴,或者在此重新处理期间可以访问。
  5. (如果现在可以访问X,请停止)。
  6. 除了从小整数优先级队列中添加和删除min的成本之外,每个处理最多两次,每次处理O(1)步,其中有专门的算法 - 请参阅{{3}为此。

答案 3 :(得分:0)

[Python中的这个解决方案类似于mcdowella&#;;但是它不是使用优先级队列,而是使用大小为X的数组来表示最多X的位置。它的复杂度为O(N+min(N,X)*D),因此它不是线性的,而是N中的线性...]

数组world跟踪位置1,...,X-1。通过跳转到最远的可到达磁贴,每个磁贴更新当前位置。

def jumpAsFarAsPossible(currentPos, D, X, world):
  for jump in range(D,0,-1): # jump = D,D-1,...,1
    reachablePos = currentPos + jump
    if reachablePos >= X:
      return X
    if world[reachablePos]:
      return jumpAsFarAsPossible(reachablePos, D, X, world)
  return currentPos

def solve(X,D,A):
  currentPos = 0
  # initially there are no tiles
  world = X * [False]

  for k,tilePos in enumerate(A):
    if tilePos < X:
      world[tilePos] = True

    # how far can we move now?
    if currentPos+D >= tilePos:
      currentPos = jumpAsFarAsPossible(currentPos, D, X, world)

    # have we reached X?
    if currentPos == X:
      return k # success in k-th second

  return -1 # X could not be reached

答案 4 :(得分:0)

这是另一次尝试:

创建一个大小为X的数组B.将其初始化为MAX_VALUE,然后填入元素B [A [i]] = min(B [A [i]],i),使B的每个元素都是巨大的或第一次在该广场上出现一块瓷砖。

将当前时间初始化为零,并从左向右沿B工作,尝试使用最多D的切片跳过0跳到X,使用不大于当前时间的B元素。如果你不能再继续下去,可以将当前时间增加到B中任何允许你跳得更远的方格中找到的最小值。

成本为O(X log(D))+ O(N) - 您通过一次成本O(N)A初始化X,然后一步一步地沿X行进。如果你保留一个优先级队列来覆盖每个时间点的X中的下一个D元素,你可以找到成本不超过log(D)的X的最小可到达元素 - 并且这些是小整数,所以你可能能够做得更好。