Today I went to a math competition and there was a question that was something like this:
You have a given number n, now you have to like calculate what's the shortest route to that number, but there are rules.
You start with number 1
You end when you reach n
You can get to n either by doubling your previous number, or by adding two previous numbers.
Example: n = 25
Slowest route : 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25
(You just keep adding 1)
Fastest route : 1,2,4,8,16,24,25, complexity = 6
Example: n = 8
Fastest route : 1,2,4,8, complexity = 3
Example : n = 15
Fastest route : 1,2,3,6,9,15, complexity = 5
How do I make a program that can calculate the complexity of a given number n (with n <= 32)?
I already know that for any given number n ( n <= 32 ) that the complexity is lower that 1.45 x 2log(n).
So now I only need to calculate all the routes with complexity below 1.45 x 2log(n) and than compare them and see which one is the fastest 'route'.
But I have no idea how to put all the routes and all this in python, because the number of routes changes when the given number n changes.
This is what I have for now:
number = raw_input('Enter your number here : ')
startnumber = 1
complexity = 0
while startnumber <= number
I accept the challenge :)
The algorithm is relatively fast. It calculates the complexity of the first 32 numbers in 50ms on my computer, and I don't use multithreading. (or 370ms for the first 100 numbers.)
It is a recursive branch and cut algorithm. The _shortest function takes 3 arguments: the optimization lies in the max_len argument. E.g. if the function finds a solution with length=9, it stops considering any paths with a length > 9. The first path that is found is always a pretty good one, which directly follows from the binary representation of the number. E.g. in binary: 111001 => [1,10,100,1000,10000,100000,110000,111000,111001]. That's not always the fastest path, but if you only search for paths that are as least as fast, you can cut away most of the search tree.
#!/usr/bin/env python
# Find the shortest addition chain...
# #param acc List of integers, the "accumulator". A strictly monotonous
# addition chain with at least two elements.
# #param target An integer > 2. The number that should be reached.
# #param max_len An integer > 2. The maximum length of the addition chain
# #return A addition chain starting with acc and ending with target, with
# at most max_len elements. Or None if such an addition chain
# does not exist. The solution is optimal! There is no addition
# chain with these properties which can be shorter.
def _shortest(acc, target, max_len):
length = len(acc)
if length > max_len:
return None
last = acc[-1]
if last == target:
return acc;
if last > target:
return None
if length == max_len:
return None
last_half = (last / 2)
solution = None
potential_solution = None
good_len = max_len
# Quick check: can we make this work?
# (this improves the performance considerably for target > 70)
max_value = last
for _ in xrange(length, max_len):
max_value *= 2
if max_value >= target:
break
if max_value < target:
return None
for i in xrange(length-1, -1, -1):
a = acc[i]
if a < last_half:
break
for j in xrange(i, -1, -1):
b = acc[j]
s = a+b
if s <= last:
break
# modifying acc in-place has much better performance than copying
# the list and doing
# new_acc = list(acc)
# potential_solution = _shortest(new_acc, target, good_len)
acc.append(s)
potential_solution = _shortest(acc, target, good_len)
if potential_solution is not None:
new_len = len(potential_solution)
solution = list(potential_solution)
good_len = new_len-1
# since we didn't copy the list, we have to truncate it to its
# original length now.
del acc[length:]
return solution
# Finds the shortest addition chain reaching to n.
# E.g. 9 => [1,2,3,6,9]
def shortest(n):
if n > 3:
# common case first
return _shortest([1,2], n, n)
if n < 1:
raise ValueError("n must be >= 1")
return list(xrange(1,n+1))
for i in xrange(1,33):
s = shortest(i)
c = len(s) - 1
print ("complexity of %2d is %d: e.g. %s" % (i,c,s))
Output:
complexity of 1 is 0: e.g. [1]
complexity of 2 is 1: e.g. [1, 2]
complexity of 3 is 2: e.g. [1, 2, 3]
complexity of 4 is 2: e.g. [1, 2, 4]
complexity of 5 is 3: e.g. [1, 2, 4, 5]
complexity of 6 is 3: e.g. [1, 2, 4, 6]
complexity of 7 is 4: e.g. [1, 2, 4, 6, 7]
complexity of 8 is 3: e.g. [1, 2, 4, 8]
complexity of 9 is 4: e.g. [1, 2, 4, 8, 9]
complexity of 10 is 4: e.g. [1, 2, 4, 8, 10]
complexity of 11 is 5: e.g. [1, 2, 4, 8, 10, 11]
complexity of 12 is 4: e.g. [1, 2, 4, 8, 12]
complexity of 13 is 5: e.g. [1, 2, 4, 8, 12, 13]
complexity of 14 is 5: e.g. [1, 2, 4, 8, 12, 14]
complexity of 15 is 5: e.g. [1, 2, 4, 5, 10, 15]
complexity of 16 is 4: e.g. [1, 2, 4, 8, 16]
complexity of 17 is 5: e.g. [1, 2, 4, 8, 16, 17]
complexity of 18 is 5: e.g. [1, 2, 4, 8, 16, 18]
complexity of 19 is 6: e.g. [1, 2, 4, 8, 16, 18, 19]
complexity of 20 is 5: e.g. [1, 2, 4, 8, 16, 20]
complexity of 21 is 6: e.g. [1, 2, 4, 8, 16, 20, 21]
complexity of 22 is 6: e.g. [1, 2, 4, 8, 16, 20, 22]
complexity of 23 is 6: e.g. [1, 2, 4, 5, 9, 18, 23]
complexity of 24 is 5: e.g. [1, 2, 4, 8, 16, 24]
complexity of 25 is 6: e.g. [1, 2, 4, 8, 16, 24, 25]
complexity of 26 is 6: e.g. [1, 2, 4, 8, 16, 24, 26]
complexity of 27 is 6: e.g. [1, 2, 4, 8, 9, 18, 27]
complexity of 28 is 6: e.g. [1, 2, 4, 8, 16, 24, 28]
complexity of 29 is 7: e.g. [1, 2, 4, 8, 16, 24, 28, 29]
complexity of 30 is 6: e.g. [1, 2, 4, 8, 10, 20, 30]
complexity of 31 is 7: e.g. [1, 2, 4, 8, 10, 20, 30, 31]
complexity of 32 is 5: e.g. [1, 2, 4, 8, 16, 32]
There is a dynamic programming solution to your problem since you either add any two numbers or multiply a number by 2 we can try all those cases and choose the minimum one also if the complexity of 25 was 5 and the route contains 9 then we know the solution for 9 which is 4 and we can use the solution for 9 to generate the solution for 25.We also need to keep track of every possible minimum solution for m to be able to use it to use it to solve n where m < n
def solve(m):
p = [set([frozenset([])]) for i in xrange(m+1)] #contains all paths to reach n
a = [9999 for _ in xrange(m+1)]
#contains all complexities initialized with a big number
a[1] = 0
p[1] = set([frozenset([1])])
for i in xrange(1,m+1):
for pos in p[i]:
for j in pos: #try adding any two numbers and 2*any number
for k in pos:
if (j+k <= m):
if a[j+k] > a[i]+1:
a[j+k] = a[i] + 1
p[j+k] = set([frozenset(list(pos) + [j+k])])
elif a[j+k] == a[i]+1:
p[j+k].add(frozenset(list(pos) + [j+k]))
return a[m],sorted(list(p[m].pop()))
n = int(raw_input())
print solve(n)
this can solve up to n = 100
For larger numbers you can get a 30% or more speedup by adding a couple lines to remove some redundant calculations from the inner loop. For this the pos2 variable is created and trimmed on each iteration:
def solve(m):
p = [set([frozenset([])]) for i in xrange(m+1)] #contains all paths to reach n
a = [9999 for _ in xrange(m+1)]
#contains all complexities initialized with a big number
a[1] = 0
p[1] = set([frozenset([1])])
for i in xrange(1,m+1):
for pos in p[i]:
pos2 = set(pos)
for j in pos: #try adding any two numbers and 2*any number
for k in pos2:
if (j+k <= m):
if a[j+k] > a[i]+1:
a[j+k] = a[i] + 1
p[j+k] = set([frozenset(list(pos) + [j+k])])
elif a[j+k] == a[i]+1:
p[j+k].add(frozenset(list(pos) + [j+k]))
pos2.remove(j)
return a[m],sorted(list(p[m].pop()))
Brute forcing this
def solve(m, path):
if path[-1] == m:
return path
if path[-1] > m:
return False
best_path = [i for i in range(m)]
test_path = solve (m, path + [path[-1]*2])
if test_path and len(test_path) < len(best_path):
best_path = test_path
for k1 in path[:-1] :
for k2 in path[:-1] :
test_path = solve (m, path + [path[-1] + k1 + k2])
if test_path and len(test_path) < len(best_path):
#retain best
best_path = test_path
return best_path
print (solve(19, [1,2])) #[1, 2, 4, 8, 16, 19]
print (solve(25, [1,2])) #[1, 2, 4, 8, 16, 25]
Runs fairly slow, I'm pretty sure a smarter solution exist but this look semantically correct
A brute force technique that simply searches through all the possible paths until it reaches its target. It is extremely fast as it does not evaluate numbers that have been reached with a lower complexity, but is guaranteed to find the optimal path.
# Use a node class that contains the number as well as a pointer to its parent
class Node:
def __init__(self, number, parent):
self.number = number
self.parent = parent
# get a list of all the numbers to reach this node
def getPath(self):
path = [self.number]
parent = self.parent
while parent != None:
path.append(parent.number)
parent = parent.parent
return path
def solve(start, target):
currentList = [] # List of visited nodes in the previous round
nextList = [Node(start, None)] # List of visited nodes in the next round (start with at least one number)
seen = set([start]) # store all number that have already been seen in the previous round
while nextList: # continue until the final number has reached, on each iteration the complexity grows
currentList = nextList # swap the lists around
nextList = []
for n in currentList:
path = n.getPath() # fetch all the number that are needed to reach this point
if n.number == target: # check of we have reach our target
return path[::-1] # the path is in reverse at this point, so reverse it back
for a in path: # for any combination of the last number and a previous number (including the last, which is the same as multiplying it by 2)
newnumber = a + path[0]
if newnumber <= target and newnumber not in seen: # only evaluate the new number if is is not yet seen already on a lower complexity
nextList.append(Node(newnumber, n))
for n in nextList: # update the seen list
seen.add(n.number)
return [] # if the destination could not be reached
print "path to 25 = ", solve(1, 25)
print "path to 8 = ", solve(1, 8)
print "path to 15 = ", solve(1, 15)
print "path to 500 = ", solve(1, 500)
will output the following:
path to 25 = [1, 2, 4, 8, 16, 24, 25]
path to 8 = [1, 2, 4, 8]
path to 15 = [1, 2, 4, 5, 10, 15]
path to 500 = [1, 2, 4, 8, 16, 32, 64, 96, 100, 200, 400, 500]
I've tested this method to solve variables up to 500, and it was able to solve it in 0.36 seconds.
Related
I have a simple code that generates a list of random numbers.
x = [random.randrange(0,11) for i in range(10)]
The problem I'm having is that, since it's random, it sometimes produces duplicate numbers right next to each other. How do I change the code so that it never happens? I'm looking for something like this:
[1, 7, 2, 8, 7, 2, 8, 2, 6, 5]
So that every time I run the code, all the numbers that are next to each other are different.
x = []
while len(x) < 10:
r = random.randrange(0,11)
if not x or x[-1] != r:
x.append(r)
x[-1] contains the last inserted element, which we check not to be the same as the new random number. With not x we check that the array is not empty, as it would generate a IndexError during the first iteration of the loop
Here's an approach that doesn't rely on retrying:
>>> import random
>>> x = [random.choice(range(12))]
>>> for _ in range(9):
... x.append(random.choice([*range(x[-1]), *range(x[-1]+1, 12)]))
...
>>> x
[6, 2, 5, 8, 1, 8, 0, 4, 6, 0]
The idea is to choose each new number by picking from a list that excludes the previously picked number.
Note that having to re-generate a new list to pick from each time keeps this from actually being an efficiency improvement. If you were generating a very long list from a relatively short range, though, it might be worthwhile to generate different pools of numbers up front so that you could then select from the appropriate one in constant time:
>>> pool = [[*range(i), *range(i+1, 3)] for i in range(3)]
>>> x = [random.choice(random.choice(pool))]
>>> for _ in range(10000):
... x.append(random.choice(pool[x[-1]]))
...
>>> x
[0, 2, 0, 2, 0, 2, 1, 0, 1, 2, 0, 1, 2, 1, 0, ...]
O(n) solution by adding to the last element randomly from [1,stop) modulo stop
import random
x = [random.randrange(0,11)]
x.extend((x[-1]+random.randrange(1,11)) % 11 for i in range(9))
x
Output
[0, 10, 4, 5, 10, 1, 4, 8, 0, 9]
from random import randrange
from itertools import islice, groupby
# Make an infinite amount of randrange's results available
pool = iter(lambda: randrange(0, 11), None)
# Use groupby to squash consecutive values into one and islice to at most 10 in total
result = [v for v, _ in islice(groupby(pool), 10)]
Function solution that doesn't iterate to check for repeats, just checks each add against the last number in the list:
import random
def get_random_list_without_neighbors(lower_limit, upper_limit, length):
res = []
# add the first number
res.append(random.randrange(lower_limit, upper_limit))
while len(res) < length:
x = random.randrange(lower_limit, upper_limit)
# check that the new number x doesn't match the last number in the list
if x != res[-1]:
res.append(x)
return res
>>> print(get_random_list_without_neighbors(0, 11, 10)
[10, 1, 2, 3, 1, 8, 6, 5, 6, 2]
def random_sequence_without_same_neighbours(n, min, max):
x = [random.randrange(min, max + 1)]
uniq_value_count = max - min + 1
next_choises_count = uniq_value_count - 1
for i in range(n - 1):
circular_shift = random.randrange(0, next_choises_count)
x.append(min + (x[-1] + circular_shift + 1) % uniq_value_count)
return x
random_sequence_without_same_neighbours(n=10, min=0, max=10)
It's not to much pythonic but you can do something like this
import random
def random_numbers_generator(n):
"Generate a list of random numbers but without two duplicate numbers in a row "
result = []
for _ in range(n):
number = random.randint(1, n)
if result and number == result[-1]:
continue
result.append(number)
return result
print(random_numbers_generator(10))
Result:
3, 6, 2, 4, 2, 6, 2, 1, 4, 7]
I am trying to find elements from array(integer array) or list which are unique and those elements must not divisible by any other element from same array or list.
You can answer in any language like python, java, c, c++ etc.
I have tried this code in Python3 and it works perfectly but I am looking for better and optimum solution in terms of time complexity.
assuming array or list A is already sorted and having unique elements
A = [2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
while i<len(A)-1:
while j<len(A):
if A[j]%A[i]==0:
A.pop(j)
else:
j+=1
i+=1
j=i+1
For the given array A=[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] answer would be like ans=[2,3,5,7,11,13]
another example,A=[4,5,15,16,17,23,39] then ans would be like, ans=[4,5,17,23,39]
ans is having unique numbers
any element i from array only exists if (i%j)!=0, where i!=j
I think it's more natural to do it in reverse, by building a new list containing the answer instead of removing elements from the original list. If I'm thinking correctly, both approaches do the same number of mod operations, but you avoid the issue of removing an element from a list.
A = [2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
ans = []
for x in A:
for y in ans:
if x % y == 0:
break
else: ans.append(x)
Edit: Promoting the completion else.
This algorithm will perform much faster:
A = [2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
if (A[-1]-A[0])/A[0] > len(A)*2:
result = list()
for v in A:
for f in result:
d,m = divmod(v,f)
if m == 0: v=0;break
if d<f: break
if v: result.append(v)
else:
retain = set(A)
minMult = 1
maxVal = A[-1]
for v in A:
if v not in retain : continue
minMult = v*2
if minMult > maxVal: break
if v*len(A)<maxVal:
retain.difference_update([m for m in retain if m >= minMult and m%v==0])
else:
retain.difference_update(range(minMult,maxVal,v))
if maxVal%v == 0:
maxVal = max(retain)
result = list(retain)
print(result) # [2, 3, 5, 7, 11, 13]
In the spirit of the sieve of Eratostenes, each number that is retained, removes its multiples from the remaining eligible numbers. Depending on the magnitude of the highest value, it is sometimes more efficient to exclude multiples than check for divisibility. The divisibility check takes several times longer for an equivalent number of factors to check.
At some point, when the data is widely spread out, assembling the result instead of removing multiples becomes faster (this last addition was inspired by Imperishable Night's post).
TEST RESULTS
A = [2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] (100000 repetitions)
Original: 0.55 sec
New: 0.29 sec
A = list(range(2,5000))+[9697] (100 repetitions)
Original: 3.77 sec
New: 0.12 sec
A = list(range(1001,2000))+list(range(4000,6000))+[9697**2] (10 repetitions)
Original: 3.54 sec
New: 0.02 sec
I know that this is totally insane but i want to know what you think about this:
A = [4,5,15,16,17,23,39]
prova=[[x for x in A if x!=y and y%x==0] for y in A]
print([A[idx] for idx,x in enumerate(prova) if len(prova[idx])==0])
And i think it's still O(n^2)
If you care about speed more than algorithmic efficiency, numpy would be the package to use here in python:
import numpy as np
# Note: doesn't have to be sorted
a = [2, 2, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 16, 29, 29]
a = np.unique(a)
result = a[np.all((a % a[:, None] + np.diag(a)), axis=0)]
# array([2, 3, 5, 7, 11, 13, 29])
This divides all elements by all other elements and stores the remainder in a matrix, checks which columns contain only non-0 values (other than the diagonal), and selects all elements corresponding to those columns.
This is O(n*M) where M is the max size of an integer in your list. The integers are all assumed to be none negative. This also assumes your input list is sorted (came to that assumption since all lists you provided are sorted).
a = [4, 7, 7, 8]
# a = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
# a = [4, 5, 15, 16, 17, 23, 39]
M = max(a)
used = set()
final_list = []
for e in a:
if e in used:
continue
else:
used.add(e)
for i in range(e, M + 1):
if not (i % e):
used.add(i)
final_list.append(e)
print(final_list)
Maybe this can be optimized even further...
If the list is not sorted then for the above method to work, one must sort it. The time complexity will then be O(nlogn + Mn) which equals to O(nlogn) when n >> M.
I am trying to write a function that iterates through a list to return the sum of the integer elements in the list. However if there is a 13 in the list, it needs to exclude the 13 and whatever comes right after it from the sum. So far I have come up with the following code. This works for almost all cases except when there are two 13's back to back. It only excludes the two 13's instead of the two 13's plus whatever is after the second 13. For example, when I call the function with print(sum13([13, 13, 2, 13, 1, 5, 6, 13])), instead of giving the output of 11 it gives 13. Any help is appreciated.
def sum13(nums):
while 13 in nums:
index13 = nums.index(13)
nums = nums[0:index13]+nums[index13+2:]
return sum(nums)
The idiomatic way of doing something like this is to keep track of the previous variable, for example:
def sum13(nums):
prev = None
my_sum = 0
for ele in nums:
if ele != 13 and prev != 13:
my_sum += ele
prev = ele
return my_sum
if __name__ == "__main__":
print(sum13([1,2,13,3,4]))
print(sum13([13, 13, 2, 13, 1, 5, 6, 13]))
Result:
7
11
You can do it functionally:
def sum13(nums):
return sum(current
for current, following
in zip(nums, [None] + nums[:-1])
if current != 13 and following != 13)
"Gimme a sum of all numbers that are not 13, and whose next number is not 13".
You could use a slice to determine whether a 13 precedes a number:
def sum13(nums):
sum_ = 0
for index, elem in enumerate(nums):
if 13 not in nums[max(index-1, 0):index+1]:
sum_ += elem
return sum_
The max is needed only for the first element, being that i-1 would be 0-1, so the slice would be [-1:1], which is empty. You can even cut it down to one line, if you really wanted to:
sum(e for i, e in enumerate(nums) if 13 not in nums[max(i-1, 0):i+1])
Because generators need love too:
l = [13, 13, 2, 13, 1, 5, 6, 13]
def sanitize(list):
il = iter(list)
for e in il:
while e == 13:
e = next(il)
if e != 13:
e = next(il)
yield e
print(sum(sanitize(l)))
The idea being that you clean up your list first and then sum it up.
Try the below. Note that this is using nexted function to remove 13s, or a series of 13s and the number right after
def refine(nums, index_i):
while 13 in nums:
print(nums)
index = len(nums) - nums[::-1].index(13) - 1
if index == index_i -1:
nums = refine(nums[0:index] + nums[index+1:], index)
else:
nums = refine(nums[0:index] + nums[index+2:], index)
return nums
print( sum(refine([0, 13, 1, 2, 3, 4, 13, 13, 7, 13, 13, 5, 6, 13], 0)) )
This will sum only [0, 2, 3, 4, 6]
However, this is assuming you have a small list to manage With large list, you might want to think of the performance of such manipulation
This is question for my interview.
Write a recursive function that does the following:
Input: An array A of length N. N is an even number and N >= 2.
Output: A reordered array B. The first half of B contains A’s elements with even indices. The second half of B contains A’s elements with odd indices. Convention: the first index of an array is 0 (and thus it is an even number).
Input 1: [4, 8, 12, 16]
For this array, the indices and the values are as follows:
Index: 0, 1, 2, 3
Value: 4, 8, 12, 16
Thus, the output is as follows:
Expected output 1: [4, 12, 8, 16]
ADDITIONAL TEST CASE
Input 2: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Expected output 2: [1, 3, 5, 7, 9, 2, 4, 6, 8, 10]
TASK
Write a recursive function in a programming language of your choice (as if you are writing real code to be used on a production server) for the above problem
In addition to the main function, you are free to write helper functions (if needed)
The code should have as few lines as possible (but it should still be clear and readable)
Note: Your recursive function must show the 'spirit' of a recursive function (not just the recursive form of a for loop)
Here is my code:
def slove(array, deep=0):
'''para:
array: list input.
return: list.
!!!Do not set value for deep!!!'''
if len(array) > 2:
if deep > 0:
for i in xrange(0, len(array), 2):
array[i], array[i + 1] = array[i + 1], array[i]
left = array[0]
right = array[-1]
array = array[1:-1]
array = slove(array, deep + 1)
array.insert(0, left)
array.append(right)
return array
else:
array[0], array[-1] = array[-1], array[0]
return array
if __name__ == '__main__':
array = map(int, raw_input('Enter array with sep is space key: ').split(' '))
# array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print slove(array)
He said that it is wrong because I use loop in program. He is correct? So how to solve it?
Why not just use slicing?
lst = [11,12,13,14,15,16]
lst[0::2] + lst[1::2]
Returns:
[11, 13, 15, 12, 14, 16]
This pseudocode function might help:
Let A[n] and B[n] be the 2 arrays where n is the size.
Then we will call the following method rearrange(0,0):
rearrange(int i, int j) {
b[j] = a[i];
b[j+n/2] = a[i+1];
if (j < n/2-1)
rearrange(i+2,j+1);
}
In this method, i jumps 2 times each therefore the odd items get stored in the first half of the output array. For the second half, j+n/2 saves the even items.
This is one (awkward) way to do it:
def even_odd_split(seq):
"""
>>> even_odd_split([4, 8, 12, 16])
[4, 12, 8, 16]
>>> even_odd_split([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
[1, 3, 5, 7, 9, 2, 4, 6, 8, 10]
"""
assert(len(seq) % 2 == 0)
assert(len(seq) > 1)
def _split(seq):
if len(seq) == 0: return [], []
a, b = _split(seq[2:])
return [seq[0]] + a, [seq[1]] + b
a, b = _split(seq)
return a + b
if __name__ == '__main__':
import doctest
doctest.testmod()
I would solve it like this:
def f(l):
if len(l) == 1:
# only one element left
return l
if len(l) % 2 == 0:
# length is even
return l[:1] + f(l[1:])
else:
# length is odd
return f(l[1:]) + l[:1]
Every invocation of the function removes one element from the list and either puts it at the beginning or at the end of the resulting list.
It does not produce the given “expected” outputs, because the elements of the list appear in another order than they appear in the input list, but it matches the specification… and also, I think it’s pretty simple.
For instance, if I have a list
[1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11]
This algorithm should return [1,2,3,4,5,6,7,8,9,10,11].
To clarify, the longest list should run forwards. I was wondering what is an algorithmically efficient way to do this (preferably not O(n^2))?
Also, I'm open to a solution not in python since the algorithm is what matters.
Thank you.
Here is a simple one-pass O(n) solution:
s = [1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11,42]
maxrun = -1
rl = {}
for x in s:
run = rl[x] = rl.get(x-1, 0) + 1
print x-run+1, 'to', x
if run > maxrun:
maxend, maxrun = x, run
print range(maxend-maxrun+1, maxend+1)
The logic may be a little more self-evident if you think in terms of ranges instead of individual variables for the endpoint and run length:
rl = {}
best_range = xrange(0)
for x in s:
run = rl[x] = rl.get(x-1, 0) + 1
r = xrange(x-run+1, x+1)
if len(r) > len(best_range):
best_range = r
print list(best_range)
Not that clever, not O(n), could use a bit of optimization. But it works.
def longest(seq):
result = []
for v in seq:
for l in result:
if v == l[-1] + 1:
l.append(v)
else:
result.append([v])
return max(result, key=len)
You can use The Patience Sort implementation of the Largest Ascending Sub-sequence Algorithm
def LargAscSub(seq):
deck = []
for x in seq:
newDeck = [x]
i = bisect.bisect_left(deck, newDeck)
deck[i].insert(0, x) if i != len(deck) else deck.append(newDeck)
return [p[0] for p in deck]
And here is the Test results
>>> LargAscSub([1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11])
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
>>> LargAscSub([1, 2, 3, 11, 12, 13, 14])
[1, 2, 3, 11, 12, 13, 14]
>>> LargAscSub([11,12,13,14])
[11, 12, 13, 14]
The Order of Complexity is O(nlogn)
There was one note in the wiki link where they claimed that you can achieve O(n.loglogn) by relying on Van Emde Boas tree
How about using a modified Radix Sort? As JanneKarila pointed out the solution is not O(n). It uses Radix sort, which wikipedia says Radix sort's efficiency is O(k·n) for n keys which have k or fewer digits.
This will only work if you know the range of numbers that we're dealing with so that will be the first step.
Look at each element in starting list to find lowest, l and highest, h number. In this case l is 1 and h is 11. Note, if you already know the range for some reason, you can skip this step.
Create a result list the size of our range and set each element to null.
Look at each element in list and add them to the result list at the appropriate place if needed. ie, the element is a 4, add a 4 to the result list at position 4. result[element] = starting_list[element]. You can throw out duplicates if you want, they'll just be overwritten.
Go through the result list to find the longest sequence without any null values. Keep a element_counter to know what element in the result list we're looking at. Keep a curr_start_element set to the beginning element of the current sequence and keep a curr_len of how long the current sequence is. Also keep a longest_start_element and a `longest_len' which will start out as zero and be updated as we move through the list.
Return the result list starting at longest_start_element and taking longest_len
EDIT: Code added. Tested and working
#note this doesn't work with negative numbers
#it's certainly possible to write this to work with negatives
# but the code is a bit hairier
import sys
def findLongestSequence(lst):
#step 1
high = -sys.maxint - 1
for num in lst:
if num > high:
high = num
#step 2
result = [None]*(high+1)
#step 3
for num in lst:
result[num] = num
#step 4
curr_start_element = 0
curr_len = 0
longest_start_element = -1
longest_len = -1
for element_counter in range(len(result)):
if result[element_counter] == None:
if curr_len > longest_len:
longest_start_element = curr_start_element
longest_len = curr_len
curr_len = 0
curr_start_element = -1
elif curr_start_element == -1:
curr_start_element = element_counter
curr_len += 1
#just in case the last element makes the longest
if curr_len > longest_len:
longest_start_element = curr_start_element
longest_len = curr_len
#step 5
return result[longest_start_element:longest_start_element + longest_len-1]
If the result really does have to be a sub-sequence of consecutive ascending integers, rather than merely ascending integers, then there's no need to remember each entire consecutive sub-sequence until you determine which is the longest, you need only remember the starting and ending values of each sub-sequence. So you could do something like this:
def longestConsecutiveSequence(sequence):
# map starting values to largest ending value so far
map = collections.OrderedDict()
for i in sequence:
found = False
for k, v in map.iteritems():
if i == v:
map[k] += 1
found = True
if not found and i not in map:
map[i] = i + 1
return xrange(*max(map.iteritems(), key=lambda i: i[1] - i[0]))
If I run this on the original sample date (i.e. [1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11]) I get:
>>> print list(longestConsecutiveSequence([1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11]))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
If I run it on one of Abhijit's samples [1,2,3,11,12,13,14], I get:
>>> print list(longestConsecutiveSequence([1,2,3,11,12,13,14]))
[11, 12, 13, 14]
Regrettably, this algorithm is O(n*n) in the worst case.
Warning: This is the cheaty way to do it (aka I use python...)
import operator as op
import itertools as it
def longestSequence(data):
longest = []
for k, g in it.groupby(enumerate(set(data)), lambda(i, y):i-y):
thisGroup = map(op.itemgetter(1), g)
if len(thisGroup) > len(longest):
longest = thisGroup
return longest
longestSequence([1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11, 15,15,16,17,25])
You need the Maximum contiguous sum(Optimal Substructure):
def msum2(a):
bounds, s, t, j = (0,0), -float('infinity'), 0, 0
for i in range(len(a)):
t = t + a[i]
if t > s: bounds, s = (j, i+1), t
if t < 0: t, j = 0, i+1
return (s, bounds)
This is an example of dynamic programming and is O(N)
O(n) solution works even if the sequence does not start from the first element.
Warning does not work if len(A) = 0.
A = [1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11]
def pre_process(A):
Last = {}
Arrow = []
Length = []
ArgMax = 0
Max = 0
for i in xrange(len(A)):
Arrow.append(i)
Length.append(0)
if A[i] - 1 in Last:
Aux = Last[A[i] - 1]
Arrow[i] = Aux
Length[i] = Length[Aux] + 1
Last[A[i]] = i
if Length[i] > Max:
ArgMax = i
Max = Length[i]
return (Arrow,ArgMax)
(Arr,Start) = pre_process(A)
Old = Arr[Start]
ToRev = []
while 1:
ToRev.append(A[Start])
if Old == Start:
break
Start = Old
New = Arr[Start]
Old = New
ToRev.reverse()
print ToRev
Pythonizations are welcome!!
Ok, here's yet another attempt in python:
def popper(l):
listHolders = []
pos = 0
while l:
appended = False
item = l.pop()
for holder in listHolders:
if item == holder[-1][0]-1:
appended = True
holder.append((item, pos))
if not appended:
pos += 1
listHolders.append([(item, pos)])
longest = []
for holder in listHolders:
try:
if (holder[0][0] < longest[-1][0]) and (holder[0][1] > longest[-1][1]):
longest.extend(holder)
except:
pass
if len(holder) > len(longest):
longest = holder
longest.reverse()
return [x[0] for x in longest]
Sample inputs and outputs:
>>> demo = list(range(50))
>>> shuffle(demo)
>>> demo
[40, 19, 24, 5, 48, 36, 23, 43, 14, 35, 18, 21, 11, 7, 34, 16, 38, 25, 46, 27, 26, 29, 41, 8, 31, 1, 33, 2, 13, 6, 44, 22, 17,
12, 39, 9, 49, 3, 42, 37, 30, 10, 47, 20, 4, 0, 28, 32, 45, 15]
>>> popper(demo)
[1, 2, 3, 4]
>>> demo = [1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11]
>>> popper(demo)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
>>>
This should do the trick (and is O(n)):
target = 1
result = []
for x in list:
for y in result:
if y[0] == target:
y[0] += 1
result.append(x)
For any starting number, this works:
result = []
for x in mylist:
matched = False
for y in result:
if y[0] == x:
matched = True
y[0] += 1
y.append(x)
if not matched:
result.append([x+1, x])
return max(result, key=len)[1:]