剑指offer在线编程(08-09)【5】

Date: 2019-08-09

1. 最小的k个数  (考察知识点:数组和高级算法(排序算法)) 延伸:最大的K个数

题目描述

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

分析:目前所有的编程题都需要首先判断是否为空,即是否有进行后续函数体的必要。此处同样需要进行非空或k>len(tinput)的判断!如果成立,直接返回[]。否则直接对tinput进行排序,然后直接取前k个元素!

# -*- coding:utf-8 -*-
class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        # write code here
        if not tinput or k > len(tinput):
            return []
        tinput.sort()  # 默认是升序排序数组,否则降序需要参数:reverse=True
        return tinput[:k]

2.  连续子数组的最大和 (考察知识点:数组)

题目描述

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

分析:连续子数组和不仅仅是编程题中容易出现,常见的选择题或者填空题也比较容易出现! 对于要求连续子数组之和的一篇完整的算法改进可看: python求连续子数组的最大和 (非常完整!,建议看)

其主要有以下是三种方法:

1) 暴力解决方法: 直接进行两个遍历i,j,然后判断所有情况的sum(list[i:j]),然后和初始max进行比较得到最终的max_val;而且在此过程中还可以得到相应的子序列的初始位置和终止位置。o(n^2)

2) 分治法:将子序列的情况分为三种:分别可能来自于全部来自左边+全部来自右边+跨越来自中间序列。o((n/2)^2)

3) 线性遍历法:只遍历一次,累积和已经小于零时,清零累积和并重新计算;当累积和大于零时,继续累加;然后再与max-val进行比较,更新最大子序列和! o(n)

# -*- coding:utf-8 -*-
class Solution:
    def FindGreatestSumOfSubArray(self, array):
        # write code here
        if not array:
            return False
        max_sum = array[0]  # 先将第一个元素赋为初始最大值
        pre_sum = 0   # 遍历过程中不断更新。为负时,将清零从i开始重计;为正时,累加当前的值
        for i in array:
            if pre_sum < 0:
                pre_sum = i   # 为负时,将清零从i开始重计(***)
            else:
                pre_sum += i   # 为正时,累加当前的值(***)
            if pre_sum > max_sum:  # 如果当前的pre_sum大于max_sum,进行更新替换。
                max_sum = pre_sum
        return max_sum

3.  整数中1出现的次数(从1到n整数中1出现的次数)   (考察知识点: 查找+数学)

题目描述

求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

分析:这题要看自己怎么想:一种是找规律(较为复杂,但是复杂度要低一些o(logn)),另一种方法是遍历(o(n)).

第一种方法的版本参考:https://www.cnblogs.com/qiaojushuang/p/7780445.html

解法: (建议后续多看这种方法,理解到位!)

# -*- coding:utf-8 -*-
class Solution:
    def NumberOf1Between1AndN_Solution(self, n):
        # write code here
        count=0
        i=1
        while i<=n:
            a=n/i
            b=n%i
            count+=(a+8)/10*i+(a%10==1)*(b+1)
            i*=10
        return count

第二种方法:遍历n个数字,在遍历过程中累加每个数字中'1'的个数(当前数字需要从int——str,即可实现每个字母作为一个元素)

# -*- coding:utf-8 -*-
class Solution:
    def NumberOf1Between1AndN_Solution(self, n):
        # write code here
        if not n:
            return 0
        count = 0
        for i in  range(n+1):
            count += str(i).count('1')
        return count

4.  把数组排成最小的数 (考察知识点:数组)

题目描述

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

分析:  刚开始自己想的比较复杂:先将每个数字转成字符串,然后每次取首位最小(如果首位相等,则比较第二首位)放入结果中。但是后来想想首位、次首位的小元素比较,实质上就是这两个字符串进行拼接后,转成数字的比较大小。所以最后直接采用一个排序函数sort(),只是这个排序函数的内置函数不是普通的升序或者降序,而是两个数字的字符串进行前后拼接的大小。

# -*- coding:utf-8 -*-
class Solution:
    def PrintMinNumber(self, numbers):
        # write code here
        if not numbers:
            return ""  # 如果初始给定的元素为空列表,则返回的是空字符串,而非空列表或者False
        num= map(str, numbers)  # 将列表中的每个数字转成字符串的形式。
        num.sort(lambda x,y: cmp(x+y, y+x))  # 此时任意比较两个字符串的两种拼接方式的大小,按照此排序。
        return ''.join(num)

5.  丑数   (考察知识点:穷举)

题目描述

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

分析:丑数一定是2/3/5的倍数,而且其分解的因子又不能为其他数,所以直接在这三个因子上进行操作即可!

第一种思路,不管重复,每次增添i*2,i*3 ,i*5的元素到set中(i会累加,set中第i个元素,但是set没有索引的方法,所以借用一个等价的list进行处理),注意后续当set的长度达到index的要求时,break出。然后将set转成list,再排序,求第index个元素。

# -*- coding:utf-8 -*-
class Solution:
    def GetUglyNumber_Solution(self, index):
        choushu=list()
        choushuset=set()
        choushu.append(1)
        choushuset.add(1)
        i=0
        while True:
            choushu.append(choushu[i]*2)
            choushu.append(choushu[i]*3)
            choushu.append(choushu[i]*5)
            
            choushuset.add(choushu[i]*2)
            choushuset.add(choushu[i]*3)
            choushuset.add(choushu[i]*5)
            
            i=i+1
            if(len(choushuset)>=index):
                break
        b=list(choushuset).sort()
        return b[index-1]

第二种思路,避免进行过多的操作,每次只往set里增添一个元素,并且增添的是当前计算中最小的元素!

# 通过的代码
class Solution:
    def GetUglyNumber_Solution(self, index):
        if (index <= 0):
            return 0
        uglyList = [1]
        indexTwo = 0
        indexThree = 0
        indexFive = 0
        for i in range(index-1):
            newUgly = min(uglyList[indexTwo]*2, uglyList[indexThree]*3, uglyList[indexFive]*5)
            uglyList.append(newUgly)
            if (newUgly % 2 == 0):
                indexTwo += 1
            if (newUgly % 3 == 0):
                indexThree += 1
            if (newUgly % 5 == 0):
                indexFive += 1
        return uglyList[-1]

# My code  但是没有通过
class Solution:
    def GetUglyNumber_Solution(self, index):
        if (index <= 0):
            return 0
        uglyList = [1]
        while len(uglyList)!=index:
            res = []
            for i in range(len(uglyList)):
                res += [uglyList[i]*2, uglyList[i]*3, uglyList[i]*5]
            uglyList.append(min(res)) 
        return uglyList[-1]

牛客上的一种解释:

首先从丑数的定义我们知道,一个丑数的因子只有2,3,5,那么丑数p = 2 ^ x * 3 ^ y * 5 ^ z,换句话说一个丑数一定由另一个丑数乘以2或者乘以3或者乘以5得到,那么我们从1开始乘以2,3,5,就得到2,3,5三个丑数,在从这三个丑数出发乘以2,3,5就得到4,6,10,6,9,15,10,15,25九个丑数,我们发现这种方法会得到重复的丑数,而且我们题目要求第N个丑数,这样的方法得到的丑数也是无序的。那么我们可以维护三个队列:

(1)丑数数组: 1

乘以2的队列:2

乘以3的队列:3

乘以5的队列:5

选择三个队列头最小的数2加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;

(2)丑数数组:1,2

乘以2的队列:4

乘以3的队列:3,6

乘以5的队列:5,10

选择三个队列头最小的数3加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;

(3)丑数数组:1,2,3

乘以2的队列:4,6

乘以3的队列:6,9

乘以5的队列:5,10,15

选择三个队列头里最小的数4加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;

(4)丑数数组:1,2,3,4

乘以2的队列:6,8

乘以3的队列:6,9,12

乘以5的队列:5,10,15,20

选择三个队列头里最小的数5加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;

(5)丑数数组:1,2,3,4,5

乘以2的队列:6,8,10,

乘以3的队列:6,9,12,15

乘以5的队列:10,15,20,25

选择三个队列头里最小的数6加入丑数数组,但我们发现,有两个队列头都为6,所以我们弹出两个队列头,同时将12,18,30放入三个队列;

……………………

6.  第一个只出现一次的字符   (考察知识点:字符串)

题目描述

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

分析:  该题还是利用collections中的Counter库进行词频统计并返回相应的字典:在python3.x版本中,其返回的结果首先是按照value进行的降序排列,当几个字符的value相等,按照相应key出现的先后顺序进行排列。但是在系统检测(python2.x)时不通过,所以就将所有value==1的字符的index进行了保存,最后返回最小的那一个便好。

# -*- coding:utf-8 -*-
import collections
class Solution:
    def FirstNotRepeatingChar(self, s):
        # write code here
        if not s:
            return -1
        res = []
        c = collections.Counter(s)  # 利用Counter进行词频统计,返回字典
        for k,v in c.items():
            if v == 1:
                res.append(s.index(k))   # 版本差异问题,所以保存所有符合条件的字符index
        return min(res)              # 选取最小的哪一个index返回

# 第二种方法,不使用collections, 直接使用count进行词频的计算,遍历遇到的第一次词频等于1,则就是第一次出现的。
# -*- coding:utf-8 -*-
class Solution:
    def FirstNotRepeatingChar(self, s):
        # write code here
        if not s:
            return -1
        for i in s:
            if s.count(i) == 1:
                return s.index(i) 
                break

7.  数组中的逆序对  (考察知识:  数组)

题目描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数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

 

思路分析:

看到这个题目,我们的第一反应是顺序扫描整个数组。每扫描到一个数组的时候,逐个比较该数字和它后面的数字的大小。如果后面的数字比它小,则这两个数字就组成了一个逆序对。假设数组中含有n个数字。由于每个数字都要和O(n)这个数字比较,因此这个算法的时间复杂度为O(n^2)。

我们以数组{7,5,6,4}为例来分析统计逆序对的过程。每次扫描到一个数字的时候,我们不拿ta和后面的每一个数字作比较,否则时间复杂度就是O(n^2),因此我们可以考虑先比较两个相邻的数字。

(a) 把长度为4的数组分解成两个长度为2的子数组;

(b) 把长度为2的数组分解成两个成都为1的子数组;

(c) 把长度为1的子数组 合并、排序并统计逆序对

(d) 把长度为2的子数组合并、排序,并统计逆序对;

在上图(a)和(b)中,我们先把数组分解成两个长度为2的子数组,再把这两个子数组分别拆成两个长度为1的子数组。接下来一边合并相邻的子数组,一边统计逆序对的数目。在第一对长度为1的子数组{7}、{5}中7大于5,因此(7,5)组成一个逆序对。同样在第二对长度为1的子数组{6}、{4}中也有逆序对(6,4)。由于我们已经统计了这两对子数组内部的逆序对,因此需要把这两对子数组 排序 如上图(c)所示, 以免在以后的统计过程中再重复统计。

接下来我们统计两个长度为2的子数组子数组之间的逆序对。合并子数组并统计逆序对的过程如下图如下图所示。

我们先用两个指针分别指向两个子数组的末尾,并每次比较两个指针指向的数字。如果第一个子数组中的数字大于第二个数组中的数字,则构成逆序对,并且逆序对的数目等于第二个子数组中剩余数字的个数,如下图(a)和(c)所示。如果第一个数组的数字小于或等于第二个数组中的数字,则不构成逆序对,如图b所示。每一次比较的时候,我们都把较大的数字从后面往前复制到一个辅助数组中,确保 辅助数组(记为copy) 中的数字是递增排序的。在把较大的数字复制到辅助数组之后,把对应的指针向前移动一位,接下来进行下一轮比较。

过程:先把数组分割成子数组,先统计出子数组内部的逆序对的数目,然后再统计出两个相邻子数组之间的逆序对的数目。在统计逆序对的过程中,还需要对数组进行排序。如果对排序算法很熟悉,我们不难发现这个过程实际上就是归并排序。

实际上用python做的好多都是利用的map直接获取结果的。

我也是没懂真正的算法怎么写(遗留问题)

下面附上他们用map通过的代码:

# -*- coding:utf-8 -*-
class Solution:
    def InversePairs(self, data):
        # write code here
        return 24903408 if data[0]==26819 else 493330277 if data[0]==627126 else 988418660 if data[0]==74073 else 2519

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值