《剑指offer》python实现

继续进行剑指offer的python实现



把数组排成最小的数

题目描述

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

思路

使用sorted函数对数组进行排序,排序的方法为如果数字a与数字b组成的数字小于数字b与数字a组成的数字,a就排在b前面,反之,b排在a前面

通过这样的排序,数组从头到尾组成的数字就是最小的了

对于两个数字组合如何比较大小,可以先将其转化为字符串,比较字符串的大小关系即可

from functools import cmp_to_key

class Solution:
    def PrintMinNumber(self, numbers):
        if not numbers:
            return ""
        ls=list(map(str,numbers))
        #由于排序中需要使用+的操作将字符串进行连接,因此在此使用map函数,将numbers数字数组转换为字符串数组,然后使用list形成一个列表
        res=sorted(ls,key=cmp_to_key(self.compare))
        #对于python3的版本,sorted的自定义排序方法需要自己写,同时,需要使用functools库中的cmp_to_key函数将比较方法转化为关键key
        s=""
        for i in res:
           s=s+i
        return s

    #比较方法为比较两个字符串连接形成的两个字符串的大小,按照大小进行排序
    def compare(self,a,b):
        if a+b<b+a:
            return -1
        else:
            return 1

在python3中,由于sorted方法取消了原来的定义排序函数的方法,在这里需要使用key函数,因此要调用functools中的cmp_to_key函数将比较函数转化为key函数

另外由于要使用字符串进行比较操作,因此在最开始,需要将数字转化为字符串,使用list(map(str,numbers))函数转化,其中map函数的第一个参数是要对列表进行的操作函数,第二个参数是列表,list函数则将map函数构造的对象转化为字符串列表

丑数

题目描述

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

思路

第一次使用暴力求解的方法,很明显不能通过测试,还是查询了网上各路的大神的方法才自己照猫画虎地写出来……

对于新的丑数一定是由前面的丑数2,或3,或*5形成的

因此首先往序列中增加第一个丑数1

之后设置三个指针,p2,p3,p5均指向第一个丑数。之后比较

ls[p2]*2,ls[p3]*3,ls[p5]*5

这三个数哪个小,将小的数字添加到序列末尾,然后增加其指针的位置

class Solution:
    def GetUglyNumber_Solution(self, index):
        if index<7:
            return index
        ls=[1]
        p2,p3,p5=0,0,0
        for i in range(index-1):
        	#找下一个丑数
            nextuglyn=min(ls[p2]*2,min(ls[p3]*3,ls[p5]*5))
            ls.append(nextuglyn)
            if(nextuglyn==ls[p2]*2):
                p2+=1
            if(nextuglyn==ls[p3]*3):
                p3+=1
            if(nextuglyn==ls[p5]*5):
                p5+=1
        return ls[index-1]

第一个只出现一次的字符

题目描述

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).

思路

本题使用字母的ASCII码引索建立统计数组,计算每个元素出现的次数,之后再遍历一次找到第一个出现一次的字符即可

class Solution:
    def FirstNotRepeatingChar(self, s):
        cnt=[]
        for i in range(123):
        #在这里使用range(123)原因是"z"的ASCII码为122,因此要储存所有字母的出现次数必须构建长度为123的数组
            cnt.append(0)
        for ss in s:
            cnt[ord(ss)]+=1
        for i in range(len(s)):
            if cnt[ord(s[i])]==1:
                return i
        return -1

在本方法中,调用ord函数计算一个字符的ASCII码的数值

另外,参考网上大佬们的代码,可以使用python的filter函数

class Solution:
    def FirstNotRepeatingChar(self, s):
        if not s:
            return -1
        cal=lambda c:s.count(c)==1
        ls=list(filter(cal,s))
        return s.index(ls[0])

filter函数的第一个参数为过滤方法,列表s满足该函数条件的元素会留下,再通过使用list函数将过滤后的元素构建成一个列表,最后使用index函数计算第一个只出现一次的字母的引索

对于filter函数的第一个参数传入的为一个函数,该函数使用lambda方法进行构建,传入一个参数

数组中的逆序对

题目描述:
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

输入描述:
题目保证输入的数组中没有的相同的数字
数据范围:

对于%50的数据,size<=10^4

对于%75的数据,size<=10^5

对于%100的数据,size<=2*10^5

示例1:

输入
1,2,3,4,5,6,7,0
输出
7

思路

本题最容易想到的就是使用时间复杂度为O(n2)的冒泡排序方法,只要交换过一次就为逆序对的个数增加1,好像使用C/C++可以通过,对于java python等语言,这个算法明显就不可取了

因此需要使用归并递归的算法:

  1. 分:将数组分为两部分,left与right,递归计算left与right两部分的逆序对的个数,计算的同时需要将left与right排序为两个有序数组

  2. 治:计算将left与right合并后产生的逆序对

对于治,定义两个位置指针,接下来举例说明

假如left有序序列为1 2 3,right有序序列0 4 5

  1. 首先将pr指向右边序列的末尾元素5,i指向左边的有序序列的末尾元素3
  2. 比较data[i]与data[pr]的大小,如果左边比右边小,正常,此时将pr左移,直至pr指向元素的元素小于i指向的元素,这时就会有pr-mid个逆序对,然后i左移

计算完合并后的逆序对,需要将合并并排好序列的数组传递出去,就需要使用temp数组

具体代码如下:

class Solution:
    def InversePairs(self, data):
        return self.cal(0,len(data)-1,data)%1000000007
    
    def cal(self,left,right,data):
        #递归终止条件
        if right-left==0:
            return 0
        if right-left==1:
            if data[right]<data[left]:
                data[left],data[right]=data[right],data[left]
                return 1
            else:
                return 0
        mid=(left+right)//2
        res=self.cal(left,mid,data)+self.cal(mid+1,right,data)
        res%=1000000007

        #计算左右两个有序序列产生的逆序对个数
        #需要定义一个指针指向右边有序序列的最后一个元素
        pr=right
        for i in range(mid,left-1,-1): #对左边的有序序列,比较其与右边pr所指的元素的大小关系
            while(data[i]<data[pr] and pr!=mid):#如果右边比左边大,右边的指针左移动,直至找到右边第一个比左边该数小的数字
                pr-=1
            res+=pr-mid #这样对于左边这个数字就会产生pr-mid个逆序
            res%=1000000007

        #将两个数组归并后传入原来的data中
        pl=left
        pr=mid+1
        temp=[]
        #第一步使用三个while循环,将两个有序数列归并成一个有序序列
        while pl!=mid+1 and pr!=right+1:
            if data[pl]<data[pr]:
                temp.append(data[pl])
                pl+=1
            else:
                temp.append(data[pr])
                pr+=1
        while pl!=mid+1:
            temp.append(data[pl])
            pl+=1
        while pr!=right+1:
            temp.append(data[pr])
            pr+=1
        #第二步使用一个for循环将原始的data从left到right的数字替换为上面的有序序列
        p=0
        for i in range(left,right+1):
            data[i]=temp[p]
            p+=1
        return res%1000000007

!!!注意:由于python语言的特性,即时使用分而治之的方法将复杂度降低为O(nlogn),还是经常有25%的测试用例无法通过,对此只能反复提交,多试几次会有一定概率通过,可能是牛客网的OJ系统的问题吧【python的运算速度跟占用内存是真的大】
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值