• 第15周Leetcode记录


    12.22 71. 两地调度

    公司计划面试 2N 人。第 i 人飞往 A 市的费用为costs[i][0],飞往 B 市的费用为 costs[i][1]。

    返回将每个人都飞到某座城市的最低费用,要求每个城市都有 N 人抵达。

    输入:[[10,20],[30,200],[400,50],[30,20]]
    输出:110
    解释:
    第一个人去 A 市,费用为 10。
    第二个人去 A 市,费用为 30。
    第三个人去 B 市,费用为 50。
    第四个人去 B 市,费用为 20。
    
    最低总费用为 10 + 30 + 50 + 20 = 110,每个城市都有一半的人在面试。
    

    最优解思路

    公司首先将这 2N 个人全都安排飞往 BB 市,再选出 N 个人改变它们的行程,让他们飞往 A 市。如果选择改变一个人的行程,那么公司将会额外付出 price_A - price_B 的费用,这个费用可正可负。因此最优的方案是,选出 price_A - price_B 最小的 NN 个人,让他们飞往 A 市,其余人飞往 B 市。

    最优解

    class Solution:
        def twoCitySchedCost(self, costs: List[List[int]]) -> int:
            # Sort by a gain which company has 
            # by sending a person to city A and not to city B
            costs.sort(key = lambda x : x[0] - x[1])
            
            total = 0
            n = len(costs) // 2
            # To optimize the company expenses,
            # send the first n persons to the city A
            # and the others to the city B
            for i in range(n):
                total += costs[i][0] + costs[i + n][1]
            return total
    

    12.23 72. 最长单词

    给定一组单词words,编写一个程序,找出其中的最长单词,且该单词由这组单词中的其他单词组合而成。若有多个长度相同的结果,返回其中字典序最小的一项,若没有符合要求的单词则返回空字符串。

    输入: ["cat","banana","dog","nana","walk","walker","dogwalker"]
    输出: "dogwalker"
    解释: "dogwalker"可由"dog"和"walker"组成。
    

    最优解思路

    先把字符串数组排序,字符串长的在前面,相同长度的字典序小的在前面,排好序后加入到set里判断是否包含,从第一个字符串开始判断,看是否由其它字符串组成,这里可以用递归
    递归出口: 如果字符串长的长度为0,说明遍历完了,之前的都满足条件,返回true
    递归操作: 遍历字符串的第0个位置开始,判断set里是否有,如果0到i的字符串正好包含在set里,下次从i+1的位置开始判断,直到遍历完了,字符串长度为0,没找到则返回false

    最优解

    class Solution {
        public String longestWord(String[] words) {
            Arrays.sort(words,(o1,o2)->{
                if(o1.length() == o2.length())
                    return o1.compareTo(o2);
                else{
                    return Integer.compare(o2.length(),o1.length());
                }
            });
    
            Set<String> set = new HashSet<>(Arrays.asList(words));
            for(String word : words){
                set.remove(word);
                if(find(set,word))
                     return word;
            }
            return "";
        }
    
        public boolean find(Set<String> set, String word){
            if(word.length() == 0)
                return true;
            for(int i = 0; i < word.length(); i++){
                if(set.contains(word.substring(0,i+1)) && find(set,word.substring(i+1)))
                    return true;
            }
            return false;
        }
    }
    

    12.26 73. 二叉搜索树迭代器

    实现一个二叉搜索树迭代器。你将使用二叉搜索树的根节点初始化迭代器。

    调用 next() 将返回二叉搜索树中的下一个最小的数。

    img

    BSTIterator iterator = new BSTIterator(root);
    iterator.next();    // 返回 3
    iterator.next();    // 返回 7
    iterator.hasNext(); // 返回 true
    iterator.next();    // 返回 9
    iterator.hasNext(); // 返回 true
    iterator.next();    // 返回 15
    iterator.hasNext(); // 返回 true
    iterator.next();    // 返回 20
    iterator.hasNext(); // 返回 false
    

    思路

    中序遍历树,各节点的值加入列表,pop。

    最优解

    class BSTIterator:
    
        def __init__(self, root: TreeNode):
            
            # Array containing all the nodes in the sorted order
            self.nodes_sorted = []
            
            # Pointer to the next smallest element in the BST
            self.index = -1
            
            # Call to flatten the input binary search tree
            self._inorder(root)
            
        def _inorder(self, root):
            if not root:
                return
            self._inorder(root.left)
            self.nodes_sorted.append(root.val)
            self._inorder(root.right)
    
        def next(self) -> int:
            """
            @return the next smallest number
            """
            self.index += 1
            return self.nodes_sorted[self.index]
    
        def hasNext(self) -> bool:
            """
            @return whether we have a next smallest number
            """
            return self.index + 1 < len(self.nodes_sorted)
    

    12.26 74. 买卖股票的最佳时机

    给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。

    你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

    返回获得利润的最大值。

    注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

    输入: prices = [1, 3, 2, 8, 4, 9], fee = 2
    输出: 8
    解释: 能够达到的最大利润:  
    在此处买入 prices[0] = 1
    在此处卖出 prices[3] = 8
    在此处买入 prices[4] = 4
    在此处卖出 prices[5] = 9
    总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8.
    

    最优解思路

    解一:动态规划

    考虑 dp[i][0]的转移方程,如果这一天交易完后手里没有股票,那么可能的转移状态为前一天已经没有股票,即 dp[i-1][0],或者前一天结束的时候手里持有一支股票,即 dp[i-1][1],这时候我们要将其卖出,并获得prices[i] 的收益,但需要支付 fee 的手续费。因此为了收益最大化,我们列出如下的转移方程:

    dp[i][0]=max{dp[i−1][0],dp[i−1][1]+prices[i]−fee}
    

    再来按照同样的方式考虑 dp[i][1] 按状态转移,那么可能的转移状态为前一天已经持有一支股票,即 dp[i-1]][1],或者前一天结束时还没有股票,即dp[i-1]][0] ,这时候我们要将其买入,并减少prices[i] 的收益。可以列出如下的转移方程:

    dp[i][1]=max{dp[i−1][1],dp[i−1][0]−prices[i]}
    

    对于初始状态,根据状态定义我们可以知道第 0 天交易结束的时候有dp[0][0]=0 以及dp[0][1]=-price[0].

    class Solution:
        def maxProfit(self, prices: List[int], fee: int) -> int:
            n = len(prices)
            dp = [[0, -prices[0]]] + [[0, 0] for _ in range(n - 1)]
            for i in range(1, n):
                dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee)
                dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i])
            return dp[n - 1][0]
    
    解二:贪心算法

    当我们卖出一支股票时,我们就立即获得了以相同价格并且免除手续费买入一支股票的权利

    class Solution:
        def maxProfit(self, prices: List[int], fee: int) -> int:
            n = len(prices)
            buy = prices[0] + fee
            profit = 0
            for i in range(1, n):
                if prices[i] + fee < buy:
                    buy = prices[i] + fee
                elif prices[i] > buy:
                    profit += prices[i] - buy
                    buy = prices[i]
            return profit
    

    12.28 75. LRU缓存机制

    运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
    实现 LRUCache 类:

    LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
    int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
    void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

    思路

    LRU缓存机制是通过hash表辅以双向链表实现的。

    • 双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的。
    • 哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双向链表中的位置。

    最优解

    class DLinkedNode:
        def __init__(self, key=0, value=0):
            self.key = key
            self.value = value
            self.prev = None
            self.next = None
    
    
    class LRUCache:
    
        def __init__(self, capacity: int):
            self.cache = dict()
            # 使用伪头部和伪尾部节点    
            self.head = DLinkedNode()
            self.tail = DLinkedNode()
            self.head.next = self.tail
            self.tail.prev = self.head
            self.capacity = capacity
            self.size = 0
    
        def get(self, key: int) -> int:
            if key not in self.cache:
                return -1
            # 如果 key 存在,先通过哈希表定位,再移到头部
            node = self.cache[key]
            self.moveToHead(node)
            return node.value
    
        def put(self, key: int, value: int) -> None:
            if key not in self.cache:
                # 如果 key 不存在,创建一个新的节点
                node = DLinkedNode(key, value)
                # 添加进哈希表
                self.cache[key] = node
                # 添加至双向链表的头部
                self.addToHead(node)
                self.size += 1
                if self.size > self.capacity:
                    # 如果超出容量,删除双向链表的尾部节点
                    removed = self.removeTail()
                    # 删除哈希表中对应的项
                    self.cache.pop(removed.key)
                    self.size -= 1
            else:
                # 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
                node = self.cache[key]
                node.value = value
                self.moveToHead(node)
        
        def addToHead(self, node):
            node.prev = self.head
            node.next = self.head.next
            self.head.next.prev = node
            self.head.next = node
        
        def removeNode(self, node):
            node.prev.next = node.next
            node.next.prev = node.prev
    
        def moveToHead(self, node):
            self.removeNode(node)
            self.addToHead(node)
    
        def removeTail(self):
            node = self.tail.prev
            self.removeNode(node)
            return node
    

    最优解总结

    hash表key是存出键,value是链表的索引,链表的值时value。这样查询,插入复杂度都是o1

    get操作,判断是否存在,不存在返回-1,存在把对应的链表索引换到头部。

    put操作,若存在和get操作类似,不存在则创建一个新的链表节点,放到头部再判断长度,超过规定长度就会从尾部去除。

  • 相关阅读:
    关于求 p_i != i and p_i != i+1 的方案数的思考过程
    poj 3041 Asteroids 二分图最小覆盖点
    poj 1325 Machine Schedule 最小顶点覆盖
    poj 1011 Sticks 减枝搜索
    poj 1469 COURSES 最大匹配
    zoj 1516 Uncle Tom's Inherited Land 最大独立边集合(最大匹配)
    Path Cover (路径覆盖)
    hdu 3530 SubSequence TwoPoint单调队列维护最值
    zoj 1654 Place the Rebots 最大独立集转换成二分图最大独立边(最大匹配)
    poj 1466 Girls and Boys 二分图最大独立子集
  • 原文地址:https://www.cnblogs.com/jimmyhe/p/14226924.html
Copyright © 2020-2023  润新知