零基础-力扣100题从易到难详解(持续更新1-10题)
1.两数之和
思路:
题目的意思是在数组里找到两个不同位置的数(不存在多解),使它们相加等于target,返回它们的下标。
我们要做的就是遍历每个数,看看哪两个数能加成target,脑海里飘过三个方法:第一个是最复杂的暴力枚举写双层循环,两两相加,这里显然时间复杂度是下面说的“底线”O(n^2);第二个是哈希表,一边扫一边把值-下标存字典,查差值是否存在,时间复杂度为O(n);第三种就是排序加双指针夹逼,但是会打乱原下标,需要额外的映射,属于比较炫技。
1.1暴力枚举(一些类知识)
class Solution(object):def twoSum(self,nums,target):#类内部声明的函数叫方法 python规定实例方法的第一个形参必须叫self 这个self代表将来调用该方法的那个对象本身,python解释器会自动把当前对象传进来
#在_init_里,self把值挂在当前这个对象自己身上,使之成为对象的一个属性,让后面任何地方都能通过这个对象访问它,要是没有赋值变量只能成为局部变量""":type nums: List[int]:type target: int:rtype: List[int]"""n = len(nums)#博主在这里做错了,tab对齐到def下面了for i in range(n-1):for j in range(i+1,n):if nums[i] + nums[j] == target:return[i,j]return []#return这里要对应n不是对应def
1.2Hash表
class Solution(object):def twoSum(self,nums,target):""":type nums: List[int]:type target: int:rtype: List[int]"""seen = {} #key : 存的是已经扫过的某个数,value:存的是这个数再nums的下标for idx,num in enumerate(nums):#用 enumerate 遍历 nums,每次拿到idx当前数字的下标,num当前数字的值,在这里相当于一个拆包语法need = target - numif need in seen:return [seen[need],idx]seen[num] = idx#如果 need 没找到,就把当前 num 记录到 seen 里,以备后面的数字来“配对”,这里num作为key,idx作为valuereturn []
1.3双指针(不建议)
class Solution(object):def twoSum(self, nums, target):""":type nums: List[int]:type target: int:rtype: List[int]"""# 把 (值, 原下标) 二元组保存下来再排序arr = [(num, i) for i, num in enumerate(nums)]arr.sort(key=lambda x: x[0]) # 按值升序left, right = 0, len(arr) - 1while left < right:s = arr[left][0] + arr[right][0]if s == target:return sorted([arr[left][1], arr[right][1]])elif s < target:left += 1else:right -= 1return []
2.有效的括号
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 :
输入:s = "([])"
输出:true
示例 :
输入:s = "([)]"
输出:false
思路:在这道题中遍历或许不再是一个好方法,因为循环是不知道顺序的,对于这种有效且最近匹配的问题我们应该想到LIFO——last-in-first-out,因为后出现的左括号应该先被检查“闭合”。那我们思路就很明确了,当你读到)右括号的时候必须和最靠近它的上一个未匹配的(左括号匹配,再去看更外层的括号。
在操作上我们知道push(x)
—— 把 x 放到最上面;pop()
—— 拿走最上面的;top()
—— 看一眼最上面是谁;is_empty()
—— 栈里还有没有东西。对应在python里面就是用 list.append()
当 push
,list.pop()
当 pop
,list[-1]
当 top
,not list
当 is_empty
。
基础的操作就是遇到左括号把它push进栈等待匹配,遇到右括号就看栈顶top是不是对应左括号,是就可以pop完成匹配,否则非法输出错误,字符串扫完后栈为空则意味所有括号都匹配成功,要是不为空则说明左括号没有闭合。
记住:“需要最近配对、或撤销最近操作”的问题,十有八九都能用栈。
2.1经典栈(最优方法,代码好理解复杂度也不错)
遇到左括号就入栈,遇到右括号就看栈顶是否匹配,时间复杂度 O(n),空间复杂度 O(n)。
class Solution(object):def isValid(self, s):""":type s: str:rtype: bool"""pair = {')':'(' , ']': '[', '}': '{'}#从左到右的查找,遇到右括号去匹配,所以写一个右括号匹配的字典stack = [] #创建栈for ch in s :if ch in pair:#右括号# 栈空或栈顶不匹配即为非法if not stack or stack[-1] != pair[ch]:return Falsestack.pop()#匹配成功弹出else:#左括号 这里记得写冒号,题主不太熟练写错了stack.append(ch)return not stack
2.2栈+放哨兵(减少一次if判断 但是可读性比较差)
class Solution(object):def isValid(self, s):""":type s: str:rtype: bool"""pair = {')': '(', ']': '[', '}': '{'}stack = ['#'] # 不同之处在这里,放哨兵,保证 stack 永远不为空for ch in s:if ch in pair:if stack[-1] != pair[ch]:return Falsestack.pop()else:stack.append(ch)return len(stack) == 1 #只剩哨兵
2.3字符串替换(取巧的办法,面试慎用哦,而且每次扫描整串可能复杂度退到O(n^2))
通过不断把成对括号替换成空串,若最终能删光则合法:
class Solution(object):def isValid(self, s):""":type s: str:rtype: bool"""while '()' in s or '[]' in s or '{}'in s:s = s.replace('()','').replace('[]','').replace('{}','')return s == ''
很凑巧的是最近匹配的LIFO顺序,replace的扫描方向也可以满足这一点:
str.replace(old, new)
的作用从左到右一次性扫描整个字符串,把所有与 old
不重叠 的子串替换成 new。