肇鑫的技术博客

业精于勤,荒于嬉

“309. 最佳买卖股票时机含冷冻期”的解题思路

309. 最佳买卖股票时机含冷冻期

原题

给定一个整数数组,其中第* i* 个元素代表了第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

  • 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
  • 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

示例:

输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

解题思路1

尝试走出所有路径。利用递归和缓存结果的方式进行优化,并设置结束条件进行约束。可惜,不能通过所有测试,提示超时。

class Solution {
    var cachedResults = [[Int]:Int]()
    
    func maxProfit(_ prices: [Int]) -> Int {
        var dealProfit = 0
        var firstDealEndAt:Int? = nil
        
         // 买,卖,冻;买,卖,冻
        for buyIndex in 0..<prices.count {
            let sellIndexStartAt = buyIndex + 1
            guard sellIndexStartAt < prices.count else {
                return dealProfit
            }
            
            let buyPrice = prices[buyIndex]
            for sellIndex in sellIndexStartAt..<prices.count {
                if let dealEndAt = firstDealEndAt {
                    guard buyIndex < dealEndAt else {
                        break
                    }
                }
                
                let sellPrice = prices[sellIndex]
                if buyPrice < sellPrice {
                    if let endDealAt = firstDealEndAt {
                        if endDealAt > sellIndex {
                            firstDealEndAt = sellIndex
                        }
                    } else {
                        firstDealEndAt = sellIndex
                    }
                    
                    var profit = sellPrice - buyPrice
                    
                    let nextIndex = sellIndex + 2
                    
                    if nextIndex < prices.count {
                        let remainPrices = Array(prices[nextIndex...])
                        let remainProfit:Int = {
                            if let profit = cachedResults[remainPrices] {
                                return profit
                            }
                            
                            let profit = maxProfit(remainPrices)
                            cachedResults.updateValue(profit, forKey: remainPrices)
                            
                            return profit
                        }()
                        
                        profit += remainProfit
                    }
                    
                    dealProfit = max(dealProfit, profit)
                }
            }
        }

        return dealProfit
    }
}

解题思路2

思路2是同时计算每一步的状态。我们知道,针对每一个数据,它可能的状态有3个,即买、卖、等待。如图:

wvR4TN8.png-1

s0[i] = max(s0[i - 1], s2[i - 1]); // 冷却可能是原地等待,也可能是卖完之后强制冷却
s1[i] = max(s1[i - 1], s0[i - 1] - prices[i]); // Stay at s1, or buy from s0 原地等待,或者购买,如果购买,则肯定是从s0状态过来的。
s2[i] = s1[i - 1] + prices[i]; // 卖出获利

最终代码:

class Solution {
    func maxProfit(_ prices: [Int]) -> Int {
        guard !prices.isEmpty else {
            return 0
        }
        
        var s0 = Array<Int>(repeating: 0, count: prices.count)
        var s1 = Array<Int>(repeating: 0, count: prices.count)
        var s2 = Array<Int>(repeating: 0, count: prices.count)
        
        s0[0] = 0
        s1[0] = -prices[0]
        s2[0] = Int.min
        
        for index in 1..<prices.count {
            s0[index] = max(s0[index - 1], s2[index - 1])
            s1[index] = max(s1[index - 1], s0[index - 1] - prices[index])
            s2[index] = s1[index - 1] + prices[index]
        }

        return max(s0.last!, s2.last!) // 最后一步,要么是冷却状态,要么是卖出状态。不可能是买入状态
    }
}

参考资料:

"916. 单词子集"的解题思路

916. 单词子集

原题

我们给出两个单词数组 A 和 B。每个单词都是一串小写字母。

现在,如果 b 中的每个字母都出现在 a 中,包括重复出现的字母,那么称单词 b 是单词 a 的子集。 例如,“wrr” 是 “warrior” 的子集,但不是 “world” 的子集。

如果对 B 中的每一个单词 bb 都是 a 的子集,那么我们称 A 中的单词 a通用的

你可以按任意顺序以列表形式返回 A 中所有的通用单词。

示例 1:

**输入:**A = ["amazon","apple","facebook","google","leetcode"], B = ["e","o"]
输出:["facebook","google","leetcode"]

示例 2:

**输入:**A = ["amazon","apple","facebook","google","leetcode"], B = ["l","e"]
输出:["apple","google","leetcode"]

示例 3:

**输入:**A = ["amazon","apple","facebook","google","leetcode"], B = ["e","oo"]
输出:["facebook","google"]

示例 4:

**输入:**A = ["amazon","apple","facebook","google","leetcode"], B = ["lo","eo"]
输出:["google","leetcode"]

示例 5:

**输入:**A = ["amazon","apple","facebook","google","leetcode"], B = ["ec","oc","ceo"]
输出:["facebook","leetcode"]

提示:

  1. 1 <= A.length, B.length <= 10000
  2. 1 <= A[i].length, B[i].length <= 10
  3. A[i] 和 B[i] 只由小写字母组成。
  4. A[i] 中所有的单词都是独一无二的,也就是说不存在 i != j 使得 A[i] == A[j]

解题思路1

这道题的一般思路很简单。循环A,然后循环B,B中的字符串中的字符如果在A的字符串中存在,则移除掉该字符,然后循环下一个字符。代码如下:

class Solution {
    func wordSubsets(_ A: [String], _ B: [String]) -> [String] {
        let array = A.filter { (s) -> Bool in
            for b in B {
                var s = s
                
                for bCharacter in b {
                    if let index = s.firstIndex(of: bCharacter) {
                        s.remove(at: index)
                    } else {
                        return false
                    }
                }
            }
            
            return true
        }
        
        return array
    }
}

这个思路验证时,提示会超时。也就是性能不达标。

解题思路2

我们注意到示例3中,存在两个相同的字母”oo“。

示例 3:

**输入:**A = ["amazon","apple","facebook","google","leetcode"], B = ["e","oo"]
输出:["facebook","google"]

我的优化思路是,把不重复的字符单独优化,重复的仍然按照“思路1”的方式进行处理。代码如下:

class Solution {
    func wordSubsets(_ A: [String], _ B: [String]) -> [String] {
        var bSet = Set<Character>()
        var bArray = [String]()
        
        B.forEach {
            let set:Set<Character> = Set($0)
            if set.count == $0.count {  // no duplicated
                bSet = bSet.union(set)
            } else {
                bArray.append($0)
            }
        }
        
        let array = A.filter { (s) -> Bool in
            // bSet filter
            let aSet:Set<Character> = Set(s)
            guard aSet.isSuperset(of: bSet) else {
                return false
            }
            
            
            for b in bArray {
                var s = s
                
                for bCharacter in b {
                    if let index = s.firstIndex(of: bCharacter) {
                        s.remove(at: index)
                    } else {
                        return false
                    }
                }
            }
            
            return true
        }
        
        return array
    }
}

进行验证。还是性能不达标。

最终的解题思路

没办法,只好去翻评论。结果有人说,必须全部优化重复的字母,不然就一定会超时。所谓全部优化,就是把B中所有重复的字母计算出来。

原题的B等价于所有的字母都存在,且出现的次数大于等于B中的字符串中出现的那个字符的最多次数。

单独计算B的字母频率。

func characterFrequencies(in array:[String]) -> Dictionary<Character,Int> {
    var dictionary = Dictionary<Character,Int>()
    
    for s in array {
        var sDic = [Character:Int]()
        
        for c in s {
            if let f = sDic[c] {
                sDic[c] = f + 1
            } else {
                sDic[c] = 1
            }
        }
        
        sDic.forEach { (c, f) in
            if let frequency = dictionary[c] {
                dictionary[c] = max(frequency, f)
            } else {
                dictionary[c] = f
            }
        }
    }
    
    return dictionary
}

完整代码:

class Solution {
    func wordSubsets(_ A: [String], _ B: [String]) -> [String] {
        let dictionary = characterFrequencies(in: B)
        
        let array = A.filter { (s) -> Bool in
            for (c,f) in dictionary {
                var index = s.startIndex
                
                for _ in 1...f {
                    if index < s.endIndex, let firstIndex = s[index...].firstIndex(of: c) {
                        let nextIndex = s.index(after: firstIndex)
                        index = nextIndex
                    } else {
                        return false
                    }
                }
            }
            
            return true
        }
        
        
        return array
    }
    
    func characterFrequencies(in array:[String]) -> Dictionary<Character,Int> {
        var dictionary = Dictionary<Character,Int>()
        
        for s in array {
            var sDic = [Character:Int]()
            
            for c in s {
                if let f = sDic[c] {
                    sDic[c] = f + 1
                } else {
                    sDic[c] = 1
                }
            }
            
            sDic.forEach { (c, f) in
                if let frequency = dictionary[c] {
                    dictionary[c] = max(frequency, f)
                } else {
                    dictionary[c] = f
                }
            }
        }
        
        return dictionary
    }
}

小结

我们经常需要用到字典结构来挺高应用的性能。

"329. 矩阵中的最长递增路径"的解题思路

329. 矩阵中的最长递增路径。这道题没有官方的题解,写下我的思路。

原题

给定一个整数矩阵,找出最长递增路径的长度。

对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)。

示例 1:

输入: nums =
[
[9,9,4],
[6,6,8],
[2,1,1]
]
输出: 4
解释: 最长递增路径为 [1, 2, 6, 9]

示例 2:

输入: nums =
[
[3,4,5],
[3,2,6],
[2,2,1]
]
输出: 4
解释: 最长递增路径是 [3, 4, 5, 6]。注意不允许在对角线方向上移动。

思路

我的思路是先将所有元素按照从小到大排序。然后开始计算,规则如下:

  1. 周边的数字如果都大于当前数字,那个当前数字只能走一步,记角标1
  2. 如果周边的数字存在小于当前数字的,因为是从小到大开始计算的,那个较小的数字之前已经计算过了。因此该方向的步数为1+角标数字。当前数字的最终角标为这四个方向中的最大的。

其实也可以按照从大到小排序。

思路图

my_solution

最后,还要避免空集。

最终代码

class Solution {
    func longestIncreasingPath(_ matrix: [[Int]]) -> Int {
        guard !matrix.flatMap({$0}).isEmpty else{
            return 0
        }
        
        var longest = 1
        var allElements = Array<Item>()
        var allElementsDictionary = Dictionary<IndexPath, Int>()
        var allSteps = Dictionary<IndexPath,Int>()
        
        for (row, rowElements) in matrix.enumerated() {
            for (column, element) in rowElements.enumerated() {
                allElements.append(Solution.Item(value: element, indexPath: Solution.IndexPath(row: row, column: column)))
                allElementsDictionary[IndexPath(row: row, column: column)] = element
            }
        }
        
        allElements.sort { (currentItem, nextItem) -> Bool in
            return currentItem.value < nextItem.value
        }
        
        allElements.forEach {
            let row = $0.indexPath.row
            let column = $0.indexPath.column
            let value = $0.value
            
            var step = 1
            
            let upIndexPath = IndexPath(row: row - 1, column: column)
            if let currentStep = getStep(currentValue: allElementsDictionary[upIndexPath], currentStep: allSteps[upIndexPath], comparingWith: value) {
                
                step = max(currentStep + 1, step)
            }
            
            let leftIndexPath = IndexPath(row: row, column: column - 1)
            if let currentStep = getStep(currentValue: allElementsDictionary[leftIndexPath], currentStep: allSteps[leftIndexPath], comparingWith: value) {
                
                step = max(currentStep + 1, step)
            }
            
            let downIndexPath = IndexPath(row: row + 1, column: column)
            if let currentStep = getStep(currentValue: allElementsDictionary[downIndexPath], currentStep: allSteps[downIndexPath], comparingWith: value) {
                
                step = max(currentStep + 1, step)
            }
            
            let rightIndexPath = IndexPath(row: row, column: column + 1)
            if let currentStep = getStep(currentValue: allElementsDictionary[rightIndexPath], currentStep: allSteps[rightIndexPath], comparingWith: value) {
                
                step = max(currentStep + 1, step)
            }
            
            allSteps[IndexPath(row: row, column: column)] = step
            longest = max(step, longest)
        }
        
        return longest
    }
    
    func getStep(currentValue:Int?, currentStep:Int?, comparingWith value:Int) -> Int? {
        if let currentValue = currentValue, currentValue < value, let currentStep = currentStep {
            return currentStep
        }
        
        return nil
    }

    struct Item {
        let value:Int
        let indexPath:IndexPath
    }
    
    struct IndexPath:Hashable {
        let row:Int
        let column:Int
    }
}