程序员的算法趣题Q37: 翻转7段码

Posted 笨牛慢耕

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了程序员的算法趣题Q37: 翻转7段码相关的知识,希望对你有一定的参考价值。

目录

1. 问题描述

2. 解题分析

3. 代码及测试


1. 问题描述

        问题:求把10个数字全部显示出来时,亮灯/灭灯的切换次数最少的显示顺序,以及这个切换次数。

        注:原题从答案来看是不包括第一个数字显示时从全黑到显示该数字所需要的亮灭切换次数的。不过这个不影响解题,但是可能会影响答案。

2. 解题分析

        从暴力搜索(原书中使用“全量搜索”这个词很贴切)着手。

        10个数字的不同排列顺序共有10!=factorial(10)=3628800种,针对每一种排列顺序进行切换次数的统计即可。

        本题解中用numpy array存储各数字显示所需要的灯亮灭表示矩阵,每行表示一个数字,表示使用7段码对应的二进制表示。

        切换次数的计算可以理解为两个二进制序列之间的汉明距离(hamming distance),即两个二进制序列之间的不同的数的个数。汉明距离可以用异或操作实现。

        此外,各数字的二进制表示之间的汉明距离总共只有10*10=100个(连自己与自己之间的距离也算上,虽然不用。考虑到查找实现的便利,(k,j)(j,k)也分开来算,虽然它们是相等的)可以预先计算好,这样可以避免在遍历搜索时的大量的重复计算。

3. 代码及测试

# -*- coding: utf-8 -*-
"""
Created on Wed Sep 22 07:37:19 2021

@author: chenxy
"""
import sys
import time
import datetime
import math
import random
from   typing import List
# from   queue import Queue
from   collections import deque
import itertools as it
from   math import sqrt, floor, ceil
import numpy as np


class Solution:
    segs = np.array([[1,1,1,1,1,1,0],
            [0,1,1,0,0,0,0],
            [1,1,0,1,1,0,1],
            [1,1,1,1,0,0,1],
            [0,1,1,0,0,1,1],
            [1,0,1,1,0,1,1],
            [1,0,1,1,1,1,1],
            [1,1,1,0,0,0,0],
            [1,1,1,1,1,1,1],
            [1,1,1,1,0,1,1]])
    distArray  = dict()
    minDistSum = np.sum(segs)
    minOrder   = None
    
    def initDistArray(self):
        for i in range(len(self.segs)):
            for j in range(len(self.segs)):
                self.distArray[tuple([i,j])] = np.sum(self.segs[i] ^ self.segs[j])

    def printDistArray(self):
        print(self.distArray)
        
    def minToggle(self):
        for order in it.permutations(list(range(10))):
            # print(order)
            distSum = 0 #np.sum(self.segs[0])
            for k in range(9):
                distSum += self.distArray[(order[k],order[k+1])]
            if self.minDistSum > distSum:
                self.minDistSum = distSum
                self.minOrder = order
        return self.minDistSum, self.minOrder
                        
if __name__ == '__main__':        
    
    sln = Solution()
    sln.initDistArray()
    # sln.printDistArray()        
    
    t1 = time.perf_counter()
    minDistSum, minOrder = sln.minToggle()
    tCost = time.perf_counter() - t1      
    print('Number of toggles = {0}, order = {1}, tCost = {2}'.format(minDistSum, minOrder,tCost))  
    
                        
        

运行结果:

Number of toggles = 13, order = (0, 8, 6, 5, 9, 4, 1, 7, 3, 2), tCost =  8.640sec

4. 后记

        运行时间有点长,需要考虑进一步优化。

        这个问题其实可以看作是,具有10个节点的全连接无向图,每条边的权重值代表两个节点所代表的数字的7段码显示的二进制表示之间的汉明距离。因此本题就转化为就该图中任意一条最长无重复路径的权重总和。这其实就是著名(恶名昭彰)的旅行商问题。这是一个NP问题,但是只有10个节点还可以对付。接下来考虑用递归+Memoization的方法来试一试会不会变得更快一些。

        上一篇:Q36: 翻转骰子

        下一篇:Q38: 填充白色

        本系列总目录参见:程序员的算法趣题:详细分析和Python全解

以上是关于程序员的算法趣题Q37: 翻转7段码的主要内容,如果未能解决你的问题,请参考以下文章

程序员的算法趣题Q35: 0和7的回文数

片段(Java) | 机试题+算法思路+考点+代码解析 2023

程序员的算法趣题Q54: 偷懒的算盘

程序员的算法趣题Q14: 国名接龙

程序员的算法趣题Q39: 反复排序

程序员的算法趣题Q56: 鬼脚图中的横线(思路2的Python题解)