这里依旧以easy的题目为主,因为个人代码量比较少,通过easy题来训练良好的代码习惯,为后面hard的题目做准备。
Problem 67:Add Binary
给定两个二进制字符串,返回求和结果的二进制字符串,如,输入a=’11’,b=’1’,输出’100’。
解题思路:第一种思路直接从字符串出发进行求解,采用递归的形式,考虑每个位置,有如下三种情况:
- 两个值都为“1”,则结果为a[0:-1]和b[0:-1]求和后再与“1”(进位)求和,在拼接一个“0”
- 两个值都为“0”,则结果为a[0:-1]和b[0:-1]求和后再拼接一个“0”。
- 如果一个为“1”,一个为“0”,则结果为a[0,-1]和b[0,-1]求和后再拼接一个“1”。
第二种思路即将字符串转换成int,求和后再转换成str。
Problem 69:Sqrt(x)
给定一个int,返回它的平方根。对于非完全平方数,返回平方根的向下取整,如3,则返回1。
解题思路:采用二分查找,从0-x开始,如果mid^2>x,l=mid;如果mid^2 <x,r=mid,当r-l小于等于1时停止循环,则l就是结果。
Problem 70:Climbing Stairs
给定n阶楼梯,每次可以选择走一阶或者两阶,问有多少种走法?
解题思路:最常规的解决办法是用递归,类似斐波那契数列,我们可以做的优化即用字典保存中间计算过的值从而减少递归的次数。另外,还可以用线性代数来解这个题,通过构造矩阵,化简为一个二阶差分方程。
Problem 83:Remove Duplicates from Sorted List
给定一个元素值为int的排序链表,移除其中重复的元素并返回处理后的链表。
解题思路:和之前的一个题一样,只不过数据结构由数组变成了链表,唯一需要注意的就是在处理完毕后将当前指针指向None,以便处理末尾元素重复的情况。如 1→2→3→3 ,如果没有这个操作,3显然是去不掉的(按照之前的解法)。
Problem 88:Merge Sorted Array
给定两个个元素值为int的排序数组,将其合并成一个排序数组,假设A有足够的空间容纳AB两个数组的元素。
解题思路:同之前的题,只不过将链表换成了数组,链表只用调节指针即可,而这里如果采用同样的方式,每个元素在每次比较时都需要移动。为此我们直接从n+m-1处开始依次复制元素,这样在
O(m+n)
的复杂度即可解决问题。每次元素最多移动一次。
Problem 100:Same Tree
判断两个树是否相同,即同时具有相同的结构,并且对应节点值相等。
解题思路:递归:
class Solution(object):
def isSymmetricTree(self, p, q):
if p and q:
return p.val == q.val and self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
return p is q
Problem 101:Symmetric Tree
判断一棵树是不是对称的,对称即如字面意思。
解题思路:上面一题的变型,可以理解为判断左子树和右子树是否对称,相同即左-左,右-右,对称修改为左-右,右-左即可,原理同上题:
class Solution(object):
def isSymmetricTree(self, p, q):
if p and q:
return p.val == q.val and self.isSymmetricTree(p.left, q.right) and self.isSymmetricTree(p.right, q.left)#左-右,右-左
return p is q
def isSymmetric(self, root):
if not root:
return True
if root.left and root.right:
return self.isSymmetricTree(root.left, root.right)
return root.left is root.right
Problem 104:Maximum Depth of Binary Tree
给定一棵二叉树,求它的最大深度。
解题思路:最常规的用递归,三行代码,但是面试时一般这样的答案得分为0,为此可以用迭代方法求解,即BFS(对于树就是层次遍历)或DFS
Problem 107: Binary Tree Level Order Traversal II
给定一棵二叉树,从底到顶,从左到右的返回每一层的节点值,返回值类型为list[list[int]]。
解题思路:层次遍历,每次出栈的时候记录节点值,每层遍历完,添加当前层的节点值,这样的结果是自顶向下的,然后reverse以下即是结果。
Problem 108:Convert Sorted Array to Binary Search Tree
给定一个升序排列的数组,将其转化为二叉平衡搜索树。
解题思路:递归建树,以数组的中间节点为根节点,然后左右分别递归。
Problem 110:Balanced Binary Tree
给定一棵树,判断该树是否为平衡二叉树。
解题思路:树的题目大多数可以用递归来解决,一课树是平衡树,当左右子树高差小于1,且左子树和右子树又分别一个棵平衡树,为此我们定义一个函数求解树的深度,则返回只需递归判断上述三个条件即可:
class Solution(object):
def depth(self, root):
if not root:
return 0
return max(self.depth(root.left), self.depth(root.right)) + 1
def isBalanced(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
if not root:
return True
return abs(self.depth(root.left) - self.depth(root.right)) <= 1 and self.isBalanced(root.left) and self.isBalanced(root.right)
Problem 111:Minimum Depth of Binary Tree
给定一棵树,判断该树的最小深度。最小深度定义为根节点到叶节点的最短距离。
解题思路:同求最大距离,但是这里的题目貌似对最小深度的定义过于严苛,比如[1,2],它认为答案是2而不是1,因为右子树为空没有叶节点,我在想如果输入[1]结果是多少?
Problem 112:Path Sum
给定一棵树和一个指定的值,判断该树中是否存在路径和为指定的值。
解题思路:递归求解,边界条件:
- 左右孩子为空且当前val==sum,证明存在,返回True
- 如果root为空,返回False
- 否则,递归返回左子树等于sum-当前val或者右子树等于sum-当前val
Problem 118:Pascal’s Triangle
给定一个numRows,返回numRows行的Pascal三角形,Pascal三角形是如下形式的三角形。
解题思路:对于第i行的第j个数,如果j==0 or j==i,则值为1,否则为tri[i-1][j-1]+tri[i-1][j]
Problem 119:Pascal’s Triangle II
给定一个rowIndex,返回第rowIndex行的Pascal三角形。
解题思路:同上,这里因为每一行只与上一行有关,因此可以用两个变量来存储相邻两行即可,空间复杂度
O(rowIndex)
Problem 121:Best Time to Buy and Sell Stock
给定一个数组,第i个元素为stock第i天的价格(出售或者购买),你只能分别购买一次和出售一次,如何才能使得利润最高?在买之前不能卖。譬如[7, 1, 5, 3, 6, 4],最大利润为6-1=5。
解题思路:不难发现,我们可以按照如下规律查找,min表示前i-1个位置的最小值,max_pro表示前i个位置的最大利润,则:
- 如果当前值小于min,则不能购买,且min赋值为当前值
- 如果当前值大于min,可以以min的价格购买并以当前价格出售,max_pro=max(max_pro, 当前值-min)
Problem 122:Best Time to Buy and Sell Stock II
同上,但是可以购买多次,出售多次。
解题思路:遍历数组,依次比较相邻两个元素的值,只要后者大于前者,我就买了再卖,这样利润最大。
Problem 123:Best Time to Buy and Sell Stock III
Problem 188:Best Time to Buy and Sell Stock IV
上述两题为hard难度,是121和122的变体,即如果我们最多能完成k次交易,能获得的最大利润是多少?,价格数组为prices。
解题思路:用动态规划求解,可以看到,我们在截止至第i个元素进行第k次购买的最大值p[k][i]:①当前不购买,则为截止至第i-1个元素进行第k次购买的最大值p[k][i-1];②当前购买,则利润为截止至第j个元素进行第k-1次购买的最大值和用price[i]参与购买的利润之和,p[k-1][j]+prices[i]-price[j],j为0~i-1,上述两者大的即为p[k][i]。边界条件:当数组只有一个元素,利润为0。
Problem 122: Valid Palindrome
给定一个字符串,判断是否会回文,只考虑字母和数字,可能有空格和标点符号,字母无视大小写。
解题思路:题目简单,主要就是效率,遍历次数过多会超时,因此不能先处理后判断,而要边处理便判断,分别从头尾向中间遍历。
Problem 136:Single Number
给定一个元素为整数的数组,其中只有一个数出现一次,其余的数出现两次,找出并返回这个出现一次的数。
解题思路:使用异或运算,从头到尾遍历,取数组元素依次异或,最终结果即为出现一次的数。
Problem 155:Min Stack
实现一个栈,同时具有一个getMin方法来获取当前栈中的最小值。
解题思路:在python中用一个list来模拟栈,同时维护一个当前栈的最小值栈,需要注意:
- 入栈时,如果最小值栈为空,则将该值也加入最小值栈;如果不为空,判断当前值与最小值栈栈顶元素的大小,小于或者等于栈顶元素即入栈。. 因为多次压入同一个值时,最小值要保存多次,因此最小值栈的入栈条件是<=而不是小于。
- 出栈时,对栈做非空判断,同时判断当前栈顶元素是否等于最小值栈的栈顶元素,如果相等,则最小值栈的栈顶元素也要弹出。
Problem 160:Intersection of Two Linked Lists
给定两个无环链表,判断其是否有公共部分。
解题思路:常规的解法是分别遍历两个链表,拿到两者的长度lenA和lenB,然后从头开始遍历:如果lenA>lenB,则让A先走lenA-lenB步,然后两者同时遍历比较,如果遍历至末尾仍然没有相同节点,则返回None,否则返回对应的交点。一共需要遍历4次
这里有另外一个解法,不用知道长度,并且只用遍历两次,我们从头开始遍历,如果其中一个为None,则让它指向另外一个的头,这样,当两者分别遍历完一次之后,第二次必然会同时到达尾部节点,因此循环的退出条件即为A=None and B=None。
def getIntersectionNode(self, headA, headB):
if headA == None or headB == None:
return None
A = headA
B = headB
while A != B:
A = headB if A == None else A.next
B = headA if B == None else B.next
if A == None and B == None:
return None
return A
Problem 160+:Intersection of Two Linked Lists
PS:这里仅仅考虑的是无环的情况,对于有环的情况呢?我们分几种情况讨论:
- 一个有环一个无环,这样必然不可能相交,
- 两个都有环且不相交
- 两个都有环且相交,但是交点不在环内
- 两个都有环且相交,且交点在环内
解题思路:我们首先要判断两个链表是否有环,如果有,入环节点在哪?我们可以分别对两个链表这么做,从头开始,设置快慢指针,如果快指针指向None了代表没有环,我们得到它的len;如果有环,则快慢指针必定会相交,注意,这里有一个核心逻辑:相交时,再设置一个快指针从头开始,和之前的快指针一起遍历,两者的交点即为入环节点,同样我们可以获取到截止入环之前的长度len。这个可以用数学证明。
对于无环的情况前面已经解决,对于两个有环的链表,我们用处理无环的常规解法,判断是否在入环之前有交点,如果没有,则在入环后一块一慢遍历,两圈之内无交点,则证明无交点。
Problem 167:Two Sum II - Input array is sorted
给定一个升序的数组和一个tar,返回数组中两个数之和等于tar的两个数对应的索引。
解题思路:分别从头l=0和尾巴r=len(numbers)-1向中间遍历,如果和大于tar,r -= 1,;如果和小于tar,l += 1,;相等则返回。
Problem 168: Excel Sheet Column Title
给定一个正整数,返回Excel对应列的编码,格式如下:
解题思路:递归,每次分为两部分,整除26大于1的部分用于递归,然后拼接,除26的余数对应的字母,如果整除后为0,则返回空。
def convertToTitle(self, n):
return "" if n == 0 else self.convertToTitle((n - 1) / 26) + chr((n - 1) % 26 + ord("A"))
Problem 171:Excel Sheet Column Number
给定一个Excel表列,将其转换成对应的数字,即把上个题中的操作反过来。
def titleToNumber(self, s):
return 0 if s == "" else self.titleToNumber(s[:-1])*26 + ord(s[-1]) - ord("A") + 1
*Problem 169:Majority Element
给定一个数组,返回其中出现次数超过一半的元素,假设数组不为空,并且这个元素总是存在的。
解题思路:遍历,用两个变量记录当前候选cand和出现次数count,初始化时,cand=nums[0],count=1,遍历中,如果count==0,则设置cand = nums[i];如果count != 0,此时,若cand == nums[i],count += 1,否则count -= 1。遍历结束,cand即为结果,其实对于一般情况,我们需要判断count是否大于0,如果大于0,则说明此时的cand可能出现超过一半,此时再遍历一遍数组来判断才行。
def majorityElement(self, nums):
count = 1
cand = nums[0]
for i in range(1, len(nums)):
if count !=0:
if cand != nums[i]:
count -= 1
else:
count += 1
else:
cand = nums[i]
count += 1
return cand
*Problem 172:Factorial Trailing Zeroes
给定一个正整数n,返回n!末尾的0的个数。
解题思路:一个因子5和一个因子2相乘得到一个0,但是2的个数远远大于5,因此我们只需考虑因子5的个数,并且25会有两个0,125会有3个0,以此类推。n/5的个数即1-n中包含一次因子5的个数,譬如n=25,则分别有5,10,15,20,25共5个5,由于25包含两个5,因此要递归处理n / 5。
def trailingZeroes(self, n):
return 0 if n == 0 else n / 5 + self.trailingZeroes(n / 5)
Problem 175: Combine Two Tables
一个数据库的题,让你写个SQL,从两个表中选择多个字段,一个简单的LEFT JOIN操作。
Problem 176:Second Highest Salary
一个数据库的题,写一个SQL,从Salary表中返回第二高的工资,如果不存在第二高,则返回NULL。
select distinct max(Salary)
from Employee
where Salary < (select max(Salary) from Employee)
or
select distinct Salary
from Employee
ordered by Salary desc limit 1 offset 1
Problem 181:Employees Earning More Than Their Managers
给定一个表,选择Employee工资大于Manager工资的员工名字。
+—-+——-+——–+———–+
| Id | Name | Salary | ManagerId |
+—-+——-+——–+———–+
| 1 | Joe | 70000 | 3 |
| 2 | Henry | 80000 | 4 |
| 3 | Sam | 60000 | NULL |
| 4 | Max | 90000 | NULL |
+—-+——-+——–+———–+
返回
+———-+
| Employee |
+———-+
| Joe |
+———-+
select E.Name Employee
from Employee as E,Employee as m
where E.ManagerId = M.Id and E.Salary > M.Salary
或者用join也可以实现
Problem 182: Duplicate Emails
从一个表中选出重复的email。
select email
from Person
group by email
having count(*) > 1
Problem 183:Customers Who Never Order
给定一个Customers表,从中选出没有在Orders有记录的用户name。
select A.Name Customers
from Customers A
where A.Id not in (select B.CustomerId from Orders B)
Problem 189: Rotate Array
给定一个数组,长度为n,将其从右往左的k个数移到数组最前端,如[1,2,3,4,5,6,7],n=7,k=3,结果为[5,6,7,1,2,3,4]。
解题思路:题目不难,第一个点就是,当k>n时,要循环用k-n直至k
Problem 190: Reverse Bits
给定一个10进制数,将其二进制(32位)表示的字符串反转,返回反转后对应的十进制的数字。譬如43261596 (00000010100101000001111010011100), 返回 964176192 (00111001011110000010100101000000)。
解题思路:需要注意,高位不足的要补1,常规解法即将其转换成2进制字符串,翻转后转换为10进制,也有一些直接进行位操作的算法,很优雅:
n = (n >> 16) | (n << 16);
n = ((n & 0xff00ff00) >> 8) | ((n & 0x00ff00ff) << 8);
n = ((n & 0xf0f0f0f0) >> 4) | ((n & 0x0f0f0f0f) << 4);
n = ((n & 0xcccccccc) >> 2) | ((n & 0x33333333) << 2);
n = ((n & 0xaaaaaaaa) >> 1) | ((n & 0x55555555) << 1);
return n;
for 8 bit binary number abcdefgh, the process is as follow:
abcdefgh -> efghabcd -> ghefcdab -> hgfedcba
Problem 191:Number of 1 Bits
给定一个10进制数,返回其2进制中1的个数。
解题思路:很老的一个题,也是很经典的一个题,使用位运算能在 O(l) ,l是该数据类型的二进制个数,考虑5=101,5 & 5-1= 100,可以发现,我们让n与n-1做&运算,则会减掉末尾的一个1,此时,计数加1,如此循环直至结果为0。
def hammingWeight(self, n):
count = 0
while n != 0:
count += 1
n = n & (n-1)
return count
Problem 193:Valid Phone Numbers
给定一个数组,数组的每一项都是一串数组加符号,返回合法的电话号码,合法是指形如:xxx-xxx-xxxx或者(xxx)xxx-xxxx,x为任意数字。
解题思路:正则表达式匹配:[0-9]{3}-|([0-9]{3})[0-9]{3}-[0-9]{4}
Problem 196:Delete Duplicate Emails
给定一个表Person,有两个字段,Id和Email,删除其中重复的Id比较大的Email。
delete A
from Person A,Person B
where A.Email = B.Email and A.Id > B.Id
Problem 197:Rising Temperature
给定一个表Weather,共三个字段:Id,Date,Temperature,找出所有温度比前一天高的Id。
select A.Id
from Weather A,Weather B
where DATEDIFF(A.Date, B.Date) = 1 and A.Temperature > B.Temperature
Problem 198:House Robber
假设你是一个强盗,要去盗取一个街区的房子,但是统一晚上不能盗任意两个相邻的房子,问如何才能使得一晚上的收益最大?给定一个非负数组代表每个房子的价值,试给出最大收益。
解题思路:找出递推关系,当前到第k个房子的最大收益f(k)要么为截至第k-1个房子的收益f(k-1),即不盗取当前房间;要么等于第k-2个房子的收益加上当前房子的收益。
def rob(self, nums):
l = n = 0
for i in nums:
l,n = n, max(l + i, n)
return n
Problem 202: Happy Number
如果一个正整数,循环执行:用各个位上的数字平方和来代替原来的数字,如果最终结果为1,则为happy number。
Example: 19 is a happy number
解题思路:常规解法,用一个set记录当前出现过的值,如果某个值重复出现,则可以说明该迭代进行了一轮完整的迭代还没有出现1,故返回False,一旦出现了1,则之后所有的数字必然为1,因此此时收敛到1,返回True。注:考虑环形链表的检测问题,我们也可以用“快慢指针”,一个一次运行两次上述步骤,一个运行一次,当两者相等时,判断值是否为1即可。注:在一轮完整的迭代中不可能出现两个相同的值。
Problem 203:Remove Linked List Elements
给定一个链表,删除指定元素的节点并返回修改后的链表的头结点。
解题思路:解题思路很直接,但是关于一些情况的处理要做到位:如果从头开始的若干个元素都等于val,需要单独处理;对于链表中的部分,需要维护两个相邻的指针,一旦t.next==val,修改指针。
def removeElements(self, head, val):
while head and head.val == val:
head = head.next
t = head
while t and t.next:
if t.next.val == val:
t.next = t.next.next
else:
t = t.next
return head
Problem 204: Count Primes
给定一个正整数n,返回小于n的数中,质数的个数。
解题思路:按照定义的方法不多说,复杂度达不到要求,我们这样做,在2~n的所有数中,我们先移除所有2的倍数,然后移除所有3的倍数,…,一直移除所有n/2的倍数,剩下的就都是质数。
def countPrimes(self, n):
p = [False]*n
count = 0
for i in range(2,n/2+1):
if p[i] == False:
count += 1
j = 2
while i*j < n:
p[i*j] = True
j += 1
return count
Problem 205:Isomorphic Strings
给定两个字符串,判断其中一个能不能通过同一个映射得到另外一个字符串,同一个映射是指,同一个字母要么映射为自身,要么映射为某一个固定的字母。譬如:add和egg返回True,但是aba和baa就不行,因为比较第一个位置a映射为b了,之后虽然第三个位置相等,但是因为只有a有映射,因此必须采用该映射。
解题思路:首先,如果字符串长度不同或者包含的不同的字母个数不同,直接返回False;否则,我们用一个字典来维持该映射。
def isIsomorphic(self, s, t):
if len(s) != len(t) or len(set(s)) != len(set(t)):
return False
dic = {}
for i in range(len(s)):
if s[i] not in dic.keys():
dic[s[i]] = t[i]
else:
if dic[s[i]] != t[i]:
return False
return True
Problem 206:Reverse Linked List
给定一个链表,将其反转,一般要求就地反转。
解题思路:递归或者迭代。这里给出迭代版本
def reverseList(self, head):
pre = None
while head:
cur = head
head = head.next
cur.next = pre
pre = cur
return pre
Problem 217:Contains Duplicate
判断一个数组中每个元素是否有重复出现,若重复,返回True,否则返回False。
解题思路:排序后遍历,如果相邻两个元素相等,返回False;或者用set,比较list和对应set的长度,若不相等,则有重复元素。
Problem 219:Contains Duplicate II
给定一个数组和一个k,问,该数组中是否存在不同的索引i,j,使得nums[i]==nums[j]并且abs(i-j)<=k。
解题思路:遍历数组,用一个字典维护val—>key的映射,如果当前值在字典中且差值小于k,返回True。PS:对于一个字典dic,使用if i in dic和if i in dic.keys()效果是一样的,但是前者效率要高很多!!!!!!!!!!。之前用后者,一直超时。
def containsNearbyDuplicate(self, nums, k):
dic = {}
for i in range(len(nums)):
if nums[i] in dic and i - dic[nums[i]] <= k:
return True
dic[nums[i]] = i
return False
至此,前220题(1/3)的easy题目结束,下一步完成该部分的medium题。