Python List Rotate: 方法汇总

博客围绕Python数组元素向右循环移位问题展开,要求空间复杂度为O(1)。介绍了三种解法:一是直接循环移位,可对移动次数取模;二是通过三次反转实现移位,时间复杂度低;三是利用Python列表负索引特性重构数组,是否满足空间复杂度要求待探讨。

问题描述

给定一个python的list和一个正整数k,考虑将数组内的元素向右shiftk个位置(感觉用shift表示比较丝滑一些,其实类似于循环移位操作)。
e.g.

list_to_be_shifted=[1,2,3,4,5,6,7]
bitnum = 3
shift(list)
>>> list
[5, 6, 7, 1, 2, 3, 4]

问题本身的解决方案很多,但是题目要求使用空间复杂度为O(1)来实现,也就是利用原数组就地正法,这样一来对题目就多了不少限制。

开干

解法一

最直接的办法当然就是直接开干一波带走,你让我移k个位置,那我就一个个来呗,看谁先把谁送走了(这玩笑开不得,毕竟它只是一堆破 铜 烂 铁,Markdown不需要引号)。所以干法如下:

def shift(self, nums: list, k: int) -> None:
    """
    Do not return anything, modify nums in-place instead.
    """
    while k > 0:
        temp = nums[-1]
        for i in range(len(nums) - 1):
            nums[len(nums) - 1 - i] = nums[len(nums) - 2 - i]
        nums[0] = temp
        k = k - 1

值得注意的一点是,当数组长度len(nums)<k时,我们并不需要老老实实移动k次,将k直接对len(nums)取模即可,因此上面可以加上一句

k = k % len(nums)

鲁迅先生说过(好吧其实不是他说的):只要是循环结构我们都可以轻易地把它变成递归。但是在这里使用递归的话其实增加了堆栈开销,并不满足要求的空间复杂度,这里只是给一个思路:

def shift_recursion(self, nums: list, k: int) -> None:
    """
    Do not return anything, modify nums in-place instead.
    """
    k = k % len(nums)
    
    if k > 0:
        temp = nums[-1]
        for i in range(len(nums) - 1):
            nums[len(nums) - 1 - i] = nums[len(nums) - 2 - i]
        nums[0] = temp
        self.rotate_recursion(nums, k - 1)

这一做法本质上与循环结构没有大的区别,不再多说。

解法二,Reverse真香!

其实抛开一位位移动的想法,我们很容易就可以想到,为什么不一下子把后面待移动的k位直接放到前面去?这样子不是既省时又省力吗?好家伙,就这么干。稍加思索我们不难发现,所谓的这种移位操作,可以直接通过三次反转来实现!各位看官们,动一动小脑筋,问题就是这么简单(其实俺也没想到可以这么干,Leetcoder牛逼就完事了,自己画了一张丑图,大家将就着看看)。
在这里插入图片描述
可以看到,只需要简单的三次Reverse即可实现题目所要求的功能,可以说是大道至简,具体的python实现如下:

def reverse(self, array: list, begin: int, end: int) -> None:
    while begin < end:
        temp = array[end]
        array[end] = array[begin]
        array[begin] = temp
        begin += 1
        end -= 1


def shift_reverse(self, nums: list, k: int) -> None:
    if k == 0:
        return
    if k > len(nums):
        k = k % len(nums)
    
    nums.reverse()
    self.reverse(nums, 0, k - 1)
    self.reverse(nums, k, len(nums) - 1)

以上代码可以完美实现所要求的功能,且时间复杂度大大降低(使用上面逐个移位的方法对于大型的输入数组是会timeout的…大家注意了)。

解法三,充分利用Python特性

对于Python的list对象,负索引是一个创造性的发明(反正俺在以前学过的语言里面没见过,暂且认为它是具有创造性的叭,有问题提请大家指正!),这样一来,本身我就可以把数组的一个位置当做数组的头部,对数组进行重排列,这正是我们这一问题的本源!其实所谓的旋转,不过是换了数组的起始位置,旋转两位,其实就是从 -2位开始当做数组头,一直到-3位到达数组尾(当然这里的 -3也就是 +(len(nums) - k)),这是我们重构数组时索引结束的位置,那么使用列表生成式直接构建旋转后的数组的两种方法如下:

# method1
# Actually for python, we can use list generator and negative index
def rotate_generator(self, nums: list, k: int) -> None:
    nums[:] = nums[-k: len(nums)] + nums[0: -k]
# method2
def rotate_generator_new(self, nums: list, k: int) -> None:
    nums[:] = [nums[i] for i in range(- (k % len(nums)), len(nums) - (k % len(nums)))]

关于上述两种方法是否满足空间复杂度为 **O(1)**的要求,还请大家自行理解(就我个人的实验结果来看,方法一在旋转前后,nums指针的值未发生变化,至少说没有开辟新的堆内存空间,但是我觉得它不是空间复杂度为O(1)的实现方法,如有错误还请指正)。

self.ico.ClearLayer() # self.ico.DispWork(sig_layer) # self.ico.DispLayer(sm_layer) # self.ico.DispLayer(dat_layer) self.__renderPDF(sig_layer) #在找到的那一层操作,正面或背面 self.ico.DelLayer(self.tmpLays) mes = f'输出目录:{self.filePath},继续将打开PDF' ans = messageBox.showMessage( bitmap='information', title='PDF输出完成', message=mes, buttons=['退出', '继续']) if ans == '继续': os.system(f"/usr/bin/evince {self.filePath} &") self.incam.COM('disp_on') # TODO 转换成png 放到output里面 self.__pdf2PNG() return 0 def __pdf2PNG(self): cmd = f"convert -density 120 -quality 80 -background white -alpha remove {self.filePath} {self.imgPath}" os.system(cmd) def __renderPDF(self, sig_lay): """ 渲染PDF:设置文档结构并构建内容 """ canv = ReportCanvas(self.filePath, pagesize=self.pageSize) self.canvas = canv self.drawingParams = self.__setTemplateParams() # 设置模板的默认参数 self.bgTemp = DrawingTemplate( canv, A4[0], A4[1], self.drawingParams) lM = 0 rM = 0 tM = 0 bM = 0 self.doc = SimpleDocTemplate(self.filePath, pagesize=self.pageSize, topMargin=tM, bottomMargin=bM, leftMargin=lM, rightMargin=rM, title="MI-13", author=self.userCN)#filePath:最终存放路径; pageSize:画布大小 self.__setPageFrame(self.doc) story = [] story.append(NextPageTemplate('p1')) g2c = self.__createGerber(sig_lay) story.append(FrameBreak()) story.append(g2c) story.append(PageBreak()) self.doc.build(story) # def __setPageFrame(self, doc: SimpleDocTemplate): """设置每一页框架分布 """ frames = [] fh = doc.height / 3 padX = self.drawingParams['padx'] padY = self.drawingParams['pady'] tableFrame = Frame(x1=padX, y1=padY + fh * 2, width=doc.width, height=fh, id='f1') gerberFrame = Frame( x1=padX, y1=padY, width=doc.width, height=fh * 2, id='f2') frames.append(tableFrame) frames.append(gerberFrame) doc.addPageTemplates([PageTemplate(id='p1', frames=frames)]) def getMergeLay(self, lay): """ 将lay备份并将备份层合并为surface """ mergeLay = f'{lay}_merge' self.ico.DelLayer(mergeLay) self.ico.ClearAll() self.ico.DispWork(lay, number=1) self.incam.COM( f'sel_copy_other,dest=layer_name,target_layer={mergeLay},invert=no,dx=0,dy=0,size=0,x_anchor=0,y_anchor=0') self.ico.DispWork(mergeLay, number=1) self.incam.COM( 'sel_cont_resize,accuracy=25.4,break_to_islands=yes,island_size=0,hole_size=0,drill_filter=no,corner_ctl=no') return mergeLay def getDnxSigLayMapping(self, lay: str): """ 获取dnx孔层与其钻带的起始信号层之间的映射关系(1:1) :param dnxLayers:线路层 :return:起始终止是线路层的所有钻孔 """ dnxSigLayMapping = [] for drl in self.ico.GetLayerMatrix()['drlAllLay']: startLay = self.ico.GetLayerMatrix()['drlThrough'][drl]['start'] endLay = self.ico.GetLayerMatrix()['drlThrough'][drl]['end'] if startLay == lay or endLay == lay: dnxSigLayMapping.append(drl) return dnxSigLayMapping # 分析创建光绘的关键部分 def __createGerber(self, sigLay): layerMatrix = self.ico.GetLayerMatrix() sig_out_list = layerMatrix['sigOutLay'] sm_lay_list = layerMatrix['smAllLay'] toRead = [] # 1. 合并信号层为 surface mergeSigLay = self.getMergeLay(sigLay) self.tmpLays.append(mergeSigLay) mergeSigLayFilePath = self.ico.getFeatureFile(self.JOB, self.workStep, mergeSigLay) toRead.append(Feature(mergeSigLayFilePath, layerType='signal')) # 3. 添加阻焊层 idx = sig_out_list.index(sigLay) # 获取对应的阻焊层 sm_layer = sm_lay_list[idx] maskFilePath = self.ico.getFeatureFile(self.JOB, self.workStep, sm_layer) toRead.append(Feature(maskFilePath, layerType='solder_mask')) # 2. 添加钻孔层 drlSet = self.getDnxSigLayMapping(sigLay) for drl in drlSet: drlFilePath = self.ico.getFeatureFile(self.JOB, self.workStep, drl) toRead.append(Feature(drlFilePath, layerType='document')) # === 创建高亮圆圈层 === highlight_layer = "pofv_highlight_circle" if self.ico.IsLayerExist([highlight_layer]): self.ico.DelLayer([highlight_layer]) self.ico.CreateOrEmptyLay(layer_list=[highlight_layer]) # 获取要高亮的孔坐标(只标第一个) if sigLay not in self.sigDimension or not self.sigDimension[sigLay]['point_x']: # 没有坐标,跳过画圈 pass else: x = float(self.sigDimension[sigLay]['point_x'][0]) y = float(self.sigDimension[sigLay]['point_y'][0]) self.ico.ClearLayer() self.ico.DispWork(highlight_layer) self.incam.COM(f"add_pad,symbol=r180,polarity=positive,x={x},y={y},mirror=no,angle=0,direction=ccw,resize=0,xscale=1,yscale=1") self.incam.COM("sel_feat2outline,width=3.0,location=on_edge,offset=0.1,polarity=as_feature,keep_original=no,text2limit=no")# 轮廓线 self.incam.COM("arc2lines,arc_line_tol=1") self.ico.DelLayer(highlight_layer + '+++') self.ico.ClearLayer() # sys.exit(0) # 高亮层也导出为 Gerber Feature self.tmpLays.append(highlight_layer) # 确保后续清理" hlight_path = self.ico.getFeatureFile(self.JOB, self.workStep, highlight_layer) toRead.append(Feature(hlight_path, layerType='signal', strokeColor = "#fcfcf6")) # === 计算尺寸和偏移 === unitSizeX, unitSizeY = self.ico.GetStepSize(self.workStep)[0:2] gbWidth = self.doc.width * 0.6 gbHeigth = self.doc.height * 2 / 3 * 0.6 offsetX = self.getOffsetXY(gbWidth, gbHeigth, unitSizeX, unitSizeY, pagesize=( self.doc.width, self.doc.height * 2 / 3))[0] # 创建绘图对象 g2c = Gerber2Canvas( gbWidth, gbHeigth, unitSizeX, unitSizeY, offsetX, 0.1, Origin.leftdown, self.canvas, toRead, rotate=0 ) # === 添加标注箭头=== dimension = [] if sigLay in self.sigDimension and self.sigDimension[sigLay]['point_x']: pointX = self.sigDimension[sigLay]['point_x'][0] pointY = self.sigDimension[sigLay]['point_y'][0] dimensionSingle = Dimension( x0=unitSizeX / 2, y0=unitSizeY * 1.2, x1=float(pointX), y1=float(pointY), direct=Direct.one_arrow, dist='POFV孔铜厚度测量位置', dimColor="#0400FF", # dimLineColor="#0800FF" # ) dimension.append(dimensionSingle) if dimension: g2c.addDimension(dimension) return g2c @staticmethod def getOffsetXY(gbWidth: float, gbHeight: float, unitSizeX: float, unitSizeY: float, pagesize: tuple = (A4[0], A4[1])): """ 获取使Gerber在PDF中居中显示的偏移量 :param gbWidth: pdf中 gerber宽度 :param gbHeight: pdf中 gerber长度 :param unitSizeX: unit宽 :param unitSizeY: unit长 :param pagesize: PDF宽和长 :return: x,y的偏移量 # 创建绘图对象 g2c = Gerber2Canvas(gbWidth,gbHeight,unitSizeX,unitSizeY,offsetX, offsetY, Origin.leftdown,self.canvas,toRead, rotate=0) """ scale1 = math.ceil(gbWidth / unitSizeX) scale2 = math.ceil(gbHeight / unitSizeY) scale = scale1 if scale1 < scale2 else scale2 offsetX = (pagesize[0] - unitSizeX * scale) / (2 * scale) offsetY = (pagesize[1] - unitSizeY * scale) / (2 * scale) return offsetX, offsetY 还有个问题,# === 正确添加阻焊层 === sm_layer = sm_lay_list[idx] maskFilePath = self.ico.getFeatureFile(self.JOB, self.workStep, sm_layer) toRead.append(Feature(maskFilePath, layerType='solder_mask')) # 在绘制阻焊层的时候,在整体顺时针转了90度
最新发布
12-02
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值