Leetcode腾讯算法题 -- 刷 出题指数为3颗星以上的题目 --第四部分

问题一:LT146. LRU 缓存机制

先了解什么是LRU

LRU的英文全称是Least Recently Used,也即最不经常使用。我们看着好像挺迷糊的,其实这个含义要结合缓存一起使用。对于工程而言,缓存是非常非常重要的机制,尤其是在当下的互联网应用环境当中,起到的作用非常重要。为了便于大家更好地理解,我们从缓存的机制开始说起。

我们很容易发现,对于一些经常打开淘宝的用户来说,其实没有必要每一次都把完整的链路走一遍。我们大可以把之前展示的结果存储在内存里,下一次再遇到同样请求的时候,直接从内存当中读取并且返回就可以了。这样可以节省大量的时间以及计算资源,比如在大促的时候,就可以把计算资源节省下来用在更加需要的地方。

缓存虽然好用,但是也不是万能的,因为内存是很贵的,我们不可能把所有数据都存在内存里。内存里只能放一些我们认为比较高价值的数据,在这种情况下,计算科学家们想出了种种策略来调度缓存,保持缓存当中数据的高价值。LRU就是其中一种比较常用的策略。

LRU的意思是最长不经常使用,也可以理解成最久没有使用。在这种策略下我们用最近一次使用的时间来衡量一块内存的价值,越久之前使用的价值也就越低,最近刚刚使用过的,后面接着会用到的概率也就越大,那么自然也就价值越高。

显然我们不能通过数组来实现这个缓存。因为数组的查询速度是很慢的
其次我们用HashMap好像也不行,因为虽然查询的速度可以做到 ,但是我们没办法做到更新最近使用的时间,并且快速找出最远更新的数据。

首先HashMap是一定要用的,因为只有HashMap才可以做到 时间内的读写,其他的数据结构几乎都不可行。但是只有HashMap解决不了更新以及淘汰的问题,必须要配合其他数据结构进行。这个数据结构需要能够做到快速地插入和删除,其实我这么一说已经很明显了,只有一个数据结构可以做到,就是链表。

链表有一个问题是我们想要查询链表当中的某一个节点需要O(N)的时间,这也是我们无法接受的。但这个问题并非无法解决,实际上解决也很简单,我们只需要把链表当中的节点作为HashMap中的value进行储存即可

所以总结来说,对于python的数据结构,就是用哈希表和链表来做。

哈希表+链表实现缓冲

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
(1)获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
(2)写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

cache = LRUCache(2)#最大空间是2 

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // 返回  1
cache.put(3, 3);    // 该操作会使得密钥 2 作废
cache.get(2);       // 返回 -1 (未找到)
cache.put(4, 4);    // 该操作会使得密钥 1 作废
cache.get(1);       // 返回 -1 (未找到)
cache.get(3);       // 返回  3
cache.get(4);       // 返回  4

这里结合链表和哈希表来做,把最常用的节点放在后面,如果有put数据进来,则删掉链表中的第一个节点。
查询的话 就用哈希表来做 时间复杂度为O(1)
【哈希表的一个重要特性就是查询的时间复杂度为O(1)】

其中一个面试题 在考察 哈希表的特性: 问了两次
给定两个数列,判断两个数列中元素的差异。
假设两个数列的长度为n,使用for-loop逐个比较,时间复杂度是O(n^2).
使用哈希表这种数据结构,先遍历一个数列建立一个哈希表,时间复杂度O(n); 然后查询第二列中的每个元素是否在表中,时间复杂度为O(n),所以总时间为O(2n)。效率提高很多。

#创建ListNode的类
class ListNode:
    def __init__(self,key = None,value = None):
        self.key = key 
        self.value = value 
        self.prev = None 
        self.next = None 

class LRUCache:
    def __init__(self, capacity: int):
        self.capacity = capacity
        self.hashtable = {}
        self.head = ListNode()
        self.tail = ListNode()
        #初始化链表为 head <-> tail
        self.head.next = self.tail
        self.tail.prev = self.head 

    #把node放在链表的末尾 
    def moveNode2Tail(self,key):
        node = self.hashtable[key]
        #首先第一做的是 删掉链表的这个node 即 让node的prev和next互相指向
        node.prev.next = node.next 
        node.next.prev = node.prev 

        #第二要做的是把node放链表的末尾
        #用上tail的定义 
        node.prev = self.tail.prev
        node.next = self.tail
        self.tail.prev.next  = node
        self.tail.prev = node 

    def get(self, key: int) -> int:
        if key in self.hashtable:
            self.moveNode2Tail(key)
        ans = self.hashtable.get(key,-1)
        if ans == -1:return ans 
        else: return ans.value

    def put(self, key: int, value: int) -> None:
        #如果插入的这个数key 已经存在哈希表的话,直接更新到末尾
        if key in self.hashtable:
            self.hashtable[key].value = value 
            self.moveNode2Tail(key)
        else:
            if len(self.hashtable)==self.capacity:
                #需要pop走前面的 头节点
                #记住链表是 head - 数字 --tail -- head 
                #这里的操作时删掉一个node
                self.hashtable.pop(self.head.next.key)
                self.head.next = self.head.next.next 
                self.head.next.prev = self.head
            new = ListNode(key,value)
            self.hashtable[key] = new
            #放在末尾
            new.prev = self.tail.prev
            new.next = self.tail
            self.tail.prev.next = new
            self.tail.prev = new 	

这里需要熟悉的操作是 链表的删除某节点 链表的插入 以及哈希表的应用。

问题二:LT394. 字符串解码

给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入

输入:s = "3[a]2[bc]"
输出:"aaabcbc"
输入:s = "3[a2[c]]"
输出:"accaccacc"
输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"
输入:s = "abc3[cd]xyz"
输出:"abccdcdcdxyz"
class Solution:
    def decodeString(self, s: str) -> str:
        stack = []
        num = 0
        ans = ''
        for c in s:
            if c == '[':
                stack.append([num, ans])
                num = 0 
                ans = ''
            elif c == ']':
                n , last_s = stack.pop()
                ans = last_s + n*ans 
            elif '0' <= c <='9':
                num =  num*10 + int(c)
                #这是因为100 字符 输入的是 1 -- 0 -- 0 不是直接输入100数字
                #"100[leetcode]"
            else:
                ans += c 
        return ans 

题目三:LT:148. 排序链表

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

输入:head = [4,2,1,3]
输出:[1,2,3,4]
输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]
输入:head = []
输出:[]

思路是:
利用快慢指针来找到链表的中点,然后断开。
快慢指针返回的是 奇数的是刚好中点,偶数则返回左端中点。
然后使得slow.next = None 来断开
不断重复直到不能再分 再用双指针来合并

class Solution:
    def getMid(self,node):
        if not node: return node 
        fast= slow = node 
        while fast.next and fast.next.next:
            slow = slow.next 
            fast = fast.next.next
        return slow 

    def merge(self,p,q):
        if not p: return q
        if not q: return p 
        if p.val < q.val: 
            p.next = self.merge(p.next, q)
            return p 
        else: 
            q.next = self.merge(p,q.next)
            return q 
            
    def sortList(self, head: ListNode) -> ListNode:
        if not head or not head.next: return head 
        mid = self.getMid(head)
        left = head 
        right = mid.next 
        mid.next = None 
        return self.merge(self.sortList(left), self.sortList(right))
  

其中的合并两个链表 还有一个便于理解的做法

def merge(p,q)
	tmp = ListNode(0)
	h = tmp
	while p and q:
		if p.val < q.val:
			h.next = p 
			p = p.next 
		else:
			h.next = q
			q = q.next 
	if p: h.next = p 
	if q: h.next = q 
	return tmp.next 	

题目四:23. 合并K个升序链表

给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def merge2(self, p, q):
        if not p: return q 
        if not q: return p 
        if p.val < q.val:
            p.next = self.merge2(p.next,q)
            return p 
        else:
            q.next = self.merge2(p,q.next)
            return q 

    def mergeKLists(self, lists: List[ListNode]) -> ListNode:
        #special case 
        if len(lists) == 0: return None 
        if len(lists) == 1:return  lists[0]
        if len(lists) ==2: return self.merge2(lists[0],lists[1])
        ans = self.merge2(lists[0],lists[1])
        N = len(lists)-2
        while N:
            ans = self.merge2(lists[-N],ans)
            N-=1 
        return ans 

超出时间限制,需要别的方法

方法二:并归法
并归法先分成一半一半的 最后合并

class Solution:
    def merge2(self, p, q):
        if not p: return q 
        if not q: return p 
        if p.val < q.val:
            p.next = self.merge2(p.next,q)
            return p 
        else:
            q.next = self.merge2(p,q.next)
            return q 
    def mergeKLists(self, lists: List[ListNode]) -> ListNode:
        #special case 
        if len(lists) == 0: return None 
        if len(lists) == 1:return  lists[0]
        n = len(lists)
        mid = n//2
        return self.merge2(self.mergeKLists(lists[:mid]),self.mergeKLists(lists[mid:]) )

题目五:气球游戏🎈

有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。
求所能获得硬币的最大数量

class Solution:
    def max_score(self,x,y,nums,dp):
        ans = 0 
        for k in range(x+1,y):
            left = dp[x][k]
            right = dp[k][y]
            ans = max(ans, left + nums[x]*nums[k]*nums[y]+right)
        dp[x][y] = ans 
    def maxCoins(self, nums: List[int]) -> int:
        nums = [1] + nums + [1]
        N = len(nums)
        dp = [[0]*(N) for _ in range(N)] 
        #j作为一个缩小区间的作用  
        for j in range(2,N):
            for i in range(0,N-j):
                self.max_score(i,i+j,nums,dp)              
        return dp[0][N-1]

题目六:LT:486. 预测赢家

给定一个表示分数的非负整数数组。 玩家 1 从数组任意一端拿取一个分数,随后玩家 2 继续从剩余数组任意一端拿取分数,然后玩家 1 拿,…… 。每次一个玩家只能拿取一个分数,分数被拿取之后不再可取。直到没有剩余分数可取时游戏结束。最终获得分数总和最多的玩家获胜。
给定一个表示分数的数组,预测玩家1是否会成为赢家。你可以假设每个玩家的玩法都会使他的分数最大化。

输入:[1, 5, 2]
输出:False
解释:一开始,玩家1可以从12中进行选择。
如果他选择 2(或者 1 ),那么玩家 2 可以从 1(或者 2 )和 5 中进行选择。如果玩家 2 选择了 5 ,那么玩家 1 则只剩下 1(或者 2 )可选。
所以,玩家 1 的最终分数为 1 + 2 = 3,而玩家 25 。
因此,玩家 1 永远不会成为赢家,返回 False 。

输入:[1, 5, 233, 7]
输出:True
解释:玩家 1 一开始选择 1 。然后玩家 2 必须从 57 中进行选择。无论玩家 2 选择了哪个,玩家 1 都可以选择 233 。
     最终,玩家 1234 分)比玩家 212 分)获得更多的分数,所以返回 True,表示玩家 1 可以成为赢家。
class Solution:
    def PredictTheWinner(self, nums: List[int]) -> bool:
        N = len(nums)
        dp = [[0]*N for _ in range(N)]
        for i in range(N):
            dp[i][i] = nums[i]
               
        for i in range(N-2,-1,-1):
            for j in range(i+1, N):
                dp[i][j] = max((nums[i] - dp[i+1][j]), (nums[j]-dp[i][j-1]))
        return dp[0][N-1]>=0

题目七: LT:887. 鸡蛋掉落

给你 k 枚相同的鸡蛋,并可以使用一栋从第 1 层到第 n 层共有 n 层楼的建筑。

已知存在楼层 f ,满足 0 <= f <= n ,任何从 高于 f 的楼层落下的鸡蛋都会碎,从 f 楼层或比它低的楼层落下的鸡蛋都不会破。
每次操作,你可以取一枚没有碎的鸡蛋并把它从任一楼层 x 扔下(满足 1 <= x <= n)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。
请你计算并返回要确定 f 确切的值 的 最小操作次数 是多少?

??? continue

def superEgg(k,N):
	dp=[[0]*(k+1) for _ in range(N+1)]
	m = 0 
	while dp[m][k] < N:
		m += 1 
		for i in range(1,k+1):
			dp[m][i] = dp[m-1][i-1] + 1 + dp[m-1][i]
	return m 
		

题目八: LT:678. 有效的括号字符串

给定一个只包含三种字符的字符串:( ,) 和 *,写一个函数来检验这个字符串是否为有效字符串。有效字符串具有如下规则:
任何左括号 ( 必须有相应的右括号 )。
任何右括号 ) 必须有相应的左括号 ( 。
左括号 ( 必须在对应的右括号之前 )。*可以被视为单个右括号 ) ,或单个左括号 ( ,或一个空字符串。
一个空字符串也被视为有效字符串。

输入: "()"
输出: True
输入: "(*)"
输出: True
输入: "(*))"
输出: True
class Solution:
    def checkValidString(self, s: str) -> bool:
        #dp[0]代表着需要的最少右括号数量  
        #dp[1]代表着需要的最多右括号数量
        dp = [0,0]
        for c in s:
            if c =='(':
                dp[0]+=1
                dp[1] +=1 
            elif c =='*':
                if dp[0]: dp[0]-=1
                dp[1] += 1  
            else:
                if dp[0]:dp[0] -=1 
                dp[1] -= 1
                if dp[1] <0:return False 
        return dp[0]==0
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jianafeng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值