一个算法题的python和lisp实现和比较。

1 篇文章 0 订阅
1 篇文章 0 订阅

最近看到一个算法题:

给定一个 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()


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值