(题目和部分思路来自leetcode 剑指offer)
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
1.使用哈希表,遍历数组,第一次出现的数字放入哈希表里,若哈希表里已经存在这个数字,则直接return这个数字。
class Solution:
def findRepeatNumber(self, nums: List[int]) -> int:
dic = set()
for num in nums:
if num in dic:
return num
dic.add(num)
return
**自我反思:**哈希表,直接用set就可以了(add, remove, pop),只有那种需要键值对的才用字典{}(比如统计元素出现次数),不要只会用字典。。。
2.原地算法,即要求不能使用额外的数据结构。遍历数组nums,把nums[i]放到nums[nums[i]]的位置(交换),比如数组的第一位是4,就把4放到nums[4]那里。直到发现nums[i]==nums[nums[i]],即那个位置已经放的是他自己了,那就证明这个数字重复了。
class Solution:
def findRepeatNumber(self, nums: List[int]) -> int:
i = 0
while i < len(nums):
if nums[i] == i:
i += 1
continue
if nums[i] == nums[nums[i]]:return nums[i]
nums[nums[i]],nums[i] = nums[i],nums[nums[i]]
return
**自我反思:**说实话,这种真不太容易想,如果没有要求必须原地算法,最好还是乖乖用哈希表吧。
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
示例:
现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
1.第一次做时,用了个又臭又烂又长的方法。先找边界,然后在这个边界内遍历。
class Solution:
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
if matrix==[] or matrix==[[]]:
return False
max_col = len(matrix[0])-1
max_row = len(matrix)-1
if target<matrix[0][0] or target>matrix[max_row][max_col]:
return False
for i in range(len(matrix[0])):
if matrix[0][i]>target:
max_col = i
for j in range(len(matrix)):
if matrix[j][0]>target:
max_row = j
for i in range(max_row+1):
for j in range(max_col+1):
if matrix[i][j] == target:
return True
return False
**自我反思:**基本就是完全顺着题意的无脑解法,首先遍历第一行,找到第一个大于target的行号,再遍历列,找到列号的边界,然后在这个区域内遍历,找target。
2.K神之究极解法,从矩阵的最左下角的值开始,如果target比他大,那么就消去这一列,因为这一列肯定都比他小了;如果target比他小,那么久消去这一行,因为这一行肯定都比他大了。
class Solution:
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
i, j = len(matrix)-1, 0
while i >= 0 and j < len(matrix[0]):
if matrix[i][j] > target: i-=1
elif matrix[i][j] < target: j+=1
else: return True
return False
**自我反思:**确实妙啊,矩阵左下角的值含义非常特殊,刚好是这一行的最小值,又是这一列的最大值,同理右上角应该也可以。
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
示例 1:
输入:s = "We are happy."
输出:"We%20are%20happy."
1.抖机灵做法(不可取)。只能说这种题用python真的十分简单。先用split把字符串按空格分开,再用%20把它们join起来。
class Solution:
def replaceSpace(self, s: str) -> str:
return "%20".join(s.split(" "))
2.遍历然后替换就可以了,只不过python的字符串是不可修改的,所以必须用一个新的空间来放元素。
class Solution:
def replaceSpace(self, s: str) -> str:
temp = []
for x in s:
if x == ' ':temp.append('%20')
else: temp.append(x)
return ''.join(temp)
**自我反思:**抖机灵可能笔试还行,面试肯定是通不过的。
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
1.正向遍历链表,把value放进数组里,然后反向输出数组。
class Solution:
def reversePrint(self, head: ListNode) -> List[int]:
result = []
while head:
result.append(head.val)
head = head.next
return result[::-1]
2.K神之递归法,其实和树的后序遍历有点像,其实只是看起来代码简介而已,其实递归大家懂得都懂,慢的一批。
class Solution:
def reversePrint(self, head: ListNode) -> List[int]:
return self.reversePrint(head.next) + [head.val] if head else []
**自我反思:**正向遍历然后反向输出,其实就是栈。
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
1.巨长。其实说白了,就是利用前序遍历的第一个节点,就是根节点,以此来区分左右两颗子树。
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if preorder == []:
return
def recursion(tree,preO,inO):
if len(preO)==0:
return
mid = inO.index(preO[0])
tree.val = preO[0]
left_in = inO[:mid]
right_in = inO[mid+1:]
left_pre = preO[1:len(left_in)+1]
right_pre = preO[len(left_in)+1:]
if len(left_in)>0:
tree.left = TreeNode(0)
if len(right_in)>0:
tree.right = TreeNode(0)
recursion(tree.left,left_pre,left_in)
recursion(tree.right,right_pre,right_in)
root = TreeNode(0)
recursion(root, preorder, inorder)
return root
**自我反思:**看了一下其他人的答案,其实大家的思路是一样的。
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
示例 1:
输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
1.定义两个数组,append的时候就往数组1里面加,delete的时候就把数组1倒进数组2,然后pop,再倒回数组1。
class CQueue:
def __init__(self):
self.stack1 = []
self.stack2 = []
def appendTail(self, value: int) -> None:
self.stack1.append(value)
def deleteHead(self) -> int:
if len(self.stack1)==0:
return -1
while self.stack1:
self.stack2.append(self.stack1.pop())
head = self.stack2.pop()
while self.stack2:
self.stack1.append(self.stack2.pop())
return head
2.之前一直很疑惑,方法肯定没问题,但是运行时间特长。。。其实delete的时候,根本不需要把stack2在倒回去。
def deleteHead(self) -> int:
if self.stack2: return self.stack2.pop()
if len(self.stack1)==0: return -1
while self.stack1:
self.stack2.append(self.stack1.pop())
return self.stack2.pop()
**自我反思:**有的时候方法还是欠优化吧,做好优化才能提升效率。
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:1
1.我愿称之为动态规划的鼻祖。
class Solution:
def fib(self, n: int) -> int:
a = 0
b = 1
for _ in range(n):
a, b = a+b, a
return a%1000000007
**自我反思:**万恶资源,由此引出了太多太多的动态规划题。。。
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:2
1.其实就是个斐波那契问题,就是最后一步爬一阶+最后一步爬两阶。
class Solution:
def numWays(self, n: int) -> int:
a = 1
b = 1
if n==0 or n==1:return a
for i in range(2,n+1):
a, b = a+b, a
return a%1000000007
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
示例 1:
输入:[3,4,5,1,2]
输出:1
1.呆呆办法,之间遍历数组,找到那个突然减小的数,就是最小数。
class Solution:
def minArray(self, numbers: List[int]) -> int:
for i in range(len(numbers)-1):
if numbers[i]>numbers[i+1]:
return numbers[i+1]
return numbers[0]
2.二分法,两个指针i和j,求中间位置mid,然后比较mid和j的大小,如果mid>j,证明mid在左排序数组里,那最小值就在[mid+1,j]中,如果mid **自我反思:**暴力搜索的时间复杂度为O(N),而二分法为O(logN) 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。 [[“a”,“b”,“c”,“e”], 但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。 示例 1: 1.整理题目的时候,对自己答案的长度十分震惊。具体来讲就是先找到所有可能的开始的位置,然后从这些可能的开始位置出发进行深度优先搜索,每次都只能前进到当前位置的周围四个位置,而且这个位置还不能在之前出现过,直到找到全部字符串。 2.其实解题的思路都是一样的,但是我写的实在是太啰嗦了,仿照着K神的思路,简化了一波代码: **自我反思:**其实当时的思路里存在很多无用功,比如没必要先找出可能的起始点,比如可以采用剪枝的思路,将遍历过的位置编程‘.’,不必像我那样专门整个数组,放走过的路径。。。虽然最后时间效率上提升那么大(因为基本思路一致),但这种小细节的改进确实能提升代码的可读性和效率。 地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子? 示例 1: 1.自己的方法依然十分拉垮,打败了5%的用户。总结来讲就两个字,硬走。设计了两个函数,一个judge判断这个位置符不符合条件,然后写了一个深度优先搜索,去遍历,每踩到一个合适的位置,就把这个位置写进可行解集合里,最后返回集合的长度。 2.K神解法,dfs的话,其实只需要向下或者向右走就可以了。 3.广度优先算法待补充 给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。 示例 1: 1.这道题最最关键的地方在于,一定要想到绳子尽量都剪成3的长度,最终的乘积才是最大的。但是,当时以为2也可以,所以分了两种可能。 **自我反思:**这道题的难点就在于选择合适的切绳长度,但如果是第一次遇到这种题或者在有限的时间里,其实很难想到。 给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m - 1] 。请问 k[0]k[1]…*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。 示例 1: 1.其实题目跟上一个题是一样的,但是多了溢出的问题。这里抄了份最简单的代码。(因为我一开始考虑错了,我直接套用了上一题的代码,然后只在最后的结果那取了余,其实这是不对的,因为过程中就可能已经溢出了。) **自我反思:**真的太naive了。完全没有考虑过程中会溢出的问题,这种要是面试,会被当场逮住吧。 请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。 示例 1: 1.考虑获得数字的二进制表示,除2取余,或者python用函数bin。 2.通过位运算符,每次判断n的最后一位是不是1,将n和1相与,然后将n右移1位。 **自我反思:**面试的时候还是用第二种会加分 实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题。 示例 1: 1.因为n有可能非常大,如果用傻瓜算法,去循环N次,一定会超时。所以每次把n缩小一半,让x*x **自我反思:**基本思路就是这样,如果要考虑越界问题,每次乘的时候还得加一个取模。class Solution:
def minArray(self, numbers: List[int]) -> int:
i, j = 0, len(numbers)-1
while i != j:
mid = int((i+j)/2)
if numbers[mid] > numbers[j]: i = mid + 1
elif numbers[mid] < numbers[j]: j = mid
else: j -= 1
return numbers[i]
剑指12.矩阵中的路径
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
may = []
self.max_row = len(board)
self.max_col = len(board[0])
self.board = board
self.result = False
if len(word)>self.max_row*self.max_col:
return False
for i in range(self.max_row):
for j in range(self.max_col):
if self.board[i][j] == word[0]:
may.append([i,j])
if len(word) == 1 and len(may)>0:
return True
for op in may:
if self.result:
break
self.recursion([],op,word[1:],[op])
return self.result
def recursion(self,pre,loc,s,done):
if self.result == True:
return
r = loc[0]
c = loc[1]
around = [[r-1,c],[r+1,c],[r,c-1],[r,c+1]]
if pre in around:
around.remove(pre)
for location in around:
if location[0]>=self.max_row or location[1]>=self.max_col or location[0]<0 or location[1]<0 or [location[0],location[1]] in done:
continue
if self.board[location[0]][location[1]] == s[0]:
done.append([location[0],location[1]])
if len(s)==1:
self.result = True
return
else:
self.recursion(loc,[location[0],location[1]],s[1:],done)
done.pop()
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
def recursion(i,j,k):
if not 0<=i<len(board) or not 0<=j<len(board[0]) or board[i][j]!=word[k]:
return False
if k == len(word)-1:
return True
board[i][j] = '.'
result = recursion(i-1,j,k+1) or recursion(i+1,j,k+1) or recursion(i,j-1,k+1) or recursion(i,j+1,k+1)
board[i][j] = word[k]
return result
for i in range(len(board)):
for j in range(len(board[0])):
if recursion(i,j,0):
return True
return False
剑指13.机器人的运动范围
输入:m = 2, n = 3, k = 1
输出:3
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
self.gone = [[0,0]]
def judge(i,j):
temp = i%10 + i//10%10 + i//10//10%10 + j%10 + j//10%10 + j//10//10%10
if temp <= k and i>=0 and i < m and j >=0 and j < n:
return True
return False
def recursion(loc):
around = [[loc[0]-1,loc[1]],[loc[0]+1,loc[1]],[loc[0],loc[1]-1],[loc[0],loc[1]+1]]
for next_step in around:
if next_step not in self.gone and judge(next_step[0],next_step[1]):
self.gone.append(next_step)
recursion(next_step)
recursion([0,0])
return len(self.gone)
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
def recursion(i,j,si,sj):
if i >=m or j >=n or k<si+sj or (i,j) in visited: return 0
visited.add((i,j))
return 1 + recursion(i+1,j,si+1 if (i+1)%10!=0 else si-8,sj) + recursion(i,j+1,si,sj+1 if (j+1)%10!=0 else sj-8)
visited = set()
return recursion(0,0,0,0)
**自我反思:**说实话,看大佬的代码,真的跟诗一样,真的值得反复揣摩。反观自己的代码,真的又臭又烂又长,效率还低。剑指14-1.剪绳子
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
class Solution:
def cuttingRope(self, n: int) -> int:
self.result = []
if n == 2:
return 1
if n == 3:
return 2
self.cut = [2,3]
def recursion(lenth,temp,m):
if lenth <= 0:
if lenth == 0 and m > 1:
self.result.append(temp)
return
for may in self.cut:
lenth = lenth - may
temp = temp * may
m += 1
recursion(lenth,temp,m)
lenth = lenth + may
temp = temp / may
m -= 1
recursion(n,1,0)
return int(max(self.result))
class Solution:
def cuttingRope(self, n: int) -> int:
if n <= 3: return n-1
a,b = n//3,n%3
if b == 0: return 3**a
if b == 1: return (3**(a-1))*4
return (3**a)*2
剑指14-2.剪绳子2
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
class Solution:
def cuttingRope(self, n: int) -> int:
result = 1
if n < 4:
return n - 1
while n > 4:
result = result * 3 %1000000007
n -= 3
return result * n % 1000000007
剑指15.二进制中1的个数
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
class Solution:
def hammingWeight(self, n: int) -> int:
return list(bin(n)).count('1')
class Solution:
def hammingWeight(self, n: int) -> int:
result = 0
while n:
result += n&1
n >>= 1
return result
剑指16.数值的整数次方
输入:x = 2.00000, n = 10
输出:1024.00000
class Solution:
def myPow(self, x: float, n: int) -> float:
result = 1
if n == 0:return 1
if n<0:
x = 1/x
n = -n
while n!= 1:
temp = n%2
n = n//2
if temp == 1:
result = result * x
x = x*x
return result*x