最近看到一个算法题:
给定一个 array,array中数字不重复,求出里面最长的连续的一段,它由连续的数组成,顺序任意
一看到题目就知道需要使用分治法,把数组分成左右两段,分别递归,但最长的有可能出现在中间,对中间的处理稍微复杂一点,但题目假设数组中的数字不重复,所以还是比较容易处理的。求出左中右三段的最长后,取他们的最大长度即可。
我分别用python和lisp解决该问题,其实是先写python,然后翻译成lisp。第一次使用lisp编程,不动手不知道,一动手发现有很多棘手的事情,经常编译不过,lisp的错误提示对初学者来说又比较晦涩,碰了不少钉子。吃一堑长一智,这就是动手的好处吧!
common lisp的开发体验和python很像,common lisp的交互式开发环境很棒,python学习和借鉴了 lisp很多的东西。
废话少说了,还是让代码说话吧,在我看来,这段lisp的代码和python的代码同样优雅,这是我始料未及的。性能没有比较,留给感兴趣的朋友吧!
lisp的实现:
(defparameter *mid_max_seq* nil)
(defun get_max_seq (li)
"给定一个 array,array中数字不重复,求出里面最长的连续的一段,它由连续的数组成,顺序任意"
(progn
(when (or (= 0 (length li)) (= 1 (length li)))
(return-from get_max_seq li))
(let ((half (round (/ (length li) 2))))
(format t "get_max_seq>>>>>>>>>li:~a, half:~d~%" li half)
(get_max_mid_seq li (1- half) (1+ half))
(setf left (get_max_seq (subseq li 0 half)))
(setf right (get_max_seq (subseq li half)))
(nth 0 (sort (list *mid_max_seq* left right) (lambda (x y) (> (length x) (length y))))))))
(defun is_seq (li)
"判断li是否由连续数字组成,假设li无重复数字, 如果最大数-最小数=li长度-1,则为连续"
(if (eql li nil)
nil
(= (- (apply #'max li) (apply #'min li)) (- (length li) 1))))
(defun get_extend_limit (li left right)
"将li从left和right向两侧扩展,直到将left和right之间不连续的数字都找到为止,并返回新的left和right"
(let* ((min_value (apply #'min (subseq li left right)))
(max_value (apply #'max (subseq li left right)))
(miss_numbers (set-difference (range min_value max_value) (subseq li left right)))
(new_left left)
(new_right right))
(format t "min:~d, max:~d, miss:~a~%" min_value max_value miss_numbers)
(when (eql miss_numbers nil)
(error "miss_numbers should not be nil"))
(when (> left right)
(error "left should not larger than right"))
(loop for value in (subseq li 0 left) do
(when (member value miss_numbers)
(setf new_left (position value li))
(return)))
(loop for value in (subseq li right) do
(when (member value miss_numbers)
(setf new_right (1+ (position value li)))))
(when (> new_left new_right)
(error "get_extend_limit return error: new_left>new_right"))
(list new_left new_right)
))
(defun range (low up)
(loop for i from low to up collect i))
(defun get_max_mid_seq (li left right )
"从中间向两端搜索,可以优化"
(progn
(format t "get_max_mid_seq>>>>>>>>li:~a, left:~d, right:~d~%" li left right)
(if (is_seq (subseq li left right))
(progn
(when (> (length (subseq li left right)) (length *mid_max_seq*))
(setf *mid_max_seq* (subseq li left right))
(format t "update *mid_max_seq* to:~a~%" *mid_max_seq*))
(when (> left 0)
(get_max_mid_seq li (1- left) right ))
(when (< right (length li))
(get_max_mid_seq li left (1+ right) ))
)
(progn
(let ((new_left_right (get_extend_limit li left right)))
(unless (and (= (first new_left_right) left) (= (second new_left_right) right))
(get_max_mid_seq li (first new_left_right) (second new_left_right) )))))))
(defun test ()
(progn
(test_get_max_mid_seq '(4 3 2 1 5 6) 2 4 '(4 3 2 1 5 6))
(test_get_max_mid_seq '(4 3 2 1 9 8 7 6 5) 3 5 '(4 3 2 1 9 8 7 6 5))
(test_get_max_mid_seq '(4 6 2 5 9 8 3 7 1) 3 5 '(4 6 2 5 9 8 3 7 1))
(test_get_max_seq '(4 3 2 1 5 6) '(4 3 2 1 5 6))
(test_get_max_seq '(4 3 2 1 7 6) '(4 3 2 1 ))
(test_get_max_seq '(7 6 2 1 3 4) '(2 1 3 4 ))
(test_get_max_seq '(7 4 2 1 3 6) '(4 2 1 3 ))
(test_get_max_seq '(4 3 2 1 9 8 7 6 5) '(4 3 2 1 9 8 7 6 5))
(test_get_max_seq '(4 6 2 5 9 8 3 7 1) '(4 6 2 5 9 8 3 7 1))
))
(defun test_get_max_seq(li expect)
(progn
(setf *mid_max_seq* nil)
(assert (equal (get_max_seq li) expect))))
(defun test_get_max_mid_seq(li left right expect)
(progn
(setf *mid_max_seq* nil)
(get_max_mid_seq li left right)
(assert (equal *mid_max_seq* expect))))
python的实现:
# -*- coding: utf-8 -*-
import bisect
import random
mid_max_seq = [] #用于保存递归的中间结果
#题目:给定一个 array,array中数字不重复,求出里面最长的连续的一段,它由连续的数组成,顺序任意。
#解决思路:采用分治法,从中间将数组分成两段,分别计算左边,右边,以及从中间开始的连续数字,取三者中最长。左边和右边采用递归(get_max_seq),中间采用向左右扩张计算(get_max_mid_seq)。
def test():
'''测试'''
li = [
{'q':[], 'a':[]},
{'q':[1], 'a':[1]},
{'q':[1, 0], 'a':[0, 1]},
{'q':[2, 1, 0], 'a':[0, 1, 2]},
{'q':[7, 4, 3, 1, 2, 0, 9, 8, 10, 6 ], 'a':[0, 1, 2, 3, 4]},
{'q':[7, 4, 3, 1, 5, 0, 9, 8, 10, 6 ], 'a':[8, 9, 10]},
{'q':[9, 6, 3, 7, 0, 2, 1, 5, 8, 4], 'a':range(10)},
]
for i in range(100):
li.append(genTestData())
for qa in li:
print '---------solve problem: ', qa
global mid_max_seq
mid_max_seq = []
result = get_max_seq(qa['q'])
result.sort()
assertEqual(result, qa['a'])
def genTestData():
'''生成测试数据'''
arr = range(10)
random.shuffle(arr)
return {'q':arr, 'a':range(10)}
def assertEqual(q, a):
'''断言q和a相等'''
if not q and not a:
return
#print 'assertEqual:q=', q, 'a=', a
if len(q) != len(a):
raise BaseException('q:%s shoule be equal than a:%s'%(q, a))
for i in range(len(q)):
if q[i] != a[i]:
raise BaseException('q:%s shoule be equal than a:%s'%(q, a))
def get_max_seq(li):
'''给定一个 array,array中数字不重复,求出里面最长的连续的一段,它由连续的数组成,顺序任意。
>>> get_max_seq([5, 1, 3, 2])
[1, 3, 2]
>>> get_max_seq([5, 3, 1, 4, 2])
[5, 3, 1, 4, 2]
'''
if len(li) == 0 or len(li)==1:
return li;
if len(set(li)) != len(li): #li是否有重复数字
raise BaseException('should not have duplicate numbers')
half = len(li)/2
#print 'half:', half
get_max_mid_seq (li, half-1, half+1)
left_max_seq = get_max_seq( li[:half] )
right_max_seq = get_max_seq( li[half:] )
all = [left_max_seq, right_max_seq, mid_max_seq]
max_len = max(map(lambda x:len(x), all))
res = filter(lambda x:len(x)==max_len, all)
if len(res) > 0:
return res[0]
else:
return []
def get_max_mid_seq(li, left, right):
'''以left和right为中心向两边搜索最长连续数字'''
if left < 0 or right > len(li):
raise BaseException('error: left=%s, right=%s '%(left, right))
#print 'get_max_mid_seq, li:', li, 'left:', left, 'right:', right
if isSeq(li[left:right]):
global mid_max_seq
if len(li[left:right]) > len(mid_max_seq):
mid_max_seq = li[left:right]
#print 'save match:', mid_max_seq
if left > 0:
get_max_mid_seq(li, left-1, right)
if right < len(li):
get_max_mid_seq(li, left, right+1)
else:
new_left, new_right = get_extend_limit(li, left, right)
if new_left == left and new_right == right:
return
else:
get_max_mid_seq(li, new_left, new_right)
def isSeq(li):
'''判断li是否由连续数字组成
>>> isSeq([2, 1, 3])
True
>>> isSeq([1, 2, 4])
False
'''
if len(set(li)) != len(li):
raise BaseException('li should not duplicated')
return max(li) - min(li) == len(li)-1
def get_extend_limit(li, left, right):
'''从li的left和right位置向左右扩展,知道将left和right之间
>>> get_extend_limit([3, 5, 2, 6, 4, 1], 2, 4)
(0, 5)
>>> get_extend_limit([2, 5, 3, 6, 4, 1], 1, 4)
(1, 5)
'''
#print 'get_extent_limit: li', li, 'left:', left, 'right:', right
min_value = min(li[left:right])
max_value = max(li[left:right])
miss_numbers = filter( lambda x : x not in li[left:right], range( min_value, max_value ) )
#print 'get_extend_limit: miss_number:%s'%(miss_numbers)
if not miss_numbers:
raise BaseException('miss_number should not be empty')
if left>right:
raise BaseException('get_extend_limit input error: left:%s, right:%s'%(left,right))
new_left , new_right = left, right
for value in li[:left]:
if value in miss_numbers:
new_left = li.index(value)
break
for value in li[right:]:
if value in miss_numbers:
new_right = li.index(value)+1
if new_left>new_right:
raise BaseException('get_extend_limit return error: new_left:%s, new_right:%s'%(new_left,new_right))
#print 'get_extent_limit, new_left:', new_left, 'new_right:', new_right
return new_left, new_right
if __name__ == "__main__":
import doctest
doctest.testmod()
test()