Content
1.二维数组中的查找
问题描述
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
考察点:数组查找
思路1
从右上角开始扫描,情况及方案如下:
- 该数大于要找的数:删除该数所在的列
- 该数小于要找的数:删除该数所在的行
- 该数等于要找的数:直接返回该数
class Solution1:
def Find(self, target, array):
rows = len(array)
cols = len(array[0])
row = 0
col = cols-1
icon = False
while (row<rows)&(col>=0):
if (array[row][col]==target):
icon = True
break
elif array[row][col]>target:
col = col-1
else:
row = row+1
return icon
if __name__ == '__main__':
s = Solution1()
array = [[1,3,6],[2,4,7],[3,5,9]]
print s.Find(7,array)
True
2.旋转数组的最小数字
问题描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
关键:二分查找(折半查找)
要求:线性表必须采用顺序存储结构;元素按关键字有序排列;
1. 将表的中间位置的记录与关键字进行比较,若同,则查找成功;否则将表按关键字分为左右两部分
2. 若小于关键字,则从右半部分继续查找
3. 若大于关键字,则从左半部分继续查找
思路:
- 设置两个指针pl,pr,分别指向数组的首尾。
- 找到数组的中间元素,与两个指针进行比较,若大于等于pl,则将pl指向中间元素;若小于等于pr,则将pr指向中间元素。
- 重复步骤2,直到两个指针最后左右相邻,此时pr即为最小值。
[bug]1.没考虑数组未经过旋转的情况2.当中间元素和首尾元素相同时,无法判断pl,pr的指向。(如:0 1 1 1 1;旋转后:1 0 1 1 1;或 1 1 1 0 1;)
# -*- coding:utf-8 -*-
class Solution:
def minNumberInRotateArray(self, rotateArray):
# write code here
left = 0
right = len(rotateArray)-1
mid = 0
while(rotateArray[left]>=rotateArray[right]):
# 递归终止条件,两个指针最后左右相邻,此时pr即为最小值。
if ((right-left)==1):
mid = right
break
mid = (left+right)/2
#bug2:当数组中的 首=尾=中间元素 时,用search()方法遍历数组,找最小。
if ((rotateArray[left]==rotateArray[right]) and (rotateArray[mid]==rotateArray[left])):
return search(rotateArray)
if(rotateArray[mid]>=rotateArray[left]):
left = mid
else:
right = mid
return rotateArray[mid]
def search(self,rotateArray):
# 遍历数组,查找最小元素
mid = rotateArray[0]
for i in range(1,len(rotateArray)):
if rotateArray[i]<mid:
mid = rotateArray[i]
return mid
s = Solution()
print s.minNumberInRotateArray([3,4,5,1,2])
1
3.数值的整数次方
问题描述
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
考察点:代码完整性
要考虑exponent的取值:负整数、0、正整数,分三种情况
思路
n次方变为n次的积,首先将exponent取绝对值进行计算,然后根据exponent的取值:负整数、0、正整数,分情况输出。
class Solution:
def Power(self, base, exponent):
# write code here
base1 = base
# 首先将exponent取绝对值,计算
for i in range(abs(exponent)-1):
base = base1*base
#根据exponent的取值:负整数、0、正整数,分情况输出
if exponent < 0:
return 1.0 / base
elif exponent == 0:
return 1
else:
return base
if __name__ == '__main__':
s = Solution()
print s.Power(2,-3)
0.125
4.调整数组顺序使奇数位于偶数前面
问题描述
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
考察点:代码完整性
数组全奇数、全偶数、为空、
思路1
- 1st遍历,判断数组是否全奇数、全偶数、为空?返回:2nd遍历;
- 2nd遍历,新建等长数组,奇数从前开始插入,偶数从(数组长-偶数个数)位置开始插入
- 额外空间:等长数组、数组总长、偶数个数、已存储的奇数/偶数个数
class Solution4:
# 1st遍历,判断数组是否全奇数、全偶数、为空?返回:2nd遍历;
# 2nd遍历,新建等长数组,奇数从前开始插入,偶数从(数组长-偶数个数)位置开始插入
# 额外空间:等长数组、数组总长、偶数个数、已存储的奇数/偶数个数
def reOrderArray(self, array):
# write code here
a_len = len(array)
new_array = [0]*a_len
or_numb = 0
for i in array:
if i%2 == 0:
or_numb += 1
#数组全奇数、全偶数、为空,直接返回原数组
if or_numb == a_len or or_numb==0:
return array
else:
oddicon = 0
oricon = 0
for i in array:
if i%2 == 0:
print "oricon",oricon,i
new_array[a_len-or_numb+oricon] = i
oricon += 1
else:
print "oddicon",oddicon,i
new_array[oddicon] = i
oddicon += 1
return new_array
if __name__ == '__main__':
s = Solution4()
print s.reOrderArray([1,4,6,8,9])
oddicon 0 1
oricon 0 4
oricon 1 6
oricon 2 8
oddicon 1 9
[1, 9, 4, 6, 8]
5.把字符串转成整数
问题描述
将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。
输入一个字符串,包括数字字母符号,可以为空。若非合法的数值表达,则返回0。
思路
- 用正则表达式判断字符串中是非法(含字母),字符串比较判断符号(+、-),负数则用整数-1【指数函数10^x,可以用pow(10,x),或*10**x,两个表示指数】
- 直接用int(),将字符串转整数
import re
print re.search(r'[a-zA-Z]','232Z43')
None
# -*- coding:utf-8 -*-
import re
import math
import time
class Solution:
def StrToInt(self, s):
# write code here
numb = len(s)-1
sums = 0
if s=='0'or re.search(r'[a-zA-Z]',s) or s=='':
return 0
elif s[0]=='-':
for i in range(1,numb+1):
sums=sums+int(s[i])*math.pow(10,numb-i)
return (-1)*sums
elif s[0]=='+':
for i in range(1,numb+1):
sums=sums+int(s[i])*math.pow(10,numb-i)
return sums
else:
for i in range(numb+1):
print i,s[i]
sums+=int(s[i])*math.pow(10,numb-i)
return sums
if __name__ == '__main__':
start = time.time()
s = Solution()
print s.StrToInt('-123')
end = time.time()
print 'time:',end-start
-123.0
time: 0.0
# 时间更短
# -*- coding:utf-8 -*-
class Solution:
def StrToInt(self, s):
# write code here
try:
return int(s)
except Exception as e:
return 0
if __name__ == '__main__':
start = time.time()
s = Solution()
print s.StrToInt('-123')
end = time.time()
print 'time:',end-start
-123
time: 0.0
6.顺时针打印矩阵
问题描述
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
思路1(较快)
- 每次打印第一行,打印完后对数组进行逆时针旋转。(魔方的逆时针旋转)
- 循环上述过程
思路2
- 考虑循环的圈数(min(m,n)+1)//2 及边界条件.
- 打印过程分为四部(右下左上)
import time
class Solution:
# matrix类型为二维列表,需要返回列表
def printMatrix(self, matrix):
# write code here
result = []
while(matrix):
result+=matrix.pop(0)
if not matrix or not matrix[0]:
break
matrix = self.turn(matrix)
return result
def turn(self,matrix):
num_r = len(matrix)
num_c = len(matrix[0])
newmat = []
for i in range(num_c):
newmat2 = []
for j in range(num_r):
newmat2.append(matrix[j][i])
newmat.append(newmat2)
newmat.reverse()
return newmat
if __name__=="__main__":
S = Solution()
print S.printMatrix([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]])
print S.printMatrix([[1,2,3,1],[4,5,6,1],[4,5,6,1]])
[1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10]
[1, 2, 3, 1, 1, 1, 6, 5, 4, 4, 5, 6]
# 实际有问题,牛客上测试没问题
# -*- coding:utf-8 -*-
class Solution:
# matrix类型为二维列表,需要返回列表
def printMatrix(self, matrix):
# write code here
res=[]
n=len(matrix) # 行
m=len(matrix[0]) # 列
if n==1 and m==1: # 若矩阵只有一个数
res=[matrix[0][0]]
return res
# f//x 先除法、再下取整,表示不大于结果的整数(注意负数)。-3//2=-2。
for o in xrange((min(m,n)+1)//2): #圈数
# 从左到右
[res.append(matrix[o][i]) for i in xrange(o,m-o-1)]
# 从上到下,并判断是否重复
[res.append(matrix[j][m-1-o]) for j in xrange(o,n-o-1) ]
[res.append(matrix[n-1-o][k]) for k in xrange(m-1-o,o,-1) ]
[res.append(matrix[l][o]) for l in xrange(n-1-o,o-1,-1) ]
return res
if __name__=="__main__":
S = Solution()
print S.printMatrix([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]])
print S.printMatrix([[1,2,3,1],[4,5,6,1],[4,5,6,1]])
[1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 1, 6, 7, 11, 10, 6]
[1, 2, 3, 1, 1, 1, 6, 5, 4, 4, 1, 5, 6, 5]
# 实际有问题,牛客上测试没问题
class Solution:
def printMatrix(self, matrix):
row = len(matrix)
column = len(matrix[0])
res = []
left = 0
top = 0
right = column-1
bottom = row-1
while(left<=right and top<=bottom):
for i in range(left,right+1):
res.append(matrix[top][i])
for i in range(top+1,bottom+1):
res.append(matrix[i][right])
if top != bottom:
for i in range(right-1,left-1,-1):
res.append(matrix[bottom][i])
if left != right:
for i in range(bottom-1,top+1,-1):
res.append(matrix[i][left])
left += 1
top += 1
right -=1
bottom -=1
return res
if __name__=="__main__":
print S.printMatrix([[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]])
print S.printMatrix([[1,2,3,1],[4,5,6,1],[4,5,6,1]])
[1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 1, 6, 7, 11, 10, 6]
[1, 2, 3, 1, 1, 1, 6, 5, 4, 4, 1, 5, 6, 5]
7.数组中出现次数超过一半的数组
问题描述
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
考察点:时间效率
思路1:基于数组特点,不修改原数组,O(n)
出现次数超过数组长度的一半,证明出现次数比其他所有元素出现次数之和还要大。
1. 遍历数组时保存两个值:一是数组中一个数字,一是次数。
2. 遍历下一个数字时,若它与之前保存的数字相同,则次数加1,否则次数减1;若次数为0,则保存下一个数字,并将次数置为1。
3. 遍历结束后,所保存的数字即为所求。然后再判断它是否符合条件即可。
# -*- coding:utf-8 -*-
class Solution:
def MoreThanHalfNum_Solution(self, numbers):
# write code here
if not numbers:
print 0
numb = numbers[0]
count = 1
for i in range(len(numbers)):
if numbers[i] == numb:
count += 1
else:
count -= 1
if count==0:
numb = numbers[i]
count = 1
count = 0
for i in range(len(numbers)):
if numbers[i] == numb:
count += 1
if count*2>len(numbers):
return numb
else:
return 0
s = Solution()
s.MoreThanHalfNum_Solution([1,2,3,2,2,2,5,4,2])
2
8.连续子数组的最大和
问题描述
常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。你会不会被他忽悠住?(子向量的长度至少是1)
考察点:子数组,时间效率
思路1: 动态规划
【核心】找到以第i个数字结尾的子数组的最大和:f(i)
【方法】
已知原数数组为array
1. 如果f(i-1)<=0,或者i=0 :则f(i)=array[i]
2. 如果f(i-1)>0,且i≠0 :则f(i)=f(i-1)+array[i]
# -*- coding:utf-8 -*-
class Solution:
def FindGreatestSumOfSubArray(self, array):
# write code here
if not array:
return 0
cursum = 0
# float('-inf')表示:负无穷大
greatsum = float('-inf')
for i in array:
# 动态规划
if cursum <= 0:
cursum = i
else:
cursum += i
if cursum>greatsum:
greatsum = cursum
return greatsum
s = Solution()
print s.FindGreatestSumOfSubArray([6,-3,-2,7,-15,1,2,2])
8
9.整数中1出现的次数
问题描述
求任意非负整数区间中1出现的次数
考察点:时间效率
思路1: 数的规律
我们从低位到高位求每位1出现的次数,累加求和即可。例如:求0~abcde中1的个数,现在我们求c这一位中1出现的次数,其他位雷同。有两部分组成
1. 第一部分:ab * 100,表示当ab这两位在0~ab-1范围内时,de可以从0~99取值
2. 第二部分:
- 如果c>1时,当ab固定时1的个数为:100个,取值为(ab100,ab199)
- 如果c=1时,当ab固定时1的个数为:(de+1)个
- 如果c=0时,当ab固定时1的个数为:0个
# -*- coding:utf-8 -*-
class Solution:
def NumberOf1Between1AndN_Solution(self, n):
# write code here
res=0
tmp=n
base=1
while tmp:
last=tmp%10
tmp=tmp/10
res+=tmp*base
if last==1:
res+=n%base+1
elif last>1:
res+=base
base*=10
return res
# 思路应该同上
# -*- coding:utf-8 -*-
class Solution:
def NumberOf1Between1AndN_Solution(self, n):
'''
我们从低位到高位求每位1出现的次数,累加求和即可
例如:求0~abcde中1的个数,现在我们求c这一位中1出现的次数,其他位雷同
有两部分组成
第一部分:ab * 100,表示当ab这两位在0~ab-1范围内时,de可以从0~99取值
第二部分:如果c>1时,当ab为ab时1的个数为0~99
如果c=1时,当ab为ab时1的个数为de + 1
如果c<0时,当ab为ab是1的个数为0
'''
# write code here
exp=1
count=0
while n/exp:
#step1
#当前位exp=100时,对于12345保留1200,当前exp是1时,对12345保留1234
#一句话,保留比exp位要大的部分,再除以10。
count+=(n/(10*exp))*exp
#step2
#判断当前位是否大于 等于 小于 1 从而决定第二部分应该加多少
if (n/exp)%10>1:
count+=exp
elif (n/exp)%10==1:
#只保留当前位或者比当前位大的部分
count+=n-(n/exp)*exp+1
exp*=10
return count
10.把数组排成最小的数
问题描述
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
考察点:时间效率,大数间的比较
- 全排列:n个数能拼成n!个数,但为O(nlogn)。
- 注意拼接后可能形成“大数”,用int可能溢出。
- 解决“大数”,直观的可以用“字符串”
- 比较拼接后的mn与nm的大小,可以直接用“字符串的大小比较规则”
思路1:
- 将整数转为字符串
- 定义新的“比较规则”,比较拼接后字符串的大小:若ab>ba,则b应该在a的前面。
- 用qsort()进行排序
# -*- coding:utf-8 -*-
class Solution:
def PrintMinNumber(self, numbers):
# write code here
if not numbers:
return ""
# 将数值转为str
num=map(str,numbers)
# lambda:定义匿名函数,冒号前为参数,冒号后为方法;
# cmp(x+y,y+x)比较拼接后的字符串的大小,x<y:返回-1;x=y:返回0;x>y:返回1.
# sort(cmp):cmp可选,指定后,则按cmp方法排序。排序方法:默认升序。
num.sort(lambda x,y:cmp(x+y,y+x))
return ''.join(num)
s = Solution()
s.PrintMinNumber([12,53,2])
'12253'
11.丑数
问题描述
把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
考察点:
- 丑数:就是乘一些2、3、5。
- 1是最小的丑数
- 关键:记录最新选出的丑数,是由多少个2/3/5相乘得到,需要3个变量来记录。
思路1:
- 从1开始,让其分别乘2、3、5,选出最小的作为下一个丑数。
- 判断所选的下一个丑数,是乘了几(2/3/5),然后相应的次数加1。(关键)
class Solution:
def GetUglyNumber_Solution(self, index):
# write code here
if index<1:
return 0
uglyList = [1]
id2,id3,id5=[0,0,0] #表示当前数乘了几次2、3、5
for i in range(index-1):
temp = min(uglyList[id2]*2,uglyList[id3]*3,uglyList[id5]*5)
uglyList.append(temp)
# 判断当前值是乘几得到的
if (temp % 2 == 0):
id2 += 1
if (temp % 3 == 0):
id3 += 1
if (temp % 5 == 0):
id5 += 1
return uglyList[-1]
s = Solution()
s.GetUglyNumber_Solution(7)
8
12.数组中的逆序对
问题描述
- 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007。
- 输入的数组中没有的相同的数字
- 数据范围:对于%50的数据,size<=10^4;对于%75的数据,size<=10^5;对于%100的数据,size<=2*10^5
考察点:
- 归并排序:先使子序列有序,再使子序列段间有序,并进行合并。
思路1:
- 将数组分割成子数组(只含1个元素),统计子数组内部逆序对的数目。
- 对两个相邻子数组进行归并排序,统计两个相邻子数组之间逆序对的数目。
# -*- coding:utf-8 -*-
class Solution:
def InversePairs(self, data):
# write code here
length = len(data)
if length<=0:
return 0
copy = data # 逆序存储每次比较时的较大的数。(初始化)
count = self.InversePairsCore(data,copy,0,length-1)
return count
def InversePairsCore(self,data,copy,low,high):
if low == high:
return 0
mid = (low+high)>>1 #右移,相当于除2
leftCount = InversePairsCore(data,copy,low,mid)%1000000007
rightCount = InversePairsCore(data,copy,mid,high)%1000000007
count = 0
# i,j设置两个指针,分别指向待比较子数组的末尾
i = mid
j = high
lcopy = high
while(i>=low and j>mid):
if data[i]>data[j]:
# 若前数组中的某值大于后数组中的data[j],则逆序对数增加j-mid个
count += j-mid
# 向copy中存储较大值
lcopy -= 1
i -= 1
copy[lcopy] = data[i]
if (count>=1000000007): # 数值过大,则求余数
count %= 1000000007
else:
lcopy -= 1
j -= 1
copy[lcopy] = data[j]
for i in range(low,-1,-1):
lcopy -= 1
copy[lcopy] = data[i]
for i in range(mid-1,-1,-1):
lcopy -= 1
copy[lcopy] = data[i]
for i in range(low,high+1):
data[i] = copy[i]
return (leftCount+rightCount+count)%1000000007
# s = Solution()
# s.InversePairs([1,2,3,4,0])
13.数字在排序数组中出现的次数
问题描述
统计一个数字在排序数组中出现的次数
思路1
常规想法,遍历数组,找到相应元素,统计。(自己统计/内置算法count())
思路2:有序数组——二分查找
class Solution:
def GetNumberOfK(self, data, k):
# write code here
count = len(data)
i = 0
for j in range(count):
if data[j] == k:
i += 1
return i
class Solution:
def GetNumberOfK(self, data, k):
# write code here
left = 0
right = len(data) - 1
k_sum = 0
while left <= right:
mid = (left + right) / 2
if data[mid] == k:
k_sum += 1
high = mid + 1
low = mid - 1
while high < len(data) and data[high] == k:#get the one not equal.
k_sum += 1
high += 1
while low >= 0 and data[low] == k:
k_sum += 1
low -= 1
return k_sum #once capture one.
elif data[mid] < k:
left = mid + 1
else:
right = mid - 1
return k_sum
14.数组中只出现一次的数字
问题描述
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
考察点:知识迁移
异或:任何一个数字异或它自己都等于0.
思路1:直接用字典存储每个数字出现的次数
思路2:用元组存储,若发现已存在,则删除已有的(牛客上最快)
- 用元组存储数字,若发现已存在,从元组中删除,最后剩下的即只出现一次的
思路3:考虑问题的特殊性,异或(牛客上最慢)
- 对所有数字依次进行异或,得到的结果为:两个只出现一次的数字异或的结果。
- 找到异或结果(二进制)的第一个非0的位置n,将所有数字按照该位置是否为0,分为两组。(相同元素肯定分到了一组,两个只出现一次的数字因为不同,肯定分到了不同的组)
- 对两组元素分别重复操作1,得到的两个数即为结果。
# -*- coding:utf-8 -*-
class Solution:
# 返回[a,b] 其中ab是出现一次的两个数字
def FindNumsAppearOnce(self, array):
# write code here
if not array:
return []
mdict = {}
for i in array:
if mdict.has_key(i):
mdict[i] += 1
else:
mdict[i] = 1
res = []
for i in array:
if mdict[i]==1:
res.append(i)
return res
# -*- coding:utf-8 -*-
class Solution:
# 返回[a,b] 其中ab是出现一次的两个数字
def FindNumsAppearOnce(self, array):
# write code here
tmp = set()
for a in array:
if a in tmp:
tmp.remove(a)
else:
tmp.add(a)
return list(tmp)
class Solution:
# 返回[a,b] 其中ab是出现一次的两个数字
def FindNumsAppearOnce(self, array):
# write code here
if not array:
return []
res = 0
for i in array:
res ^= i
index = 0
while res&1 ==0:
res = res>>1
index += 1
num1,num2 = [0,0]
for i in array:
if (i>>index)&1 ==0:
num1 ^= i
else:
num2 ^= i
return [num1,num2]
s = Solution()
s.FindNumsAppearOnce([2,4,3,6,3,2,5,5])
[4, 6]
15.和为S的连续正数序列
问题描述
- 小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
- 输出:输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
考察点:知识迁移
思路:
- 初始化两个变量,small=1,big=2,求序列(small,big)的和msum,与tsum进行比较
- 若msum==tsum,则存储该序列,并继续增大big寻找下一个序列;若msum
class Solution:
def FindContinuousSequence(self, tsum):
if tsum < 3:
return []
small = 1
big = 2
middle = (tsum + 1)>>1 #相当于(tsum + 1)/2,但是“右移1”的速度比‘/’快.
curSum = small + big
output = []
while small < middle:
if curSum == tsum:
output.append(range(small, big+1))
big += 1
curSum += big
elif curSum > tsum:
curSum -= small
small += 1
else:
big += 1
curSum += big
return output
16.和为s的两个数字
问题描述
- 输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
- 对应每个测试案例,输出两个数,小的先输出。
考察点:知识迁移
- 注意:递增序列
思路1:O(n^2)
- 直接遍历列表,依次判断当前元素与其后的元素之和是否为tsum
思路2:O(n)(JZ上思路,java实现,python有问题)
- 设置两个指针,头指针指向首位元素(最小),尾指针指向尾部元素(最大)
- 计算两个指针的和,若大于S,则尾指针前移;若小于,则头指针后移。
# 顺序遍历列表,每遍历一个元素,判断其后的值相加是否为tsum。O(n)
class Solution:
def FindNumbersWithSum(self, array, tsum):
# write code here
ls = []
# 判断一个对象是否是一个已知的类型
if not isinstance(array, list):
return ls
# 枚举array中的数(顺序遍历)
for i, v in enumerate(array):
for v1 in array[i:]:
if (v + v1) == tsum:
ls.append([v, v1])
if ls:
return ls[0]
else:
return ls
# 设置两个指针,夹逼法 (Java实现)
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
ArrayList<Integer> list = new ArrayList<Integer>();
if (array == null || array.length < 2) {
return list;
}
int i=0,j=array.length-1;
while(i<j){
if(array[i]+array[j]==sum){
list.add(array[i]);
list.add(array[j]);
return list;
}else if(array[i]+array[j]>sum){
j--;
}else{
i++;
}
}
return list;
}
}
17.扑克牌的顺子
问题描述
- 他去买了一副扑克牌,发现里面居然有2个大王,2个小王。(原本只有1大1小,54张)
- 他从中抽出了5张牌,判断是不是一个顺子(是否连续,并且没有“对子”,即相同值),A看作1,J为11,Q为12,K为13,大\小王可以看成任何数字,为了统计方便,给出的数组中0代表大/小王。
- 输出:如果牌能组成顺子就输出true,否则就输出false。
考察点:邮箱建模能力
思路
- 数组排序(快排),统计0的个数zero,及间隔的个数gap
- 若zero>=gap,返回True.
总结
- 统计间隔数,设置两个下标,依次递增,比较差是否为1.
- 快速排序(非递归方法,待实现)
# -*- coding:utf-8 -*-
class Solution:
def qsort(self, myList,start,end):
#判断low是否小于high,如果为false,直接返回
if start < end:
i,j = start,end
#设置基准数
base = myList[i]
while i < j:
#如果列表后边的数,比基准数大或相等,则前移一位直到有比基准数小的数出现
while (i < j) and (myList[j] >= base):
j = j - 1
#如找到,则把第j个元素赋值给第个元素i,此时表中i,j个元素相等
myList[i] = myList[j]
#同样的方式比较前半区
while (i < j) and (myList[i] <= base):
i = i + 1
myList[j] = myList[i]
#做完第一轮比较之后,列表被分成了两个半区,并且i=j,需要将这个数设置回base
myList[i] = base
#递归前后半区
self.qsort(myList, start, i - 1)
self.qsort(myList, j + 1, end)
return myList
def IsContinuous(self, numbers):
# write code here
zero = 0
gap = 0
mlen = len(numbers)
if (not numbers) or (mlen == 0):
return False
numbers = self.qsort(numbers,0,mlen-1)
print numbers
# 统计数组中0的个数
for i in numbers:
if i == 0:
zero += 1
# 统计数组中间隔数
n1 = zero #第一个非0位下标
n2 = zero+1 #第2个非0位下标
while(n2<mlen):
if (numbers[n1]==numbers[n2]):
return False
gap += numbers[n2]-numbers[n1]-1
n1 = n2
n2 += 1
return True if zero>=gap else False
s=Solution()
s.IsContinuous([1,3,2,6,4])
[1, 2, 3, 4, 6]
False
18.孩子们的游戏(圆圈中最后剩下的数)
问题描述:约瑟夫环问题
0,1,…n-1这n个数围成一个圆圈,从数字0开始,每次从圆圈中删除第m个数。求出圆圈最后剩下的数。
考察点:抽象建模能力
思路1:直接用环形链表模拟圆圈O(nm),空间O(n)
- 创建环形链表,每次删除第m个元素
- 重复,知道只剩下一个元素
思路2:找每次删除元素的数字规律,创建递归函数(待分析)。时间O(n),空间O(1)
- 分析得,f(n,m)=[f(n-1,m)+m]%n, n>1; f(n,m)=0, n=1。
class Solution:
def LastRemaining_Solution(self, n, m):
# write code here
if n<1 or m<1:
return -1
last = 0
for i in range(2,n+1):
last = (last+m)%i
return last
s=Solution()
s.LastRemaining_Solution(5,3)
3
19.求1+2+…+n
问题描述
- 求1+2+3+…+n
- 要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
考察点:发散思维
思路:利用逻辑与(&&-java, and-python)的短路功能
- 若and前面为0,则后面不计算。
总结
python中的‘逻辑与’and
- 存在假值(0,[],”),返回第一个假值
- 所有为真,返回最后一个真值
# -*- coding:utf-8 -*-
class Solution:
def Sum_Solution(self, n):
return n and (n+self.Sum_Solution(n-1))
s=Solution()
s.Sum_Solution(3)
6
20.不用加减乘除做加法
问题描述
- 写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号
考察点:发散思维
思路1:借助内置函数sum()
思路2:借助位运算(python运行较慢,没看懂)
以5+7为例:5(101)+7(111)=12(1100)
1. 计算每个位置上的和,不进位,相当于“异或”。101^111=010,即2。
2. 计算进位的值,各位做“与”,再左移1位。101&111=101,101<<1=1010,即10。
3. 若进位为0,则第一步为最终结果,跳出循环;否则,重复上两步,不过相加的值为上两步的结果。
总结:
- 0xFFFFFFFF表示16位数,其中每个F为1111,所以该数转为2进制为32个1。转为10进制,为4294967296
- 大数相加,要防止溢出:num1 if num1 >> 31 == 0 else num1 - 4294967296
class Solution:
def Add(self, num1, num2):
# write code here
return sum([num1,num2])
# 牛客上运行超时!!!
class Solution:
def Add(self, num1, num2):
# write code here
while num2!=0:
temp = num1^num2
num2 = (num1&num2)<<1
num1 = temp
return num1
s=Solution()
s.Add(2,3)
5
# 牛客上运行通过
class Solution:
def Add(self, num1, num2):
# write code here
while num2 != 0:
temp = num1 ^ num2
num2 = (num1 & num2) << 1
# 为后面的移位操作做准备
num1 = temp & 0xFFFFFFFF
print num1>>31
# 防止溢出
return num1 if num1 >> 31 == 0 else num1 - 4294967296
s=Solution()
s.Add(2,3)
0
0
5L
21.数组中重复的数字
问题描述
- 数组长为n,数值范围(0,n-1)。 数组中存在一些重复,不知道有几个,也不知道重复几次。请找出任意一个有重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
- 输出:若有重复数字,选择任意一个赋值给duplication[0],并返回True.
思路1:无需额外数组或hash【时间O(n)】,有bug,牛客没检测到
已知原数组为numbs,长度为length。不过有个bug,当给定的数组中的值,范围不在(0,n-1)内时,比如>=n,会出错。
1. 访问元素x后,设置numbs[x]+=length
2. 访问新元素y时,若发现numbs[y]>=length,则证明y已出现过(即在第1步被访问过)。
思路2:【更快】
- 访问元素x时,判断numbs[x]==x,‘否’则进行元素交换;‘是’则证明重复。
思路3:设置n个标志位存储是否已访问。【最快,占用空间也最少】
- 创建长度为length的数组icon,存储True/False.(第i个值即表示值i是否已存在)
- 每访问一个元素x,判断icon[x]=True;若否,则设置其为True.
# 有bug,还没改对
# -*- coding:utf-8 -*-
class Solution:
# 这里要特别注意~找到任意重复的一个值并赋值到duplication[0]
# 函数返回True/False
def duplicate(self, numbers, duplication):
# write code here
mlen = len(numbers)
for i in range(mlen):
if i >= mlen:
i = float('-inf')
for i in range(mlen):
index = numbers[i]
if index >= mlen:
index -= mlen
# 有bug,没考虑原数组中的值>=mlen的情况
if numbers[index]>=mlen:
duplication[0] = index
print duplication[0]
return True
numbers[index]=numbers[index]+mlen
print numbers
return False
s=Solution()
s.duplicate([2,1,5,1,4],[-1])
2
True
# 更快
# -*- coding:utf-8 -*-
class Solution:
# 这里要特别注意~找到任意重复的一个值并赋值到duplication[0]
# 函数返回True/False
def duplicate(self, numbers, duplication):
# write code here
for i in range(len(numbers)):
if numbers[i] != i:
temp = numbers[numbers[i]]
if temp == numbers[i]:
duplication[0] = numbers[i]
print duplication[0]
return True
else:
numbers[numbers[i]] = numbers[i]
numbers[i] = temp
return False
s=Solution()
s.duplicate([2,1,5,1,4],[-1])
1
True
# -*- coding:utf-8 -*-
class Solution:
# 这里要特别注意~找到任意重复的一个值并赋值到duplication[0]
# 函数返回True/False
def duplicate(self, numbers, duplication):
# write code here
mlen = len(numbers)
# 设置列表,存储是否存在,因为规定输入的数值范围为(0,n-1),所以设置mlen个标志。
icon = [False]*mlen
for i in range(mlen):
# 若数值超过(0,n-1),忽略不计
if numbers[i]>=mlen:
continue
if icon[numbers[i]]==True:
duplication[0]=numbers[i]
print duplication[0]
return True
icon[numbers[i]]=True
return False
s=Solution()
s.duplicate([2,1,5,1,4],[-1])
1
True
22.构建乘积数组
问题描述
- 给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…A[i-1]*A[i+1]…*A[n-1]。不能使用除法。
- 即B[i]为A中所有元素出(除A[i]外)相乘。
思路1(更快)
将B[i]的值看做矩阵M中每行的乘积,这样B可以分两部分计算:上三角+下三角。(其中M:列为B[0],B[1],…B[n-1];行为A[0],A[1],…A[n-1];其中A[i]=B[i]=1)
1. 计算矩阵M的下三角连乘,暂时作为B的值。
2. 计算矩阵M的上三角连乘,并与上步得到的B的值相乘。
思路2
用两个循环,控制连乘时A、B下标不同
- 定义外层循环,控制B的下标
- 定义内层循环,获取A元素(除A[i])的所有连乘积,并赋值给B[i]
# -*- coding:utf-8 -*-
class Solution:
def multiply(self, A):
# write code here
if not A:
return []
mlen = len(A)
B = [None]*mlen
B[0] = 1
# 计算“下三角”的连乘值。
for i in range(1,mlen):
B[i] = B[i-1]*A[i-1]
temp = 1
# 计算“上三角”的连乘值并用中间变量存储,然后与上步得到B的值相乘。
for i in range(mlen-2,-1,-1):
temp *= A[i+1]
B[i] *= temp
return B
# -*- coding:utf-8 -*-
class Solution:
def multiply(self, A):
# write code here
if not A:
return []
B = A[:]
index = 0
mlen = len(A)
for i in range(mlen):
sum = 1
index = i
# 计算A的连乘积,连乘除A[i]外的所有元素
for j in range(mlen):
if j!=index:
sum *= A[j]
B[index] = sum
return B