阅读 520

算法数组篇-完整版

数组中超过一半的数字

题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

示例

输入:[1,2,3,2,2,2,5,4,2]

返回值:2

解题思路

思路一:遍历数组,用字典dict存储数组中某个数字出现的次数,当这个数字出现的次数大于数组长度的一半时,返回这个数字。
def MoreThanHalfNum(numbers):
	length = len(numbers)
	if length == 0: return 0
	if length == 1: return numbers[0]
	dict = {}
	for i in range(length):
		if numbers[i] in dict:
			dict[numbers[i]] += 1
			if dict[numbers[i]] > length/2:
				return numbers[i]
		else:
			dict[numbers[i]] = 1
	return 0
复制代码
  • 时间复杂度:O(n) ,因为只循环了一次数组
  • 空间复杂度:O(1)~O(n),因为开辟了字典dict,最优为1最差为n
思路二:对数组进行排序,取中间值。
def MoreThanHalfNum(numbers):
	length = len(numbers)
	if length == 0: return 0
	if length == 1: return 1
	numbers.sort()
	num = numbers[int(length/2)]
	if (numbers.count(num) > length/2): 
		return num
	return 0
复制代码
  • 时间复杂度:O(nlogn)。python的sort()函数的时间复杂度最差是O(nlogn)<O(n),所以这种方法的时间复杂度是O(nlogn)
  • 空间复杂度:O(1)
思路三:如果有符合条件的数字,则它出现的次数比其他所有数字的次数和还要多。那么我们可以在遍历数组时保存两个值:一个是数组中的一个数字,一个是次数。遍历下一个数字时,若它与之前保存的数字相同,则次数加1,否则次数减1;若次数为0,则保存下一个数字,并将次数置为1。相当与两两抵消。
def MoreThanHalfNum(numbers):
	length = len(numbers)
	if length == 0: return 0
	if length == 1: return 1
	num = numbers[0]
	count = 1
	for i in range(1, length):
		if count == 0:
			num = numbers[i]
			count = 1
		elif numbers[i] == num:
			count += 1
		else:
			count -= 1
	if numbers.count(num) > length/2:
		return num
	return 0
复制代码
  • 时间复杂度:O(n) ,因为对数组进行了遍历

  • 空间复杂度:O(1)

进制转换

题目描述

给定一个十进制数M,以及需要转换的进制数N。将十进制数M转化为N进制数。

示例

输入:7,2

返回值:"111"

解题思路

首先是十进制转换为其他进制本质来说就是使用该进制取余数放在末位,然后依次取余数插入前一位。我们可以直接依次取余数然后添加到数组中,最后将数组反转即可。需要注意的点:一是小于等于0的情况;二是超过十进制(比如十六进制)会出现非数字的情况,可以先存在一个数组里,然后根据对应数字取对应字母。
def solve(M, N):
	if M == 0: return 0
	if M < 0: return '-'+ solve(abs(M,N))
	a = [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F']
	l = []
	while True:
		M, reminder = divmod(M, N)
		l = l + [reminder]
		if M == 0: 
			break
	l.reverse()
	result = ''
	for i in l:
		result += str(a[i])
	return result
复制代码
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
  • divmod()函数把除数和余数运算结果结合起来,返回一个包含商和余数的元组。输入(7,2)输出(3,1)

合并两个有序数组

题目描述

给出两个有序的整数数组A和B ,请将数组B合并到数组A 中,变成一个有序的数组 注意: 可以假设A 数组有足够的空间存放B 数组的元素,A和B 中初始的元素数目分别为m和n

示例

输入:[1,4,7,9] [2,3,6]

返回值:[1,2,3,4,6,7,9]

解题思路

因为题目说A有足够的空间可以存放B数组,因此不用单独开辟新的空间。使用双指针起始点分别为A和B的末尾,依次对比大小,如果A数组当前指的数字大于B,则将此数存入新A数组(m+n长度)的末尾,并且将A数组的指针往前移一位。另一情况同理。如果A数组遍历完毕,B数组没有,直接将B数组剩余数字按当前排序放置于新A数组。因为直接放置数字在A数组中,如果B遍历完毕,则合并自然完成
def merge(A, m, B, n):
	if m == 0 and n == 0: return []
	while m > 0 and n > 0:
		if A[m-1] > B[n-1]:
			A[m+n-1] = A[m-1]
			m -= 1
		else:
			A[m+n-1] = B[n-1]
			n -= 1
	if n > 0: A[:n] = B[:n]
复制代码
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

两数之和

题目描述

给出一个整数数组,请在数组中找出两个加起来等于目标值的数, 你给出的函数twoSum 需要返回这两个数字的下标(index1,index2),需要满足 index1 小于index2.。注意:下标是从1开始的 假设给出的数组中只存在唯一解

示例

输入:[3,2,4],6

返回值:[2,3]

解题思路

定义一个字典,存放循环当前数值num与target的差值a和num对应的index。如果当前循环数字num2存在字典中,证明之前循环的某个值num1与target的差值a存在与字典中(a=num2),既num1+num2=target,且num1的index一定小于num2的index。返回a对应的index(即num1对应的index)及num2对应的index即可。注意:index是从1开始的,所以需要返回时需加1
def twoSum(numbers, target):
	d = {}
	for n in range(len(numbers)):
		a = target - numbers[n]
		if numbers[n] in d:
			return d[numbers[n]]+1,n+1
		else:
			d[a] = n
复制代码
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

注:如果题目说明数组是有序数组,则可以使用左右下标指向数组的首尾,如果相加小于目标值,则left+1,如果相加大于目标值,则right-1

斐波那契数列

题目描述

大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。 n<=39

示例

输入:4

返回值:3

解题思路

使用递归,当前数等于前两个数之和
def Fibonacci(n):
	res = [0,1,1,2]
	while len(res) <= n:
		res.append(res[-1]+res[-2])
	return res[n]
复制代码
  • 时间复杂度:O(2^n)
  • 空间复杂度:递归栈的空间

有序数组的二分查找

题目描述

请实现有重复数字的有序数组的二分查找。 输出在数组中第一个大于等于查找值的位置,如果数组中不存在这样的数,则输出数组长度加一。

示例

输入:5,4,[1,2,4,4,5](依次为数组长度,查找值,有序数组)

返回值:3(输出位置从1开始计算)

解题思路

首先用两个指针标明左、右,然后求出中间指针位置mid。然后用mid所在位置与关键字进行比较,如果大于等于查找值,则进一步比较mid-1位置的值是否小于查找值或者mid等于0,如是则返回mid+1;如不是则右指针指向mid(即在数组的左半侧继续查找)。如果小于查找值,则左指针指向mid(即在数组的右半侧继续查找)。如遍历完毕后仍未找到,则返回n+1。
def upper_bound_(n , v , a ):
	left = 0
	right = n - 1
	while left < right:
		middle = int(left + (right-left)/2)
		if a[middle] >= v:
			if middle == 0 or a[middle-1] < v:
				return middle+1
			else:
				right = middle
		else:
			left = middle+1
	return n + 1
复制代码
  • 时间复杂度:O(logn)
  • 空间复杂度:O(1)

子数组的最大累加和问题

题目描述

给定一个数组arr,返回子数组的最大累加和 例如,arr = [1, -2, 3, 5, -2, 6, -1],所有子数组中,[3, 5, -2, 6]可以累加出最大的和12,所以返回12.

[要求] 时间复杂度为O(n)O(n),空间复杂度为O(1)O(1)。

示例

输入:[1, -2, 3, 5, -2, 6, -1]

返回值:12

解题思路

因为要求空间复杂度为O(1),所以直接在array进行操作。动态规划的思想,一边遍历一边计算最大序和,nums[i-1]并不是数组前一项的意思,而是到前一项为止的最大子序和,和0比较是因为只要大于0,就可以相加构造最大子序和。如果小于0则相加为0,nums[i]=nums[i],相当于最大子序和又重新计算。
def maxsumofSubarray(array):
	for i in range(1, len(array)):
		array[i] = array[i] + max(array[i-1], 0)
	return max(array)
复制代码
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

只出现一次的数字

题目描述

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

[要求] 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例

输入:[2,2,1]

返回值:1

解题思路

使用异或。交换律:a ^ b ^ c <=> a ^ c ^ b;任何数于0异或为任何数 0 ^ n => n;相同的数异或为0: n ^ n => 0。所以:2 ^ 3 ^ 2 ^ 4 ^ 4等价于 2 ^ 2 ^ 4 ^ 4 ^ 3 => 0 ^ 0 ^3 => 3

def singleNumber(nums):
    a = 0 
    for num in nums:
        a = a ^ num
    return a
复制代码
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

杨辉三角

题目描述

给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。

示例

输入:3

返回值:[1,3,3,1]

解题思路

先直接生成所需空间(用1填充),再循环计算更新生成

def getRow(int):
    res = []
    for i in range(rowIndex+1):
        now = [1] * (i+1)
        if i >= 2:
            for n in range(1, i):
                now[n] = pre[n-1] + pre[n]
        res += [now]
        pre = now
    return now
复制代码
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(n^2)

加一

题目描述

给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。你可以假设除了整数 0 之外,这个整数不会以零开头。

示例

输入:[1,2,3]

返回值:[1,2,4]

解题思路

从末尾往前循环,逢九进位,返回【1】+当前数组。否则直接当前位+1后return。

def plusOne(digits):
	n = len(digits)
	for i in range(n-1, -1, -1):
		if digits[i] == 9:
			digits[i] = 0
		else:
			digits[i] += 1
			return digits
	return [1]+digits
复制代码
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

移除元素

题目描述

给你一个数组 nums和一个值 val,你需要 原地 移除所有数值等于val的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例

给定 nums = [3,2,2,3], val = 3,

函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。

解题思路

因为需要原地修改数组,所以可以使用python内置函数pop(key),key是要删除的键值。另,需注意实时删除元素会改变数组的长度,所以从末尾开始往前循环

def removeElement(nums,val):
	n = len(nums)
	for i in range(n-1, -1, -1):
		if nums[i] == val:
			nums.pop(i)
	return len(nums)
复制代码
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

罗马数字转整数

题目描述

罗马数字包含以下七种字符:I:1,V:5,X:10,L:50,C:100,D:500和M:1000。

例如, 罗马数字 2 写做'II',即为两个并列的 1。12 写做'XII',即为'X'+'II'。 27 写做'XXVI', 即为'XX'+'V'+'II'。

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做'IIII',而是'IV'。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为'IX。这个特殊的规则只适用于以下六种情况:

I可以放在V(5) 和X(10) 的左边,来表示 4 和 9; X可以放在L(50) 和C(100) 的左边,来表示 40 和90; C可以放在D(500) 和M(1000) 的左边,来表示400 和900;

给定一个罗马数字,将其转换成整数。输入确保在 1到 3999 的范围内。

示例

输入:"LVIII"

输出:58(解释: L = 50, V= 5, III = 3)

解题思路

首先建立一个hashMap来映射符号和数字的对应关系。然后从左往右循环,如果当前值不小于右侧,就加上该值,否则就减去该值。

def romanToInt(s):
	num = 0
	a = {'I':1, 'V':5, 'X':10, 'L':50, 'C':100, 'D':500, 'M':1000}
	for i in range(len(s)):
		if i < len(s)-1 and a[s[i]] < a[s[i+1]]:
			num -= a[s[i]]
		else:
			num += a[s[i]]
	return num
复制代码
  • 时间复杂度:O(n)

  • 空间复杂度:O(1)

文章分类
代码人生
文章标签