剑指offer记录

第二章:面试需要的基础知识

3. 找到数组中重复的数字

方法一: 先排序,然后对排序后的数组从前到后进行查找。

class solution:
	def duplicate(self, numbers, duplication):
		# 对非法输入的处理
		if numbers == None or len(numbers)<= 1:
			return False
		for i in numbers:
			if i < 0 or i >= len(numbers):
				return False
		# 对数组进行排序
		sort_num = numbers.sort()
		for i in range(len(numbers)-1):
			if numbers[i] == numbers[i+1]:
				duplication[0] = numbers[i]
				return True
		return False

方法二:(剑指方法) 若没有出现重复数字,则数组按顺序排列后下标为i的位置存放的数字也是i。因此,将数组从前到后扫描一遍,对每个位置的数字m判断其是否等于下标值i,若不等于,则判断数字m和下标位置为m的数字大小情况:相等则找到重复数字;不等则交换。
空间复杂度O(1), 虽然存在两层循环,但是每一个数字最多经过两次交换就能找到正确的位置,因此时间复杂度是O(n)

class solution:
	def duplicate(self, numbers, duplication):
		# 非法输入的判断
		if numbers == None or len(numbers)<=1:
			return False
		for i in numbers:
			if i < 0 or i > len(numbers) -1 :
				return False
		
		for i in range(len(numbers)):
			while numbers[i] != i:
				if numbers[i] == numbers[numbers[i]]:
					 duplication[0] = numbers[i]
					 return True
				# 交换二者的位置
				else:
					numbers[i], numbers[numbers[i]] = numbers[numbers[i]], numbers[i]
		return False

拓展题目: 不修改数组找到重复的数字:长度n+1的数组
方法一: 利用辅助空间,如字典等

class solution:
	def duplicate(self, numbers):
		# 对于非法输入的判断:
		if numbers == None or len(numbers) <= 1:
			return False
		for i in numbers:
			if i < 1 or i > len(numbers)-1:
				return False
		temp_dict = {}
		for i in range(len(numbers)):
			if numbers[i] not in temp_dict:
				temp_dict[numbers[i]] = i
			else:
				return temp_dict[numbers[i]]
		return False

方法二:为了避免创造新的辅助空间,则使用一种类似于二分查找的方法。但是该方法不能保证找到所有重复的数字。

class solution:
	def duplicate(self, numbers):
		# 非法输入的判断
		if numbers == None or len(numbers) <= 1:
			return False
		for i in numbers:
			if i < 1 or numbers > len(numbers) -1 :
				return False
		start = 1
		end  = len(numbers)-1 
		while start <= end:
			mid = (start + end) // 2
			count = self. countRange(numbers, start, mid, len(numbers))
			if start == end:
				if count>1:
					return start
				else:
					return False
			if count > mid - start+1:
				end = mid
			else:
				start = mid + 1			
		return False
	# 计算指定区间内元素个数			
	def countRange(self, nums, start, end, lens):
		if nums is None:
			return -1
		count = 0
		for i in range(lens):
			if numbers[i] >= start and numbers[i] <= end:
				count +=1 
		return count

4. 二维数组中的查找

方法一:暴力法 二层遍历,时间不是最优

class solution:
	def Find(self, array, target):
	# 非法输入的判断
	if not array:
		return False
	m, n = len(array), len(array[0])
	for i in range(m):
		for j in range(n):
			if array[i][j] == target:
				return True
	return False

方法二:数组中右上角的数字帮助筛选掉一列或一行。若当前数字大于数组中右上角数字则当前行被剔除,若小于则当前列被剔除。直到要找的数字就是当前剩余数组的左上角数字。

class solution:
	def Find(self, array, target):
		if not array:
			return False
		m, n = len(array), len(array[0])
		row = 0
		col = n -1
		while row<m and col >= 0:
			if target == array[row][col]:
				return True
			elif target > array[row][col]:
				col -= 1
			elif target < array[row][col]:
				row += 1
		return False		

5. 替换空格

方法一: python特性

class solution:
	def ReplaceSpace(self, str):
		return str.replace(" ", "%20")

方法二: python方法

class solution:
	def ReplaceSpace(self, str):
		return '%20'.join(str.split(' '))

方法三:(剑指方法) 为了避免字符的多次移动。先统计原始字符串中的空格数量,确定新的字符串长度 = 原始长度 + 空格数量*2。于是在原始字符串之后先空出增加的这些空间。接着为了避免自负的重复复制,采用从后向前遍历字符串的方式,引入两个指针,分别指向新的字符串的末尾以及原始字符串的末尾。

class solution:
	def ReplaceSpace_stand(str):
	# 先计算原始空格数量
	count = 0
	s = list(str)
	for i in s:
		if i == ' ':
			count+= 1
	# 原始字符串末尾
	p1 = len(s) - 1
	s += [None] * (2*count)
	# 新字符串的末尾
	p2 = len(s) - 1
	while p1 >= 0:
		if s[p1] != ' ':
			s[p2] = s[p1]
			p2 -= 1
			p1 -= 1
		else:
			for i in range('0', '2', '%'):
				s[p2] = i
				p2 -=1 
			p1 -= 1
	return ''.join(s)	

注意:以后在合并两个数组(字符串)的时候同样也可以使用从后向前的方式,这样可以有效的减少移动的次数,提高效率。

6. 从头到尾打印链表

方法一: 从后向前遍历链表,但是从前向后输出。先将值进行存储,之后逆序输出。

class ListNode:
	def __init__(self, val):
		self.val = val
		self.next = None
class solution:
	def printListFromTailToHead(self, listNode):
		# 对非法输入的判断
		if not ListNode:
			return []
		res = []
		while ListNode.next is not None:
			res.append(ListNode.val)
			ListNode = ListNode.next
		return res[::-1]

7. 重建二叉树

方法一: 使用递归的方式进行构建。

class TreeNode:
	def __init__(self, val):
		self.val = val
		self.right = None
		self.left = None
class solution:
	def reConstructBinaryTree(self, pre, tin):
		# pre是前序遍历,tin是中序遍历
		# 对非法输入的判断
		if not pre or not tin:
			return None
		root = TreeNode(pre[0])
		index = tin.index(pre[0])

		root.left = self.reConstructBinaryTree(pre[1:index+1], tin[0:index])
		root.right = self.reConstructBinaryTree(pre[index+1:], tin[index+1:])
		return root

8. 二叉树的下一个节点

方法一:
分情况讨论:

  • 当前节点存在右子树: 从右子节点出发,一直沿着左子节点指针,找到其右子树的最左孩子。
  • 当前节点没有右孩子,且是其父节点的左孩子:当前节点的根节点
  • 当前节点没有右孩子,且是其父节点的右孩子:沿着父节点一路向上判断。
class TreeNode:
	def __init__(self, val):
		self.val = val
		self.right = None
		self.left = None
		self.pre = None
class solution:
	def GetNext(self, pNode):
		# 对非法输入的判断
		if not pNode:
			return
		if pNode.right != None:
			pNode = pNode.right
			while pNode.left:
				pNode = pNode.left
			return pNode
		elif pNode.right == None and pNode.pre.left == pNode:
			return pNode.pre
		else:
			while pNode.pre != None and pNode.pre.left != pNode:
				pNode = pNode.pre
			return pNode.pre		

9. 用两个栈实现队列

方法一: 两个栈stacka和stackb分别用作进栈 和出栈。进栈时,直接将元素压入stacka中即可;出栈时,先判断在stackb中是否存在元素:若是空,则将stacka全部压入到stackb中,pop出第一个元素;若是不为空,则直接pop第一个元素即可。

class solution:
	def __init__(self):
		self.stackA = []
		self.stackB = []
	def push(self, node):
		self.stackA.append(node)
	def pop(self):
		if self.stackB:
			self.stackB.pop()
		elif not self.stackA:
			return None
		else:
			while self.stackA:
				self.stackB.append(self.stackA.pop())
			return self.stackB.pop()		

拓展题目: 用两个队列实现一个栈

class solution:
	def __init__(self):
		self.queueA = []
		self.queueB = []
	def push(self, node):
		self.queueA.insert(0, node)
	def pop(self):
		if not self.queueA:
			return None
		else:
			while len(self.queueA) != 1:
				self.queueB.insert(0, self.queueA.pop())
			self.queueA, self.queueB = self.queueB, self.queueA
			return self.queueB.pop()

算法题目考查

  • 递归和循环 有很多算法都可以使用递归和循环实现,基于递归的代码通常更加简洁,但是性能不如基于循环的方式。
  • 回溯法 若是面试题要求在二维数组(具体表现为迷宫或是棋盘)上搜索路径,那么可以尝试使用回溯法,回溯法通常比较适合用递归的代码来实现。
  • 动态规划 对于求解某个问题的最优解,并且该问题可以分为很多子问题时,可以尝试使用动态规划的思路。为了避免重复计算,使用自下而上的循环代码来实现,也就是把子问题的最优解先计算出来并且使用数组(一般是一维的或者是二维的)保存下来,再基于这些子问题的解来计算大问题的解。
  • 贪婪算法 在分解子问题的时候发现存在某个特殊的选择,若是采用这个特殊的选择则一定可以得到最优解。(通常也会要求证明贪婪选择的结果一定可以得到最优解)
  • 位运算 可以看作是一类特殊的运算。掌握与、或、异或、左移和右移这五种基本的位运算。

10. 斐波那契数列

递归和循环:递归代码简洁,面试时尽量多的使用递归。但是因为递归是函数自身的调用,因此效率更低。除此之外,递归最为致命的问题是调用栈的溢出:当递归的层级过多时,就会超出栈的容量,导致调用栈溢出。

方法一: 基于循环

class solution:
	def Fibonacci(self, n):
		if n <= 0 :
			return 0
		elif n == 1:
			return 1
		else:
			small = 0
			big = 1
			for i in range(2, n+1):
				sum = small + big
				small = big
				big = sum
			return big

方法二: 基于递归

class solution:
	def Fibonacci(self, n):
		if n<=0:
			return 0
		elif n == 1:
			return 1
		else:
			return self.Fibonacci(n-1) + self.Fibonacci(n-2)

题目拓展1 <小青蛙跳台阶> 一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
该题目是斐波那契数列的现实版变形应用

class solution:
	def jumpFloor(self, n):
		if n<=0:
			return 0
		elif n == 1 or n == 2:
			return n
		else:
			small = 1
			big = 2
			for i in range(2, n):
				sum = small + big
				small = big
				big = sum
			return big		

题目拓展2 <小青蛙跳台阶进阶> 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。
数学归纳法证明: f ( n ) = 2 n − 1 f(n) = 2^{n-1} f(n)=2n1

class solution:
	def jumpFloor2(self, n):
		if n <= 0:
			return 0
		else:
			return 2**(n-1)

题目拓展3 <矩形覆盖>我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法。
斐波那契数列的变形

class Solution:
    def rectCover(self, number):
        # write code here
        if number<=0:
            return 0
        if number==1:
            return 1
        if number==2:
            return 2
        small,big=1,2
        for i in range(3,number):
            sum_i=small+big
            small=big
            big=sum_i
        return big 

11. 旋转数组的最小数字

查找和排序:

  • 二分查找是必须掌握的重点。哈希表和二叉树查找的重点在于考察数据结构,哈希表的最大有点事可以利用它能够在O(1)时间内查找到某一元素,是效率最高的查找方式,缺点在于需要使用额外的空间来实现。与二叉排序树对应的数据结构是二叉搜索树。
  • 排序比查找复杂一些。对于一般的排序方式都要求掌握,而且要会比较各种方式的特点,从额外空间消耗,平均时间复杂度和最差时间复杂度等方面去比较他们的优缺点。尤其需要掌握快速排序!

方法一: 利用二分查找和旋转数组的特点找到分界值。

class solution:
	def minNumberInRotateArray(self, rotateArray):
		# 对于非法输入的判断
		if not rotateArray:
			return 
		if Len(rotateArray) == 0:
			return 0
		index1 = 0
		index2 = len(rotateArray) - 1
		indexmid = index1
		# 递增数组本身也是一种特殊的循环数组。直接返回index1对应的数组值即可。不用参与下面的循环中
		while rotateArray(index1) >= rotateArray(index2):
			#循环终止的条件:
			if index1 - index2 == 1:
				indexmid = index2
				break
			indexmid = (index1 + index2) // 2
			# 中间元素位于后面的递增数组中:中间元素一定小于等于第二个指针的值。最小值位于中间元素之前
			if rotateArray[indexmid] <= rotateArray[index2]:
				index1 = indexmid
			# 中间元素位于前面的递增数组中:中间元素一定大于等于第一个指针的值。最小值位于中间元素之后
			elif rotateArray[indexmid] >= rotateArray[index1]:
				index2 = indexmid
			# 三个值相等时
			elif rotateArray[indexmid] == rotateArray[index1] and rotateArray[indexmid] == rotateArray[index2]:
				return self.minValue(rotateArray,index1,index2):
		return rotateArray[indexmid]

	def minValue(self,rotateArray,index1,index2):
		res = rotateArray[index1]
		for i in range(index1+1, index2+1):
			if rotateArray[i]<res:
				res = rotateArray[i]
		return res
				

12. 矩阵中的路径

回溯法:非常适合于由多个步骤组成的问题,并且每一个步骤都有多个选项的时候。对于重复选择的问题,使用回溯法类似于树状结构:树的也节点对应着终结状态,当该终结状态满足题目的约束条件时,则找到了一个可行的解决方法;否则则回溯。

对于二维数组中找路径的问题一般都使用回溯法。回溯法一般使用递归来实现。

class solution:
	def hasPath(self, matrix, rows, cols, path):
		# 对于非法输入的判断
		if not matrix or len(rows) == 0 or len(cols) == 0 or len(path) == 0:
			return False
		# 记录走过位置的矩阵
		markpath = [None] * (rows*cols)
		pathindex = 0
		for row in range(rows):
			for col in range(cols):
				if self.hasPathCore(row, col, rows, cols, matrix, path, markpath, pathindex, path):
					return True
		return False


	def hasPathCore(self, row, col, rows, cols, matrix, path, markpath, pathindex)
		# 回溯终结条件
		if pathindex == len(path):
			return True
		haspath = False
		if row>= 0 and row < rows and col >= 0 and col < cols and markpath[row*cols+col] == False and matrix[row*cols+col] == path[pathindex]:
			pathindex += 1
			markpath[row*cols+col] = True
			haspath = self. hasPathCore(row+1, col, rows, cols, matrix, path, markpath, pathindex) or
			self.hasPathCore(row-1, col, rows, cols, matrix, path, markpath, pathindex) or
			self.hasPathCore(row, col+1, rows, cols, matrix, path, markpath, pathindex) or
			self.hasPathCore(row, col-1, rows, cols, matrix, path, markpath, pathindex)
			# 回溯的条件
			if not haspath:
				pathindex -= 1
				markpath[row*cols+col] = False
		return haspath		

13. 机器人的运动范围

方法一: 使用回溯法

class solution:
	def movingCount(self, threshold, rows, cols):
		# 对于非法输入的判断
		if threshold < 0 or rows < 0 or cols < 0:
			return False
		markpath = [False] * (rows) * cols
		# 从【0,0】位置开始出发,因此不需要循环row和col
		count = self.movingCountCore(threshold,rows,cols,0,0,markmatrix)
		return count
		
	def movingCountCore(self, threshold,rows,cols,row,col,markmatrix):
		# 对于非法输入的判断
		if row < 0 or row >= rows or col < 0 or col >= cols:
			return False
		value = 0
		if self.check(rows, cols, row, col, threshold, markpath):
			markpath[cols*row+col] = True
			value = 1 + self.movingCountCore(threshold,rows,cols,row+1,col,markmatrix) + self.movingCountCore(threshold,rows,cols,row-1,col,markmatrix) +
self.movingCountCore(threshold,rows,cols,row,col+1,markmatrix) + 
self.movingCountCore(threshold,rows,cols,row+1,col,markmatrix)
		return value
	
	def check(self, rows, cols, row, col, threshold, markpath):
		if row >= 0 and row < rows and col >= 0 and col < cols and self.getDigitNum(row) + self.getDigitNum(row) <= threshold and not markpath[cols*row+col]:
			return True
		return False
				
	def getDigitNum(self,number):
		res = 0
		while number:
			res += number%10
			number = number//10
		return res
			

14. 剪绳子

动态规划与贪婪算法:

  • 动态规划(特点):
    - 大问题可以分解成为小问题
    - 小问题之间还有相互重叠的更小的问题
    - 大问题的最优解依赖于小问题的最优解
    - 采用从上往下的分析问题方法,采用从下往上的求解方法。
  • 贪婪算法:
    - 每一步都做出贪婪的选择,基于这个选择可以得到最优解。需要使用数学的方式来证明贪婪选择是正确的。

方法一: 动态规划

class solution:
	def MaxProductAfterCut(self, n):
		if n < 2:
			return 0
		if n == 2:
			return 1
		if n == 3:
			return 2
		products = [0]*(n+1)
		products[0] = 0
		products[1] = 1
		products[2] = 2
		products[3] = 3
		for i in range(4, n+1):
			max = 0
			for j in range(1, i//2+1):
				product = products[j]*products[i-j]
				if product>max:
					max = product
			products[i] = max
		return products[n]

方法二:贪婪算法:当剩余的绳子长度大于等于5时,尽量多的裁剪出3,若是所剩长度为4,裁剪成两个2。

class solution:
	def MaxProductAfterCut(self, n):
		if n < 2:
			return 0
		if n == 2:
			return 1
		if n == 3:
			return 2
		timesOf3 = n // 3
		if n - timesOf3*3 == 1:
			timesOf3 -= 1
		timesOf2 = (n-timesOf3) // 2
		return (3**timesOf3)*(2**timesOf2)
		

15. 二进制中1的个数

位运算:

  • 二进制:前缀0b;十进制:后缀d;十六进制:前缀0x;八进制:前缀0
  • 与,或,异或,左移和右移
    - 左移(<<):一定要区分于循环左移!!左移n位的时候,最左边的n位是直接丢弃的。
    - 右移 (>>):也要区分于循环右移!! 符号位是0时(正数),右移之后在最左边补充n个0;符号位是1时(负数),右移之后在最左边补充n个1。(因此,负数一直做右移操作最终一定会变成0xffffffff)
    - 如果该整数是负数,要把它和0xffffffff相与,消除负数的影响。

重要思路: 将一个数减去1之后再和原来的整数做位与运算,得到的结果相当于将原先整数的二进制表示中最右边的1变成0。

class solution:
	def NumberOf1(self, n):
		count = 0 
		if n < 0:
			n = n&(0xffffffff)
		while n:
			n = (n-1)&n
			count += 1
		return count

相关题目:判断一个整数是不是2的整数次方。(二进制表达中有且只有一个1)

class solution:
	def isNum2(self, n):
		if n < 0:
			n = n & (0xffffffff)
		return if not (n-1)&n

相关题目:输入两个整数m和n,计算需要改变m的二进制表示中的多少位才能得到n。
思路:先将两个数字进行异或运算,然后判断数字中1的个数

class solution:
	def needStepNum(self, m, n):
		tem = m ^ n
    	count = 0
    	while tem:
        	tem = (tem-1)&tem
        	count += 1
    	return count		

第三章:高质量的代码

代码的规范性:最好使用完整的英文单词组合命名变量和函数,不要使用含糊不清的变量名。
代码的完整性:基本功能,输入边界值得到正确的输出,对不合规范的非法输入做出了合理的错误处理。

16. 数值的整数次方

方法一: 全面但是不够高效的解法。要求求解base的exponent次方,且不需要考虑大数问题。只要处理好base是0和exponent是负数的情况就可以。

class solution:
	def Power(self, base, exponent):
		# 对于base是0的情况
		if base == 0 and exponent < 0:
			# 返回值、全局变量和异常这三种错误处理方式
			# 在这里使用全局变量处理异常
			g_InvalidInput = True
			return 0
		if exponent > 0:
			return self.PowerWithUnsignedExponent(base,exponent)
		return 1.0 / self.PowerWithUnsignedExponent(base,-exponent)
	def PowerWithUnsignedExponent(self,base,exponent):
		res = 1
		for i in range(exponent):
			res *= base
		return res		

方法二: 全面又高效的方法。

  • 基于递归用O(logn)的事件来求得n次方的算法。要想知道n次方,先要求得n/2次方,然后对该结果进行平方即可
  • 使用右移运算符来代替除以2。位运算的效率比乘除法要高
  • 使用位运算来判断数字的奇偶性。位运算的效率比求余运算要高
class solution:
	def Power(self, base, exponent):
		# 对于base是0的情况
		if base == 0 and exponent < 0:
			# 返回值、全局变量和异常这三种错误处理方式
			# 在这里使用全局变量处理异常
			g_InvalidInput = True
			return 0
		if exponent > 0:
			return self.PowerWithUnsignedExponent(base,exponent)
		return 1.0 / self.PowerWithUnsignedExponent(base,-exponent)
	def PowerWithUnsignedExponent(self,base,exponent):
		if exponent == 0:
			return 1
		if exponent == 1:
			return base
		result = self.PowerWithUnsignedExponent(base,exponent>>1)
		result *= result
		if exponent & 0x01 == 1:
			result *= base
		return result

17. 打印从1到最大的n位数

题目中的陷阱:

  • 大数问题:为了防止大数的溢出,使用数组或者字符串来表示大数
  • 打印字符串时:前面的0不需要打印出来,而是应该从第一个非0数字开始打印。这样更加符合阅读习惯。

方法一: 把问题转换成数字排列的方法,递归让代码更加简洁

如果题目中是关于n位的整数并且没有限制n的取值范围,或者输入任意大小的整数,那么一定要考虑大数问题字符串是有效表示大数问题的一种方法

class solution:
	def Print1ToMaxOfNDigits(self, n):
		# 对于非法输入的处理
		if n < 0:
			return 0
		number = [0] * n
		for i in range(10):
			number[0] = str(i)
			self.Print1ToMaxOfNDigitsRecursively(number,n,0)
			
	def PrintNumber(self,number):
		# 打印
		isBeginning0 = True
		for i in range(len(number)):
			if isBeginning and number[i] != '0':
				isBeginning = False
			if not isBeginning:
				print('%c'%number[i])
		print('\t')
		
	def Print1ToMaxOfNDigitsRecursively(self,number,length,index):
		# 开始打印的条件:字符串最低位被设置
		if index == length - 1:
			self.PrintNumber(number)
			return 
		for i in range(10):
			number[index+1] = str(i)
			self.Print1ToMaxOfNDigitsRecursively(number,length,index+1)
			

18. 删除链表的节点

要求1:在O(1)时间内删除链表节点
方法一: 常规思路是从head节点开始遍历一遍链表,目的是找到要删除的节点的前一个节点。但是,这样的时间复杂度将是O(n),为了避免这样耗时的顺序查找的过程,可以使用覆盖的思路。将所要删除的节点的下一个节点的值覆盖掉当前节点的值,再将当前节点的下一节点的指针指向下下一个节点。特殊情况: 当前链表只有一个节点;链表有多个节点,且要删除的节点是链表的尾节点。这种方法基于一个基本假设:所要删除的节点一定位于当前给定的链表中。

class ListNode:
	def __init__(self):
		self.val = None
		self.next = None

class solution:
	def delete_node(self,head_node,del_node):
		#对于非法输入的判断
		if not head_node or not del_node:
			return False
		# 常规情况:所要删除的节点存在next节点
		if del_node.next:
			del_node.val = del_node.next.val
			del_node.next = del_node.next.next
			del_node.next.val = None
			del_node.next.next = None
		# 链表只有一个节点
		elif head_node == del_node:
			head_node = None
			del_node = None
		# 所要删除的节点是尾巴
		else:
			node = head_node
			while node.next != del_node:
				node = node.next
			node.next = None
			del_node = None
		return head_node	

要求2: 在一个排序的链表中,删除其中重复的节点

class ListNode:
	def __init__(self, val):
		self.val = val
		self.next = None
class solution:
	def deleteDuplication(self, pHead):
		# 对于非法输入的判断
		if not pHead:
			return False
		# 先要创建一个伪头节点,避免从head开始就产生重复的情况
		first = ListNode(-1)
		first.next = pHead
		prenode = first
		# 从头节点开始遍历
		while pHead and pHead.next:
			if pHead.val == pHead.next.val:
				val = pHead.val
				while pHead and pHead.val == val:
					pHead = pHead.next
				prenode.next = pHead
			else:
				prenode = pHead
				pHead = pHead.next
		return first.next

19. 正则表达式匹配

方法一: 使用递归的思路

  • 第二个字符不是“*”时:两个串匹配成功则同时后移一位。不成功则结束
  • 第二个字符是“*”时:
    - 第一个字符不匹配:模式串后移两位,原串不动;
    - 第一个字符匹配:模式串后移两位,原串后移一位;模式串不动,原串后移一位;;模式串后移两位,重新匹配当前位和原串第一位。
class solution:
	def match(self, s, pattern):
		# 对于非法输入的判断
		if len(s) > 0  and len(pattern) == 0:
			return False
		if len(s) == 0 and len(pattern) == 0:
			return True
		# 对于第二个字符是“*”:
		if len(pattern) > 1 and pattern[1] == '*':
			# 第一个字符匹配的情况:
			if len(s) > 1 and (s[0] == pattern[0] or pattern[0] == '.'):
				# 注意千万不要忽略:只移动pattern串两位的情况。相当于直接忽略前两个字符进行匹配
				return self.match(s[1:],pattern[2:]) or self.match(s[1:],pattern) or self.match(s, pattern[2:])
			# 第一个字符就不匹配的情况,模式串直接后移两位
			else:
				return self.match(s,pattern[2:])
		# 第二个字符不是“*”:
		else:
			if len(s) > 1 and (s[0] == pattern[0] or pattern[0] == '.'):
				return self.match(s[1:],pattern[1:])
			else:
				return False	

20. 表示数值的字符串

规律总结:

  • 关于±号:只能出现在第一个位置或者是紧跟着e的第一个位置
  • 关于字母:只能出现e/E
  • 关于小数点:只能出现一次小数点,在小数点前后可以有数字或者无数字
class solution:
	def isNumeric(self, s):
		# 非法输入的判断
		if not s or len(s) == 0:
			return False
		s_list = [i.lower() for i in s]
		# 对于字符串中没有e的情况,纯数字的判断
		if 'e' not in s_list:
			return self.isDigit(s_list)
		elif 'e' in s_list:
			# 将字符串从e开始拆分成两个部分
			index = s_list.index('e')
			front = s_list[:index]
			behind = s_list[index+1:]
			if len(behind) == 0 or '.' in behind:
				return False
			return self.isDigit(front) and self.isDigit(behind)
	def isDigit(self,alist):
		allow_num = ['0', '1', '2', '3', '4', '5',
                     '6', '7', '8', '9', '+', '-', '.']
        dotnum = 0
        for i in range(len(alist)):
        	if alist[i] not in allow_num:
        		return False
        	elif alist[i] == '.':
        		dotnum += 1
        	elif (alist[i] == '+' or alist[i] == '-') and i != 0:
        		return False
        if dotnum > 1:
       		return False
       	return True		

21. 调整数组顺序使得奇数位于偶数之前

方法一: 借助两个指针:第一个指针指向数组中的第一个数字,第二个指针指向最后一个数字;第一个个指针从前向后移动,寻找偶数停下来,第二个指针从后向前移动,寻找奇数停下来,接着将两个指针所指的数字进行交换;结束的条件是两个指针相遇

class solution:
	def reOrderArray(self, array):
		# 对于非法输入的判断
		if not array or len(array) == 0:
			return 
		pBegin = 0
		pEnd = len(array) - 1
		while pBegin < pEnd:
			# 一定要记住在循环里面也要在判断一遍指针的大小
			while pBegin < pEnd and  not self.isEven(array[pBgein]):
				pBegin += 1
			while pBegin < pEnd and self.isEven(array[pEnd]):
				pEnd -= 1
			if pBegin < pEnd:
				array[pBegin], array[pEnd] = array[pEnd], array[pBegin]
		return array
	# 使用位运算来判断奇偶,时间复杂度更低:偶数返回True
	 def isEven(self,number):
        return number & 1==0

题目拓展: 不改变奇偶数字原本的顺序
解题思路:使用新的存储空间

class solution:
	def reOrderArray(self, array):
		# 对于非法输入的判断
		if not array or len(array) == 0:
			return 
		tmp1 = []
		tmp2 = []
		for i in array:
			if i & 1 == 0:
				tmp2.append(i)
			else:
				tmp1.append(i)
		return tmp1 + tmp2
	

22. 链表中倒数第k个节点

方法一:定义两个指针,一个快指针,一个慢指针。快指针走到第k步的时候,慢指针开始走。当快指针走到链表末尾时,慢指针到达第k个节点。注意一些特殊情况的处理:

  • 输入链表的头节点为空
  • 输入的链表的节点数小于k
  • k等于0时

思路总结: 当使用一个指针遍历链表不能解决问题时,可以尝试使用两个指针来遍历链表。快指针和慢指针。

class ListNode:
	def __init__(self, val):
		self.val = val
		self.next = None

class solution:
	def FindKthToTail(self, head, k):
		# 对于非法输入的处理
		if not head or k <= 0:
			return None
		pAhead = head
		pBehind = None
		for i in range(k-1):
			if pAhead.next:
				pAhead = pAhead.next
			# 防止链表的总节点数小于k
			else:
				return None
		pBehind = head
		while pAhead.next:
			pAhead = pAhead.next
			pBehind = pBehind.next
		return pBehind			

题目拓展:求链表的中间节点
思路:使用快慢指针,快指针一次两步,慢指针一次一步。快指针到达末尾时,慢指针指向中间节点。

class ListNode:
	def __init__(self, val):
		self.val = val
		self.next = None
class solution:
	def FindHalfnode(self, head):
		# 对于非法输入的判断
		if not head or head.next == None:
			return None
		pFast = head
		pSlow = head
		while pFast.next:
			if pFast.next.next:
				pFast = pFast.next.next
			else:
				pFast = pFast.next
			pSlow = pSlow.next
		return pSlow

23. 链表中环的入口节点

方法一: 仍然使用双指针的思想。首先将该题目分解成两个小问题:

  • 判断该链表中是否存在环:快慢指针,一个一步一个两步。若存在环快指针和慢指针一定会相遇
  • 判断链表的入口节点:相遇后,将其中一个指针重新指向链表的头节点,然后两个指针都一步步向前走,相遇的位置就是入口。
class ListNode:
	def __init__(self, val):
		self.val = val
		self.next = None
class solution:
	def EntryNodeOfLoop(self, pHead):
		# 对于非法输入的判断
		if not pHead:
			return None
		pFast = pHead
		pSlow = pHead
		while pFast and pFast.next:
			pFast = pFast.next.next
			pSlow = pSlow.next
			if pFast == pSlow:
				break
		if pFast == None or pFast.next == None:
			return None
		pFast = pHead
		while pFast != pSlow:
			pFast = pFast.next
			pSlow = pSlow.next
		return pFast	

附加要求:求环中的节点数目

class solution:
	def EntryNodeOfLoop(self, pHead):
		# 对于非法输入的判断
		if not pHead:
			return None
		pFast = pHead
		pSlow = pHead
		while pFast and pFast.next:
			pFast = pFast.next.next
			pSlow = pSlow.next
			if pFast == pSlow:
				break
		if pFast == None or pFast.next == None:
			return None
		meetingNode=pFast 
		nodes = 1
		while pFast != meetingNode:
			pFast = pFast.next
			nodes += 1
		return nodes

24. 反转链表

方法一: 使用三个指针,分别记录当前节点,前一个节点和后一个节点(防止链表断裂)。

class ListNode:
	def __init__(self, val):
		self.val = val
		self.next = None
class solution:
	def ReverseList(self, pHead):
		# 对于非法输入的判断
		if not pHead:
			return None
		if not pHead.next:
			return head
		pNode = pHead
		pPrev = None
		while pNode:
			pNext = pNode.next
			# 一定要注意最后一个节点的调整顺序!!!
			if not pNext:
				pNewhead = pNode
			pNode.next = pPrev
			pPrev = pNode
			pNode = pNext
		return pNewhead
		

25. 合并两个排序的链表

方法一: 使用递归思路。注意对于空链表的处理!

class ListNode:
	def __init__(self, val):
		self.val = val
		self.next = None
	
class solution:
	def Merge(self, pHead1, pHead2):
		if not pHead1:
			return pHead2
		elif not pHead2:
			return pHead1
		pNewhead = None
		if pHead1.val < pHead2.val:
			pNewhead = pHead1
			return self.Merge(pHead1.next, pHead2)
		else:
			pNewhead = pHead2
			return self.Merge(pHead1, pHead2.next)
		return pNewhead

26. 树的子结构

方法一: 使用递归,要注意空指针的情况!

class TreeNode:
	def __init__(self, val):
		self.val = val
		self.right = None
		self.left = None
class solution:
	def HasSubtree(self, pRoot1, pRoot2):
		res = False
		if pRoot1 and pRoot2:
			if pRoot1.val == pRoot2.val:
				res = self.SubtreeCore(pRoot1,pRoot2)
			if not res:
				res = HasSubtree(pRoot1.left,pRoot2)
			if not res:
				res = HasSubtree(pRoot1.right,pRoot2)
		return res
	def SubtreeCore(self,pRoot1,pRoot2):
		if pRoot2 == None:
			return True
		if pRoot1 == None:
			return False
		if pRoot1.val != pRoot2.val:
			return False
		return self.SubtreeCore(pRoot1.left, pRoot2.left) and self.SubtreeCore(pRoot1.right, pRoot2.right)

注意:如果树节点的val值是double的时候,需要自定义一个equal函数,当num1和num2相差小于1e-07时即可认为是相等的

def Equal(val1, val2):
	if abs(val1-val2) < 1e-07:
		return Ture
	else:
		return False

第四章:解决面试题的思路

在做题之前最好先和面试官讲清楚解题的思路。
解决复杂问题的三种思路:画图、举例和分解。

27.二叉树的镜像

方法一: 使用递归的思路:先前序遍历二叉树的每一个节点,如果遍历到的节点有子节点,就交换她的两个子节点。递归结束的条件是交换完所有非叶节点的左右子节点后。

考点:树的遍历算法

class TreeNode:
	def __init__(self,val):
		self.val = val
		self.right = None
		self.left = None
class solution:
	def Mirror(self, root):
		# 使用递归
		if not root:
			return
		if not root.left and root.right:
			return 
		ptemp = root.right
		root.right = root.left
		root.left = ptemp
		
		if root.left:
			return self.Mirror(root.left)
		if root.right:
			return self.Mirror(root.right)

28. 对称的二叉树

方法一: 如果一颗二叉树和它的镜像结果是一样的,则是对称的。定义一种新的前序遍历算法:根右左,同时考虑每一个节点的空节点。

考点:树的遍历算法

class TreeNode:
	def __init__(self, val):
		self.val = val
		self.right = None
		self.left = None

class solution:
	def isSymmetrical(self, pRoot):
		return self.isSymmetricalCore(pRoot,pRoot):
	# 一个使用前序遍历,一个使用变形版前序遍历。两者结果一样则说明对称。
	def isSymmetricalCore(self,pRoot1,pRoot2):
		if not pRoot1 and not pRoot2:
			return True
		if not pRoot1 or not pRoot2:
			return False
		if pRoot1.val != pRoot2.val:
			return False
		return self.isSymmetricalCore(pRoot1.left,pRoot2.right) and self.isSymmetricalCore(pRoot1.right,pRoot2.left)

29. 顺时针打印矩阵

方法一: 首先注意循环结束条件;其次注意最内圈的几种特殊情况的判断。

class solution:
	def printMatrix(self, matrix):
		# 对于非法输入的判断
		if not matrix:
			return 
		rows = len(matrix)
		columns = len(matrix[0])
		if rows <= 0 or columns <= 0:
			return 
		res = []
		start = 0
		while (rows > start*2 and columns > start*2):
			self.printMatrixInCircle(matrix,columns,rows,start,res)
			start += 1
		return res
		
	def printMatrixInCircle(self,matrix,columns,rows,start,res):
		endX = columns - 1 - start
		endY = rows - 1 -start
		# 从左到右打印一行
		for i in range(start, endX+1):
			res.append(matrix[start][i])
		# 从上到下打印一列
		if endY > start:
			for i in range(start+1, endY+1):
				res.append(matrix[i][endX])
		# 从右到左打印一行
		if endY > start and endX > start:
			for i in range(endX-1, start-1, -1):
				res.append(matrix[endY][i])
		# 从下到上打印一列
		if endY-1 > start and endX > start:
			for i in range(endY-1, start, -1):
				res.append(matrix[i][start])				

30. 包含min函数的栈

方法一: 借助辅助栈。数据栈每压入一个新的元素,都要在辅助栈中也添加如当前序列中最小的数据。保证辅助栈中元素数量始终和数据栈中一致,防止当前数据栈中最小元素已经被弹出,但是辅助栈中元素还没有更新的情况。

class solution:
	def __init__(self):
		self.stack = []
		self.minstack = []
	def push(self, node):
		self.stack.append(node)
		if self.minstack == [] or node.val < self.min():
			self.minstack.append(node)
		else:
			self.minstack.append(self.min())
	def pop(self):
		if self.stack == [] or self.minstack == []:
			return None
		self.stack.pop()
		self.minstack.pop()
	def min(self):
		# 辅助栈的栈顶元素是当前的最小值
		return self.minstack[-1]

31. 栈的压入弹出序列

方法一: 借助辅助栈。把数据从push栈中压入辅助栈中,若当前辅助栈的栈顶元素和pop栈的栈顶元素相同,则直接将pop栈中的该元素弹出。再次判断当前栈顶元素,相同则直接弹出;不同则压入新的元素再判断。直到pop栈中元素全部弹出,则表示一致。

class solution:
	def IsPopOrder(self, pushV, popV):
		# 建立一个辅助栈
		stack = []
		while popV:
			# 元素入栈后直接出站的情况
			if pushV and pushV[0] == popV[0]:
				pushV.pop(0)
				popV.pop(0)
			# 栈顶元素相等
			elif stack[-1] == popV[0]:
				stack.pop(-1)
				popV.pop(0)
			elif pushV:
				stack.append(pushV.pop(0))
			else:
				return False
		return True							

32. 从上到下打印二叉树

(1)题目一:不分行从上到下打印二叉树
方法一:不管是广度优先遍历一幅有向图还是一棵树,都要用到队列(两端都可以进出的队列)。首先把起始节点(对于树而言是根节点)放入队列。接下来每次从队列的头部去除一个节点,遍历这个节点之后把他可以到达的的节点依次放入到队列中。重复这个遍历过程,直到队列中的节点全部被遍历到为止。

class TreeNode:
	def __init__(self, val):
		self.val = val
		self.right = None
		self.left = None
class solution:
	def PrintFromTopToBottom(self, root):
		# 非法输入的判断
		if not root:
			return []
		res = []
		res_val = []
		# 先把树的根节点放进去
		res.append(root)
		while len(res) > 0:
			node = res.pop(0)
			res_val.append(node)
			if node.left:
				res.append(node.left)
			if node.right:
				res.append(node.right)
		return res_val

(2)题目二:分行从上到下打印二叉树
方法一:大体思路和上题一致。需要增加两个变量:当前层中还没有打印的节点数;下一层的节点数目

class TreeNode:
	def __init(self, val):
		self.val = val
		self.right = None
		self.left = None

class solution:
	# 需要返回的是一个二维列表
	def Print(self, pRoot):
		# 非法输入的判断
		if not pRoot:
			return []
		res = []
		res_val = []
		temp = []
		nextLevel = 0
		toBePrinted = 1
		while len(res) > 0:
			node = res[0]
			temp.append(node.val)
			if node.left:
				nextLevel += 1
				res.append(node.left)
			if node.right:
				nextLevel += 1
				res.append(node.right)
			
			del res[0]
			toBePrinted -= 1
			if toBePrinted == 0:
				res_val.append(temp)
				toBePrinted = nextLevel
				nextLevel = 0
				temp = []
		return res_val

方法二:牺牲空间换取时间和代码简洁度

class solution:
	def Print(self, pRoot):
		if not root:
			return []
		res = []
		nodes = [pRoot]
		while nodes:
			curstack, nextstack = [], []
			for node in nodes:
				curstack.append(node.val)
				if node.left:
					nextstack.append(node.left)
				if node.right:
					nextstack.append(node.right)
			res.append(curstack)
			nodes = nextstack
		return res

(3)题目三:之字形打印二叉树
方法一:使用两个栈。一个是当前层的节点,一个是下一层要打印的节点。如果当前打印的是奇数层,先保存左子节点,再保存右子节点;偶数层则是先保存右子节点。

class TreeNode;
	def __init__(self, val):
		self.val = val
		self.right = None
		self.left = None
class solution;
	def Print(self, pRoot):
		if not pRoot:
			return []
		res = []
		nodes = [pRoot]
		left2Right = True
		while nodes:
			curstack, nextstack = [], []
			if left2Right:
				for node in nodes:
					curstack.append(node.val)
					if node.left:
						nextstack.append(node.left)
					if node.right:
						nextstack.append(node.right)
			else:
				for node in nodes:
					curstack.append(node.val)
					if node.right:
						nextstack.append(node.right)
					if node.left:
						nextstack.append(node.left)
			res.append(curstack)
			nextstack.reverse()
			nodes = nextstack
			left2Right = not left2Right
		return res			

方法二:保存方法都一样,偶数行从右到左打印,偶数行从左到右打印。保存方式都使用先保存左节点,再保存右节点。

class solution:
	def Print(self, pRoot):
		if not pRoot:
			return []
		res = []
		nodes = [pRoot]
		left2Right = True
		while nodes:
			curstack, nextstack = [], []
			for node in nodes:
				curstack.append(node.val)
				if node.left:
					nextstack.append(node.left)
				if node.right:
					nextstack.append(node.right)
			res.append(curstack)
			if not left2right:
				nextstack.reverse()
			nodes = nextstack
			left2Right  = not left2Right
		return res
			

33. 二叉搜索树的后序遍历序列

方法一: 二叉搜索树的特点是左《根《右;首先找到根节点,其次根据节点值的大小来区分出左子树和右子树;使用递归来判断

class solution:
	def VerifySquenceOfBST(self, sequence):
		if not sequence or len(sequence) <= 0:
			return False
		# 首先找到根节点
		root = sequence[-1]
		i = 0
		# 通过二叉树特点分开左右子树
		for i in range(len(sequence)):
			if sequence[i] > root:
				break
			i += 1
		#当右子树中出现比根节点小的值时直接错误
		for node in sequence[i:-1]:
			if node < root:
				return False
		# 递归判断左右子树
		left = True
		if i > 0:
			left = self.VerifySquenceOfBST(sequence[:i])
		right = True
		if i < len(sequence) - 1:
			right = self.VerifySquenceOfBST(sequence[i:-1])
		return right and left

34. 二叉树中和为某一值的路径

方法一: 利用前序遍历,先访问根节点的规律,再结合递归调用;对路径的添加实质上是一种栈的压入和压出的过程:先进后出的特点。

class ListNode:
	def __init__(self, val):
		self.val = val
		self.next = None

class solution:
	def FindPath(self, root, expectNumber):
		# 对于非法输入的判断
		if not root:
			return []
		result = []
		
		def FindPathCore(root,path,currentNum):
			currentNum += root.val
			# 节点的压入过程
			path.append(root)
			# 到达叶节点的标志
			flag = (root.left == None and root.right == None)
			if currentNum == expectNum and flag:
				onepath = []
				for node in path:
					onepath.append(node.val)
				result.append(onepath)
			if currentNum < expectNum:
				# 利用前序遍历
				if root.left:
					FindPathCore(root.left,path,currentNum)
				if root.right:
					FindPathCore(root.right,path,currentNum)
			# 节点的弹出
			path.pop()
		
		FindPathCore(root,[],0)
		return result		

分治法思想: 将大问题分解成小问题,把分解之后的小问题各个解决,再把小问题的解决方案结合起来解决大问题。

35. 复杂链表的复制

方法一: 使用分治法的思想,将问题分解成三个步骤:根据原始链表的每个节点N创建对应的N‘(新创建的节点链接在原始节点之后);设置每一个节点的m_pSibling指针;根据节点的奇偶位置把原始链表拆分成两个。

class RandomListNode:
	def __init__(self, val):
		self.val = val
		self.next = None
		self.random = None

class solution:
	def Clone(self, pHead):
		# 非法输入的判断
		if not pHead:
			return None
		self.CloneNodes(pHead)
		self.ConnectRandomNodes(pHead)
		return self.ReconnectNodes(pHead)
	def CloneNodes(self,pHead):
		'''
		复制原始链表的每个节点,将复制得到的节点接到原始节点之后
		'''
		pNode = pHead
		while pNode:
			pCloned = RandomListNode(0)
			pCloned.val = pNode.val
			pCloned.next = pNode.next
			pNode.next = pCloned
			pNode = pCloned.next
		
	def ConnectRandomNodes(self,pHead):
		'''
		将复制后的链表中的克隆结点的random指针链接到被克隆结点random指针的后一个结点
		'''
		pNode = pHead
		while pNode:
			pCloned = pNode.next
			# 但一定要考虑空指针的情况
			if pNode.random != None:
				pCloned.random = pNode.random.next
			pNode = pCloned.next
				
	def ReconnectNodes(self,pHead):
		'''
		拆分链表	
		'''
		pNode = pHead
		pClonedNode = pClonedHead = pHead.next
		pNode.next = pClonedNode.next
		pNode = pNode.next
		while pNode:
			pClonedNode.next = pNode.next
			pClonedNode = pClonedNode.next
			pNode.next = pClonedNode.next
			pNode = pNode.next
		return pClonedHead

方法二: 使用递归法

class solution:
	def Clone(self, pHead):
		if not pHead:
			return None
		pClonedHead = ListRandomNode(pHead.val)
		pClonedHead.random = pHead.random
		pClonedHead.next = self.Clone(pHead.next)
		return pClonedHead

36. 二叉搜索树与双向链表

方法一: 递归,将特定节点的左指针指向其左子树中的最后子节点,将其右指针指向其右子树中的最左子节点,依次递归,调整好全部节点的指针指向。

class TreeNode:
	def __init__(self, val):
		self.val = val
		self.right = None
		self.left = None

方法二: 对树进行中序遍历,把遍历得到的结果存储到一个list中,再将list中的值组成一个双向链表。

class solution:
	def Convert(self, pRootOfTree):
		# 对于非法输入的判断
		if not pRootOfTree:
			return
		self.attr = []
		self.inOrder(pRootOfTree)

		for i, v in enumerate(self.attr[:-1]):
			self.attr[i].right = self.attr[i+1]
			self.attr[i+1].left = v
		return self.attr[0]
	def inOrder(self,root):
		# 将中序遍历结果存储下来
		if not root:
			return
		self.inOrder(root.left)
		self.attr.append(root)
		self.inOrder(root.right)

37. 序列化二叉树

实现两个函数,完成二叉树的序列化和反序列化

  • 序列化:采用前序遍历二叉树输出节点,再碰到左子节点或者右子节点为None的时候输出一个特殊字符”#”。
  • 反序列化:
class TreeNode:
	def __init__(self, val):
		self.val = val
		self.right = None
		self.left = None
class solution;
	flag = -1
	def Serialize(self, root):
		# 序列化
		if not root:
			return '#'
		return str(root.val) + ',' + self.Serialize(root.left) + ',' + self.Serialize(root.right)

	def Deserialize(self, s):
		# 反序列化
		self.flag += 1
		lis = s.split(',')
		# 如果当前遍历长度已经超过序列化后的结果,结束遍历
		if self.flag >= len(lis):
			return None
		root = None
		if lis[self.flag] != '#':
			root = TreeNode(int(lis[self.flag]))
			root.left = self.Deserialize(s)
			root.right = self.Deserialize(s)
		return root		

38. 字符串的排列

方法一: 递推的方法。将字符串分解成两部分:首字符和剩下的字符。

  • 首先将给定的字符按照字典顺序进行排列。再遍历不同的字符,将其分别作为首字符。
  • 将剩下的字符递归进行排列。流程图
class solution:	
	def Permutation(self, ss):
		if not ss:
			return []
		if len(ss) == 1:
			return list(ss)
		pStr = []
		charlist = list(ss)
		charlist.sort()

		for i in range(len(ss)):
			# 跳过相同的字符
			if charlist[i] == charlist[i+1]:
				continue
			# 递归对第一一个字符后面的部分进行排列
			temp = self.Permutation(''.join(list(charlist[:i]))+''.join(charlist[i+1:]))
			# 对第一个字符和后面的排列结果进行组合
			for j in temp:
				pStr.append(charlist[i] + j)
		return pStr

拓展题目:求给定字符的所有组合:输入a,b,c,但是输出是
a、b、c、ab、ac、bc、abc

思路:输入n个字符,求这n个字符的长度为m(1《m《n)的组合的时候,把字符分成两部分:第一个字符和其余的所有字符。

  • 组合中包含第一个字符:从剩余的字符中选取m-1个字符
  • 组合中不包含第一个字符:从剩余的字符中选取m个字符
class solution:
	def com(self, s):
		out = []
		if len(s) == 0:
			return 
		for i in range(1, len(s)+1):
			self.combation(s ,0, i, out):
	def combation(self,s ,begin, length, out):
		if length == 0:
			print(out)
		if begin >= len(s):
			return 
		
		out.append(s[begin])
		self.combination(s, begin+1, length-1, out)
		out.pop()
		self.combination(s, begin+1, length, out)		

第五章:优化时间和空间效率

39. 数组中出现次数超过一半的数字

方法一: 基于partition函数的时间复杂度为O(n)的算法。该算法的缺点是需要修改数组
方法二: 根据数组特点:若是数组中存在一个数字出现次数超过数组长度一半,就是说他出现的次数比其他所有数字出现次数只和还要多。
需要考虑的特殊情况:出现次数最多的数字,其次数仍旧没有超过数组长度一半

class solution:
	def MoreThanHalfNum_Solution(self, numbers):
		# 对于非法输入的判断
		if not numbers or len(numbers) == 0:
			return 0
		# 若是存在出现次数最多的数字一定是最后一次把times设置成1de数字
		res = numbers[0]
		times = 1
		for num in range(1, len(numbers)):
			if times == 0:
				res = numbers[i]
				times = 1
			elif numbers[i] == res:
				times += 1
			else:
				times -= 1
		# 统计该数字出现次数是否超过一半
		sum = 0
		for j in numbers:
			if j == res:
				sum += 1
		return res if sum > len(numbers)//2 else 0

40. 最小的k个数

方法一: python自带排序函数

class solution:
	def GetLeastNumbers_Solution(self, tinput, k):
		# 对于非法输入的判断
		if k > len(tinput) or not tinput:
			return []
		tinput.sort()
		return tinput[:k]

方法二: 基于Partition函数, 自己实现快速排序。递归实现

  • 优点:速度快
  • 缺点:需要对数组进行修改,当数字过多时,内存消耗过大
class solution:
	def GetLeastNumbers_Solution(self, tinput, k):
		# 对于非法输入的判断
		if k > len(tinput) or not tinput:
			return []
		def quick_sort(array):
		# 递归实现快速排序
			if not array:
				return []
			base = array[0]
			left = quick_sort([i for i in array[1:] if i < base])
			right = quick_sort([i for i in array[1:] if i >= base])
			return left + list(base) + right
		return quick_sort(tinput)[:k]

方法三: 使用最大堆:创建一个大小为K的数据容器来存储最小的K个数,然后遍历整个数组,将每个数字和容器中的最大数进行比较,如果这个数大于容器中的最大值,则继续遍历,否则用这个数字替换掉容器中的最大值。

  • 优点:适合处理海量数据。时间复杂度:O(Nlogk)
    python实现方法 heapq库: 该模块提供了堆排序算法的实现。堆是二叉树,最大堆中父节点大于或等于两个子节点,最小堆父节点小于或等于两个子节点。

heapq库:

# heapq库的应用
import heapq

#创建堆两种方法:空列表和heapq.heapify(nums)
nums = [1, 2, 3, 4, 5]
res = []
for i in nums:
	heapq.heappush(i, res)
# headq.heapify(nums)
# 堆创建好后,可以通过`heapq.heappop() 函数弹出堆中最小值。
print(heapq.heappop(nums))
#弹出最小的值,并且将新的值插入其中
print(heapq.heappushpop(nums))
# 借助弹出来实现堆排序
def heapsort(iterable):
	h = []
	for value in iterable:
		heapq.heappush(h, value)
	return [heapq.heappop(h) for i in range(len(h))]

# 访问堆内容:
# 获取堆中最大或最小的范围值
nums = [1, 2, 3, 4, 5]
print(heapq.nlargest(3, nums))
print(heapq.nsmallest(3, nums))

解法:

import heapq
class solution:
	def GetLeastNumbers_Solution(self, tinput, k):
		# 对于非法输入的判断
		if k > len(tinput) or not tinput:
			return []
		# 创建堆
		max_heap = []
		#利用最小堆实现最大堆,因此要两次取相反数
		for num in range(tinput):
			num = -num
			if len(max_heap) < k:
				# 直接压入
				heapq.heappush(max_heap, num)
			else:
				# 弹出堆中的最小值,将新的值插入
				heapq.heappushpop(max_heap, num)
		return list(map(lambda x:-x, max_heap))

41. 数据流中的中位数

方法一: 每一次数据流中有新数据进来都重新排序。对于奇数个数字,中位数是最中间的数字,偶数个是最中间的两个数字的平均值

class solution:
	def __init__(self):
		self.array = []
	def Insert(self, num):
		self.array.append(num)
		self.array.sort()
	def GetMedian(self):
		length = len(self.array)
		if length % 2 == 1:
			return self.array(length//2)
		else:
			return (self.array(length//2-1)+self.array(length//2))/2.0 	 

方法二: 使用最大堆和最小堆来实现。

  • 要保证数据平均分配到两个堆中:大顶堆和小顶堆。
  • 大顶堆用来存较小的数字,数字从大到小排列(利用取相反数实现);小顶堆存放较大的数字,数字从小到大排列
  • 当读取数据的时候,先将数据取相反数插入大顶堆中,再将大顶堆的堆顶元素取相反数插入小顶堆中。
  • 如果小顶堆的元素个数多于大顶堆的元素个数(即插入元素之后,所有的元素一共有奇数个)再把小顶堆的堆顶元素取相反数插入大顶堆中。如果小顶堆的元素个数等于大顶堆的元素个数(即当前元素的个数为偶数个),不再进行元素的变动。
import heapq

class solution:
	def __init__(self):
		self.small = []
		self.big = []
	def Insert(self, num):
		small, big = self.small, self.big
		# 先取反插入到大顶堆,同时把大顶堆堆定元素压入小顶堆
		heapq.heappush(small, -heapq.heappushpop(big, -num))
		if len(small) > len(big):
			heapq.heappush(big, -heap.heappop(small))
	def GetMedian(self,n=1):
		small,large = self.small,self.large
		if len(large) > len(small):
            return float(-large[0])
        return (small[0] - large[0]) / 2.0
数据结构插入的时间复杂度得到中位数的时间复杂度
没有排序的数组O(1)O(n)
排序的数组O(n)O(1)
排序的链表O(n)O(1)
二叉搜索树平均O(logn),最差O(n)平均O(logn),最差O(n)
AVL树O(logn)O(1)
最大堆和最小堆O(logn)O(1)

42. 连续子数组的最大和

方法一: 使用递归或者循环来实现:当以第i-1个数字结尾的子数组中所有数字的和小于0时,则以第i个数字结尾的子数组之和就是第i个数字本身;否则,以第i个数字结尾的子数组之和是i-1时的结果加第i个数字。

class solution:
	# 设置一个标志位,处理非法输入
	g_InvalidInput = False
	def FindGreatestSumOfSubArray(self, array):
		if not array or len(array) <= 0:
			# 设置异常标志位
			g_InvalidInput = True
			return 0
		g_InvalidInput = False
		cursum = 0
		# 负无穷!!!!!!
		greatsum = float('-inf')
		for num in array:
			if cursum <= 0:
				cursum = num
			else:
				cursum += i
			if cursum > greatsum:
				greatsum = cursum
		return greatsum
	

43. 1-n整数中1出现的次数

方法一: 不考虑时间效率的解法:对1-n中每一个数字中的每一位都判断其是否为1。即对每一个数字都做除法和求余运算。如果输入数字n,n有O(logn)位,判断每一位是否为1,时间复杂度是O(nlogn)。当n很大时运算效率较低

class solution:
	def NumberOf1Between1AndN_Solution(self, n):
		count = 0
		for i in range(1, n+1):
			count += self.NumberOf1Core(i)
		return count

	def NumberOf1Core(self,number):
		count = 0
		while number:
			if number % 10 == 1:
				count += 1
			number = number//10
		return count
				

方法二: 数学归纳总结法:https://blog.csdn.net/weixin_37251044/article/details/89283043
数学总结:
k = n % ( i ∗ 10 ) k = n\%(i*10) k=n%(i10)
c o u n t ( i ) = ( n / ( i ∗ 10 ) ∗ i + ( i f ( k > i ∗ 2 − 1 ) i e l s e i f ( k < i ) 0 e l s e ( k − i + 1 ) ) ) count(i) = (n/(i*10)*i + (if (k>i*2-1) i else if (k<i) 0 else (k-i+1))) count(i)=(n/(i10)i+(if(k>i21)ielseif(k<i)0else(ki+1)))

class solution:
	def NumberOf1Between1AndN_Solution(self, n):
		i = 10**(len(n)-1)
		ans = 0
		while i:
			k = n%(i*10)
			ans += (n%i*10)*i
			if k > i*2 - 1:
				ans+= i
			if k < i:
				ans += 0
			else:
				ans += k-i+1
			i = i//10
		return ans

python暴力法: 将数字转换成字符串之后,数1的个数

class solution:
	def NumberOf1Between1AndN_Solution(self, n):
		count = 0
		for i in range(1, n+1):
			count += str(i).count('1')
		return count

44. 数字序列中某一位的数字

方法一: 数学归纳得到的结论:

  • 0-9: 10个数字,每一个一位 10*1
  • 10-99: 90个数字,一个两位 90*2
  • 100-999: 900个数字,一个三位 900*3
class Solution:
    def findNthDigit(self, n):
        if n < 0:
            return -1
        # 几位数
        len_number = 1
        while True:
            numbers = 9*pow(10, len_number-1)
            if n <= numbers * len_number:
                print(n)
                a = (n-1)//len_number
                b = (n-1)%len_number
                print(a, b)
                base = pow(10, len_number-1)
                print(base)
                res = base + a
                return str(res)[b]
            n = n - len_number*numbers
            len_number += 1

45. 把数组排成最小的数

方法一: 定义一种新的比较规则;注意大数问题;反证法证明
注:使用字符串来解决大数问题

class solution:
	def theMax(self, str1, str2):
		# 定义新的比较规则
		return str1 if str1+str2 > str2+str1 else str2
	def PrintMinNumber(self, numbers):
		# 使用冒泡排序,把最大的放到最后
		string_num = [str(num) for num in numbers]
		length = len(numbers)
		# 冒泡排序
		for i in range(length-1):
			for j in range(0, length-1-i):
				if self.theMax(string_num[j], string_num[j+1]) == string_num[j]:
                    string_num[j], string_num[j+1]= string_num[j+1], string_num[j]
        return string_num				

46. 把数字翻译成字符串

方法一: 先试用递归的思想来分析问题: f ( i ) = f ( i + 1 ) + g ( i , i + 1 ) ∗ f ( i + 2 ) f(i) = f(i+1) + g(i,i+1)*f(i+2) f(i)=f(i+1)+g(i,i+1)f(i+2)
,其中 g ( i , i + 1 ) g(i, i+1) g(i,i+1)表示第i位和第i+1位数字拼接起来是否在0-25之间,若在则是1,不在为0。分析得到这样的结果之后为了保证效率,需要使用循环的方式来从后到前实现。

class solution:
	def getTranslationCount(self, number):
		#对于非法输入的判断
		if not number or len(number) == 0 or number<0:
			return 0
		numberstr = str(numberstr)
		return self.getTranslateCount(numberStr)
	def getTranslateCount(self,numberStr):
		length = len(numberStr)
		counts = [0] * length
		for i in range(length-1, -1, -1):
			count = 0
			if i < length-1:
				count += counts[i-1]
			else:
				count = 1
			if i <length-1:
				num = int(number[i])*10 + int(number[i+1])
				if num=>10 and num<=25:
					if i < length -2:
						count += counts[i-1]
					else:
						count += 1
			counts[i] = count
		return counts[0]		

47. 礼物的最大价值

方法一: 动态规划,创建一个二维数组(m*n)。数组中的每一个数字表示到达坐标(i,j)的格子时,礼物的最大价值。 f ( i , j ) = m a x ( f ( i − 1 ) , f ( j − 1 ) ) + g i f t ( i , j ) f(i,j) = max(f(i-1),f(j-1))+gift(i,j) f(i,j)=max(f(i1),f(j1))+gift(i,j)

class solution:	
	def getMaxValue1(self,array,rows,cols):
		# 对于非法输入的判断
		if not array or rows<=0 or cols<= 0:
			return 0
		maxValues = [[0 for i in range(cols)] for j in range(rows)]
		for i in range(rows):
			for j in range(cols):
				left = 0
				up = 0
				# 如果行号大于0,说明i-1行有数字
				if i > 0:
					up = maxValues[i-1, j]
				if j > 0:
					left = maxValues[i, j-1]
				# array是一维数组
				maxValues[i,j] = max(left, up) + array[i*cols+j]
		return maxValues[rows-1, cols-1]

方法二: 动态规划,创建一个一维数组(1*n)。到达(i,j)时的最大价值只和(i-1,j)和(i,j-1)处的最大价值有关。数组中下标0-j-1处的数字表示第i行前面j个格子礼物的最大价值。从j到下标n-1的数字表示第i-1行后面n-j个元素的最大价值。

class solution:
	def getMaxValue1(self,array,rows,cols):
		# 对于非法输入的判断
		if not array or rows<=0 or cols<=0:
			return 0
		maxValues = [0 for i in range(cols)]
		for i in range(rows):
			for j in range(cols):
				left = 0
				up = 0
				# 说明前面i-1行有内容
				if i > 0:
					up = maxValues[j]
				if j > 0:
					left = maxValues[j-1]
				maxValues = max(left, up) + array[i*cols+j]
		return maxValues[cols-1]

48. 最长不含重复字符的字符串

方法一: 使用递归。注意要建立一个数据结构存储各个字符出现的最新下标位置

  • 第i个字符在之前没有出现过: f ( i ) = f ( i − 1 ) + 1 f(i) = f(i-1)+1 f(i)=f(i1)+1 f ( i ) f(i) f(i)表示到第i个字符为结尾的最长长度
  • 第i个字符之前出现过,且上一次出现距离 d d d小于等于 f ( i − 1 ) f(i-1) f(i1) f ( i ) = d f(i) = d f(i)=d
  • 第i个字符之前出现过,且上一次出现距离 d d d大于 f ( i − 1 ) f(i-1) f(i1) f ( i ) = f ( i − 1 ) + 1 f(i) = f(i-1)+1 f(i)=f(i1)+1
class solution:
	def lengthOfLongestSubstring(self, s):
		# 对于非法输入的判断
		if not s:
			return 0
		maxlength = 0
		curlength = 0
		usedChar = {}
		for i in range(len(s)):
			if s[i]in usedChar and i - usedChar[s[i]]<= curlength:
				curlength = i - usedChar[s[i]]
			else:
				curlength += 1
			maxlength = max(maxlength, curlength)	
			usedChar[s[i]] = i 
		return maxlength			

49. 丑数

时间和空间效率的平衡:以时间换空间\以空间换时间

方法一: 对每一个数字逐个判断是否为丑数,虽然直观但是不够高效。

class solution:
	def GetUglyNumber_Solution(self, index):
		# 对于非法输入的判断
		if index <= 0:
			return 0
		totalNum = 0
		uglyNum = 0
		while uglyNum<index:
			totalNum += 1
			if self.isUgly(totalNum):
				uglyNum += 1
		return totalNum
		
	def IsUgly(self,number):
		while number%2 == 0:
			number = number//2
		while number%3 == 0:
			number = number//3
		while number%5 == 0:
			number = number//5
		return True if number == 1 else False
			

方法二: 创建一个排序的数组保存已经找到的丑数。因为新的丑数一定是由旧的丑数*2,3或者5得到的。

class solution:
	def GetUglyNumber_Solution(self, index):
		# 对于非法输入的判断
		if index <= 0:
			return 0
		Ugly_list = [1]
		curindex = 1
		t2 = t3 = t5 = 0
		while curindex<index:
			minval = min(Ugly_list[t2]*2, Ugly_list[t3]*3, Ugly_list[t5]*5)
			Ugly_list.append(minval)
			while Ugly_list[t2]*2 <= minval:
				t2 += 1
			while Ugly_list[t3]*3 <= minval:
				t3 += 1
			while Ugly_list[t5]*5 <= minval:
				t5 += 1
			curindex += 1
		return Ugly_list[index-1]	

50. 第一个只出现一次的字符

方法一: 创建一个哈希表来使用辅助空间完成任务。

class solution:
	def FirstNotRepeatingChar(self, s):
		# 对于非法输入的判断
		if not s or len(s) <= 0:
			return -1
		temp_dict = {}
		for i in s:
			if i not in temp_dict:
				temp_dict[i] = 1
			else:
				temp_dict[i] += 1
		for index, val in enumerate(temp_dict):
			if temp_dict[val] == 1:
				return index
		return -1

方法二: 使用一个包含256个字符的辅助数组来构建一个虚拟哈希表,每个字符根据它的ascaii码值作为数组的下标对应数组的一个数字,数组中存储的是每一个字符出现的次数。(字符char是8bit的类型,总共只有256个字符)

class Solution:
    def FirstNotRepeatingChar(self, s):
        # write code here
        if s=='':
            return -1
        if len(s)==1:
            return 0
 
        hash_table=[0]*256
        for c in s:
            hash_table[ord(c)]+=1
        for i,c in enumerate(s):
            if hash_table[ord(c)]==1:
                return i
        return -1

拓展题目:字符流中第一个只出现一次的字符。

class solution:
	def __init__(self):
		self.char = []
		self.char_dict = {}
	def Insert(self, char):
		if char not in self.char_dict:
			self.char.append(char)
			self.char_dict[char] = 1
		else:
			self.char.append(char)
			self.char_dict[char] = -2
	def FirstAppearingOnce(self):
		for index, val in enumerate(self.char_dict):
			if self.char_dict[val] == 1:
				return val
		return '#'		

51. 数组中的逆序对

方法一: 暴力法。两层循环将每一个元素和它之后的元素进行比较

class solution:
	def InversePairs2(self, data):
		# 对于非法输入的判断
		if not data or len(data) <= 1:
			return 0
		count = 0
		length = len(data)
		for i in range(length):
			for j in range(i+1, length):
				if data[i] > data[j]:
					count += 1
		return count

方法二: 递归的归并排序方法。

class solution:
	def InversePairs3(self, data):
		# 对于非法输入的判断
		if not data or len(data) <= 1:
			return 0
		length = len(data)
		# 创建一个辅助数组
		copy = [0]*length
		count = self.Core(data, copy, 0, length-1)
		return count
	def Core(self,data,copy,start,end):
		# 结束递归的条件
		if start == end:
			copy[start] = data[start]
			return 0
		length = (end - start) // 2
		left = self.Core(data, copy, start, start+length)
		right = self.Core(data, copy, start+length+1, end)
		
		indexcopy = end
		p1 = start + length
		p2 = end
		while p1 >= start and p2 >= start+length+1:
			if data[p1] > data[p2]:
				copy[indexcopy] = data[p1]
				indexcopy -= 1
				p1 -= 1
				count += p2-start-length
			else:
				copy[indexcopy] = data[p2]
				indexcopy -= 1
				p2 -= 1
		# 对于没有复制完的数组剩余部分
		while p1 >= start:
			copy[indexcopy] = data[p1]
			indexcopy -= 1
			p1 -= 1
		while p2 >= start +length + 1:
			copy[indexcopy] = data[p2]
			indexcopy -= 1
			p2 -= 1
		return count + left + right		

52. 两个链表的第一个公共节点

方法一: 蛮力法:在第一个链表上顺序遍历每个节点,每遍历到一个节点,就在第二个链表上顺序遍历每个节点,直到找到第二个链表上有节点和第一个链表上的节点一样停止。(O(mn))

方法二: 从后往前遍历两个链表将其存入到两个辅助栈中。利用栈的先进后出的特点实现从尾到头的比较。(时间复杂度O(m+n),且需要额外辅助空间O(m+n))

class ListNode:
	def __init__(self, val):
		self.val = val
		self.next = None
class solution:
	def FindFirstCommonNode(self, pHead1, pHead2):
		stack1 = []
		stack2 = []
		while pHead1:
			stack1.append(pHead1)
			pHead1 = pHead1.next
		while pHead2:
			stack.append(pHead2)
			pHead2 = pHead2.next
		node = ListNode(0)
		while stack1 or stack2:
			node = stack1.pop()
			val1 = node.val
			val2 = stack2.pop().val
			if val1 == val2:
				continue
			else:
				res = node
				return res
		return False			

方法三: 快走法。首先遍历两个链表得到它们的长度,如果m>n,则m链表先走m-n步,然后两个链表再同时走,直到找到第一个相同的节点(即为它们的第一个公共节点)。(推荐,时间复杂度O(m+n),且不需要额外辅助空间)。

class ListNode:
	def __init__(self, val):
		self.val = val
		self.next = None
class solution:
	def FindFirstCommonNode(self, pHead1, pHead2):
		length1 = self.GetLength(pHead1)
		length2 = self.GetLength(pHead2)

		if length1 > length2:
			fasthead = pHead1
			slowhead = pHead2
		else:
			fasthead = pHead2
			slowhead = pHead1
		diff = abs(length1-length2)
		for i in range(diff):
			fasthead = fasthead.next
		while fasthead != None and slowhead != None and fasthead != slowhead:
			fasthead = fasthead.next
			slowhead = slowhead.next
		return fasthead
		
	def GetLength(self,pHead):
		length = 0
		while pHead:
			length += 1
			pHead = pHead.next
		return length

第六章:面试中的各项能力

53. 在排序数组中查找数字

方法一: 使用二分查找法确定第一次出现该数字的位置和最后一次出现该数字的位置。

class solution:
	def GetNumberOfK(self, data, k):
		# 非法输入
		if not data or len(data) == 0:
			return 0
		number = 0
		length = len(data)
		first = self.GetFirst(data, length, k, 0, length-1)
		last = self.GetLast(data, length, k, 0, length-1)
		if first > -1 and last > -1:
			number = last - first + 1
		return number

	def GetFirst(self,data,length,k,start,end):
	# 找到函数中第一个
		if start > end:
			return -1
		while start <= end:
			middle = (start+end) // 2
			if data[middle] == k:
				if middle > 0 and data[middle-1] == k:
					end = middle-1
				else:
					return middle
			elif data[middle] > k:
				end = middle -1
			else:
				start = middle + 1
			return self.GetFirst(data, length, k, start, end)
	def GetLast(self, data, length, k, start, end):
		# 找到函数中最后一个
		if start > end:
			return -1
		middle = (start+end)//2
		if data[middle] == k:
			if middle < end and data[middle+1] == k:
				start = middle + 1
			else:
				return middle
		elif data[middle] > k:
			end = middle -1
		else:
			start = middle + 1
		return self.GetLast(data, length, k, start, end)		

题目二: 找到0-n-1中缺失的数字
方法一: 使用等差数列求和,减去目前所有数字的和

class solution:
	def GetLostNum(self, num):
		# 对于非法输入的判断
		if not num:
			return -1
		n = len(num)
		# 先求得0-n-1的标准和
		standard_sum = n*(n-1)/2
		cur_sum = 0
		for i in num:
			cur_sum += i
		return the (standard_sum - cur_sum)		

方法二: 使用二分查找法,找到第一个下标和当前位置数字不相等的元素即可。结果就是该数字对应的元素下标。

class solution:
	def GetLostNum(self, num):
		# 对于非法输入的判断
		if not num:
			return -1
		return self.FindFirstIndex(num, 0, len(num)-1)
		
	def FindFirstIndex(self, num, start, end):
		# 二分查找法查找
		if start > end:
			return -1
		middle = (start + end)//2 
		if num[middle] != middle:
			if num[middle-1] != middle-1:
				end = middle -1
			else:
				return middle
		else:
			start = middle + 1
		return self.FindFirstIndex(num, start, end)		

题目三: 找到递增数组中数值和下标相等的元素
方法一:二分查找法

class solution:
	def FindMatchNum(self, num):
		# 对于非法输入的判断
		if not num or len(num) < 1:
			return -1
		start = 0
		end = len(num-1)
		#使用二分查找
		while start <= end:
			middle = (start + end)//2
			if num[middle] == middle:
				return middle
			elif num[middle] > middle:
				end = middle - 1
			else:
				start = middle + 1		
		return -1

54. 二叉搜索树的第k大节点

方法一: 使用中序遍历,得到的结果的第k个即为最终所得。

class TreeNode:
	def __init__(self, val):
		self.val = val
		self.left = None
		self.right = None
class solution:
	def KthNode(self, pRoot, k):
		# 对于非法输入的判断
		if not pRoot or k<= 0:
			return None
		res = []
		def Inorder(self, pRoot):
			if not pRoot:
				return []
			InOrder(pRoot.left)
			res.append(pRoot)
			InOrder(pRoot.right)
		InOrder(pRoot)
		if len(res) < k:
			return None
		return res[k-1]		

55. 二叉树的深度

方法一: 递归实现。如果一棵树只有一个节点,深度为1;如果根节点只有左子树,深度为左子树深度+1;右子树同理;若是根节点左右子树都有,则深度为两子树深度较大值+1。

class TreeNode:
	def __init__(self, val):
		self.val = val
		self.left = None
		self.right = None
class solution:
	def TreeDepth(self, pRoot):
		if pRoot == None:
			return 0
		left = self.TreeDepth(pRoot.left)
		right = self.TreeDepth(pRoot.right)
		return max(left, right) + 1

拓展一:判断是否为平衡二叉树

方法一: 后序遍历加判断是否深度相差大于1,同时进行

class TreeNode:
	def __init__(self, val):
		self.val = val
		self.left = None
		self.right = None
class solution:
	def TreeDepth(self, pRoot):
		if pRoot == None:
			return 0
		left = self.TreeDepth(pRoot.left)
		right = self.TreeDepth(pRoot.right)
		return max(left, right) + 1
	def IsBalanced(self, pRoot):
		if pRoot is None:
			return True
		left = self.TreeDepth(pRoot.left)
		right = self.TreeDepth(pRoot.right)
		diff = abs(left-right)
		if diff > 1:
			return False
		return self.IsBalanced(pRoot.left) and self.IsBalanced(pRoot.right)		

56. 数组中数字出现的次数

题目一:数组中只出现一次的两个数字
方法一:利用异或运算(任何一个数字异或他自己都等于0),将原始的数组分成两个,每一个数组中都包含一个出现了一次的数字。然后再次利用异或运算分别找到这两个数字。

class solution:
	def FindNumsAppearOnce(self, array):
		# 对于非法输入的判断
		if not array or len(array)<2:
			return 
		resultEOR = 0
		for i in array:
			resultEOR = result^i
		# 得到异或结果出现1的位置
		index = self.FindFirstBit(resultEOR)
		# 根据从右边起indexBit位是否为1来进行分组
		res1, res2 = 0, 0
		for i in array:
			if self.IsBit(i, indexBit):
				res1 = res1^i
			else:
				res2 = res2^i
		return [res1, res2]
		
	def FindFirstBit(self,num):
		# 用于找到在整数num中最右边是1的位
		indexBit = 0
		while num&1 == 0 and indexBit < 32:
			num = num >> 1
			indexBit += 1
		return indexBit
	def IsBit(self,num,indexBit):
		# 用于判断在二进制表示中从右边起的indexBit位是否为1,是的话返回1
		num = num >>indexBit
		return num&1

题目二:数组中唯一只出现一次的数字。其余数字都出现三次
方法一:因为其余数字都出现三次,所以将数字的二进制表示按位相加。如果某一位的和能被三整除,则该数字对应位位0,否则是1。

class solution:
    def FindOnceNum(self,num):
        if not num or len(num)<=3:
            return
        res_temp = [0]*32
        for i in num:
            for j in range(31, -1, -1):
                # 当前位是1
                if i & 1 == 1:
                    res_temp[j] += 1
                else:
                    res_temp[j] += 0
                i = i >> 1
        res = 0
        for num in range(len(res_temp)):
            # 当前位不是3的倍数,缺失数字对应位为1
            if res_temp[num]%3 != 0:
                res += pow(2, len(res_temp)-num-1)
            else:
                res += 0
        return res		

57. 和为s 的数字

题目一:输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得他们的和正好是s。如果有多对数字的和等于s,输出任意一对即可。
方法一:从两端向中间移动。使用两个指针,在初始时候分别指向数组中第一个数字和最后一个数字,若当前和较小,则将第一个指针向后移动。若和较大,则将第二个指针向前移动。循环结束的条件是两个指针相遇。

class solution:
	def FindNumbersWithSum(self, array, tsum):
		# 非法输入
		if len(array) < 2:
			return []
		found = False
		start, end = 0, len(array)-1
		while start<end:
			cur_sum = array[start] + array[end]
			if cur_sum == tsum:
				found = True
				return [array[start], array[end]]
			elif cur_sum < tsum:
				start += 1
			else:
				end -= 1
		return []	

题目二:输入一个正数s,打印出所有和为s的连续正数序列。
方法一:使用两个指针small和big。循环结束的条件是small到(1+s)/2。

class solution:
	def FindNumbersWithSum(self, array, tsum):
		# 对于非法输入的判断
		if tsum< 3:
			return []
		small, big = 1, 2
		cursum = small + big
		middle = (s+tsum)//2
		res = []
		while small <= middle:
			if cursum == tsum:
				res.append(list(range(small, big+1)))
			while cursum > tsum and small<middle:
				cursum -= small
				small += 1
				if cursum == tsum:
					res.append(list(range(small, big+1)))
			big += 1
			cursum += big
		return res		

58. 翻转字符串

题目一:输入一个英文句子,反转句子中单词的顺序,但单词内字符的顺序不变。
方法一:先翻转句子中所有的字符;再反转每个单词中的顺序

class solution:
	def ReverseSentence(self, s):
		# 对于非法输入的判断
		if not s or len(s) == 0:
			return ''
		s = list(s)
		# 第一次反转
		s = self.Reverse(s)
		start = 0
		end = 0 
		listTemp = []
		res = ''
		while end<len(s):	
			# 到最后一位
			if end == len(s)-1:
				listTemp.append(self.Reverse(s[start:]))
				break
			elif s[start] == '':
				start += 1
				end += 1
				listTemp.append('')
			elif s[end] == '':
				listTemp.append(self.Reverse(s[start:end]))
				start = end
			else:
				end += 1
		for i in listTemp:
			res += ''.join(i)
		return res
		
	def Reverse(self,s):
		start = 0
		end = len(s) - 1
		while start < end:
			s[start], s[end] = s[end], s[start]
			start += 1
			end -= 1
		return s	

题目二:左旋转字符串。把字符串前面的若干个字符转移到字符串的尾部
方法一:调用三次Reverse函数。先分别反转两个部分,再将整个字符串反转一次。

class solution:
	def LeftRotateString(self, s, n):
		if s in None or len(s) <= 0:
			return ''
		if len(s) <= n:
			return s
		s = list(s)
		listTemp = []
		result = ''
		left = listTemp.append(self.Reverse[0:n])
		right = listTemp.append(self.Reverse[n:-1])
		return ''.join(self.Reverse(sum(listTemp,[])))
	def Reverse(self, s):
		start = 0
		end = len(s) - 1
		while start < end:
			s[start], s[end] = s[end], s[start]
			start += 1
			end -= 1
		return s

59. 队列的最大值

题目一:给定一个数组和滑动窗口的大小,找出所有滑动窗口的最大值。

方法一:使用两端开口的队列。队列中保存的元素是下标,队列头部保存的是最大值。在存入一个数字的下标之前,首先要判断队列里已有数字是否小于待存入的数字。如果已有数字小,则将他们依次从队列尾部删除。同时要判断当前队列头部的数字是否已经从滑窗中滑出。

class solution:
	def maxInWindows(self, num, size):
		# 对于非法输入的判断
		if not num or size<= 0:
			return []
		res = []
		if len(num) >= size and size >= 1:
			deque = []
			for i in range(size):
				while deque and num[i] >= num[deque[-1]]:
					deque.pop()
				deque.append(i)
		for i in range(size, len(num)):
			# 队列头的元素是当前最大元素
			res.append(num[deque[0]])
			# 判断当前元素是否大于队列尾部元素
			while deque and num[i] >= num[deque[-1]]:
				deque.pop()
			# 判断队列头的元素是否已经在滑窗之外
			if deque[0] <= i-size:
				deque.pop(0)
			deque.append(i)
		res.append(num[deque[0]])
		return res		

题目二:求得队列的最大值

class solution:
	def __init__(self):
		self.deque = []
		self.maxstore = []
		self.start = 0
		self.end = 0
		self.maxindex = 0
	def push_back(self, num):
		# 将元素压入
		self.deque.append(num)
		while deque and num >= self.deque[self.maxstore[-1]-start]:
			self.maxstore.pop()
		self.maxstore.append(self.end)
		self.end += 1
	def pop_front(self):
		self.deque.pop()
		self.start += 1
		while self.maxstore and (self.maxstore[0] <start or self.maxstore[0] >= end):
			self.maxstore.pop(0)
	def max(self):
		return self.queue[self.maxstore[0]-start]

60. n个骰子的点数

方法一: 使用循环的方法,下一轮点数和为n的次数应该等于上一轮点数和为n-1…n-6的点数只和

class solution:
	def numberOfDice(self, n):
		# 行表示每一轮,列表示可能出现的点数和
		res = [[0 for i in range(6*n+1)] for i in range(n+1)]
		# 首先进行第一轮
		for i in range(1, 7):
			res[1][i] = 1
		# 第二轮到第n轮
		for i in range(2, n+1):
			for j in range(i, 6*i+1)
				for k in range(1, min(j+1, 7)):
					res[i][j] += res[i-1][j-k]
		prob = []
		total = pow(6,n)
		for i in range(n, 6*n+1):
			prob.append(res[-1][i]/total)
		return prob

61. 扑克牌中的顺子

方法一: 首先将数组中的数字进行排序。计算数组中0的个数,判断空缺处是否小于或者等于0的个数。当数组中出现了两个完全一样的数字时直接返回错误。

class solution:
    def IsContinuous(self, numbers):
        # 非法输入的判断
        if not numbers:
            return False
        numbers.sort()
        print(numbers)
        zero_count = 0
        empty = 0
        for i in range(len(numbers)-1):
            if numbers[i] == 0:
                zero_count += 1
            else:
                if numbers[i] == numbers[i+1]:
                    return False
                elif numbers[i+1] - numbers[i] > 1:
                    empty += (numbers[i+1] - numbers[i] - 1)
        print(zero_count, empty)
        if empty <= zero_count:
            return True
        return False

62. 圆圈中该最后剩下的数字

题目:0-n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。
方法一: 典型的约瑟夫环的问题。 f ( n , m ) = [ f ( n − 1 , m ) + m ] % n f(n,m) = [f(n-1,m)+m]\%n f(n,m)=[f(n1,m)+m]%n(当n为1时,结果为0)

class solution:
	def LastRemaining_Solution(self, n, m):
		# 非法输入的判断
		if n < 1 or m < 1:
			return -1
		res = 0
		for i in range(2, n+1):
			res = (res+m)%n
		return res		

63. 股票的最大利润

class solution:
    def maxProfit(self, prices):
        min_price = prices[0]
        max_profit = 0
        for price in prices[1:]:
            if price<min_price:
                min_price = price
            profit = price - min_price
            max_profit = max(profit, max_profit)
        return max_profit

64. 求解1+2+3+…+n

方法一: 利用两个函数,一个函数充当递归函数的角色,另一个函数处理终止递归的情况,如果**对n连续进行两次反运算,那么非零的n转换为True,0转换为False。**利用这一特性终止递归。注意考虑测试用例为0的情况。

# -*- coding:utf-8 -*-
class Solution:
    def Sum_Solution(self, n):
        # write code here     
        return self.sum(n)
    
    def sum0(self,n):
        return 0
    
    def sum(self,n):
        func={False:self.sum0,True:self.sum}
        return n+func[not not n](n-1)

方法二: 优化后的结果

class solution:
	def sum_solution(self, n):
		return n and n + self.sum_solution(n-1)

65. 不用加减乘除做加法

方法一: 使用位运算,首先对两个数字进行异或运算(没有进位的加法);再对两个数字进行位与运算,左移一位后(进位),将结果与上一步得到的数字再进行异或,指导第二步没有进位产生。
注:要注意与0xffffffff,消除负数的影响。(复数符号位是1)

class solution:
	def Add(self, num1, num2):
		while num2:
			temp = (num1^num2)&0xffffffff
			carry = ((num1&num2)<<1)&0xffffffff
			num1 = temp
			num2 = carry
		if num1 < 0x7fffffff:
			return num1
		else:
			return ~(num1^0xffffffff)
			

相关问题:不使用新的变量,交换两个变量的值

基于加减法基于异或运算
a = a + ba = a ^ b
b = a - bb = a ^ b
a = a - ba = a ^ b

66. 构建乘积数组

方法一: B [ i ] B[i] B[i]拆分为两个部分的乘积。 C [ i ] = A [ 0 ] ∗ A [ 1 ] ∗ . . . . ∗ A [ i − 1 ] C[i]=A[0]*A[1]*....*A[i-1] C[i]=A[0]A[1]....A[i1], D [ i ] = A [ i + 1 ] ∗ . . . . A [ n − 2 ] ∗ A [ n − 1 ] D[i]=A[i+1]*....A[n-2]*A[n-1] D[i]=A[i+1]....A[n2]A[n1]。归纳可知, C [ i ] = C [ i − 1 ] ∗ A [ i − 1 ] C[i] = C[i-1]*A[i-1] C[i]=C[i1]A[i1], D [ i ] = D [ i + 1 ] ∗ A [ i + 1 ] D[i]=D[i+1]*A[i+1] D[i]=D[i+1]A[i+1]

class solution:
	def multiply(self, A):
		n = len(A)
		B = [1]*n
		C = [1]*n
		D = [1]*n
		for i in range(1, n+1):
			C[i] = C[i-1]*A[i-1]
		for j in range(n-2, -1, -1):
			D[j] = D[j+1]*A[j+1]
		for k in range(0,n):
			B[k] = C[k]*D[k]
		return B

总结

1. 相关的数据结构

  • 树:常考树的遍历算法,和二叉搜索树的性质。常与递归相结合
    - T7,8,26,27,28,33,34,35,36,54,55
  • 栈和队列:两者常常混考
    - 栈:T9,30,31
    - 队列:T9,32,59:不管是广度优先遍历一幅有向图还是一棵树,都要用到队列。(T32)
  • 链表:T18, 22, 23, 24, 25, 32
  • 数组和字符串:T38,39,42

2. 算法

  • 递归和循环
    - 常常使用递归分析,但是循环解决。递归一般和树的遍历一起考。T16,19,25,26, 27, 38,42, 48,55
  • 回溯法
    - 常常用于在二维数组中寻找路径的题目,或是迷宫题。T12, 13
  • 贪婪算法
    - 一般需要证明T14
  • 动态规划: T14, 25

位运算

  • 异或/或/与
  • 左移/右移和循环左移/右移的区别。
  • 对于负数的处理(0xffffffff)
  • 相关技巧:
    - T15: 计算1的个数(判断是否是2的倍数;改变多少位变成新的数字)
    - T16: 右移相当于除以2,左移相当于乘2
    - T16: 奇偶的判断
    - T65: 不使用加减乘除实现加法
    - T65: 交换两个数字

3. 查找/排序

  • 二分查找:T3, 11, 53
  • 快速排序:T40(比base大的放在右边,小的放在左边;递归)
  • 归并排序:T51 (拆分成两个部分,两个指针进行比较)

4. 其他技巧

  • 大数问题:使用数组或者字符串解决。T17,45
  • 两个指针的逼近法:T21,57
    - 快慢指针:T21(中间节点寻找:快指针2步,慢指针1步),T53
  • 数学归纳/规律:T43,44,49,58(反转字符串)
  • 约瑟夫环: r e s = ( r e s + m ) % n res = (res+m)\%n res=(res+m)%n(长度为n,找到第m)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值