实战碰到的算法题目

1,102 阅读5分钟

1. 小于n的最大数(字节真题)

题目要求:给定一个数n如23121;给定一组数字a如[2 4 9]求由a中元素组成的小于n的最大数

思路:来自这里

先按目标数N的位数从高位到低位找,如果在数组中能找到与某位数相等的数,则将其推进数组,如果没找到,则从数组剩余数里面挑其次,有其次就找其次,且从该位开始后面可以直接push数组中最大的数了;如果没有其次,那么说明需要回退上一位(pop),找到上一位的其次,循环直到res拿到结果,走 break 跳出循环。

func maxNumber(_ n: Int, _ set: [Int]) -> Int {
    var digits = [Int]()
    var n = n
    while n != 0 {
        digits.insert(n % 10, at: 0)
        n = n / 10
    }
    
    // 在set里面 优先找等于当前数字, 否则找次小的
    // 都不满足(set里没有比当前数 更小的数了)返回 -1
    func findSuitNum(_ n: Int) -> Int {
        for i in (0..<set.count).reversed() {
            if set[i] <= n { return set[i] }
        }
        return -1
    }
    
    var fixIndex = 0
    // 是否寻找次小数
    var findMax = true
    while fixIndex != digits.count {
        let num = digits[fixIndex]
        // 最后一位强制查询次小数
        if fixIndex == digits.count - 1 {
            findMax = false
        }
        let suitN = findSuitNum(findMax ? num : num - 1)
        findMax = true
        if suitN == -1 {
            // 没找到,则回退上一位 去找次小数
            fixIndex -= 1
            // 最高位就没找到直接break
            if fixIndex < 0 {
                break
            } else {
                findMax = false
            }
        } else if suitN == num {
            // 找到当前数,找下一位
            fixIndex += 1
        } else if suitN < num {
            // 只能找到次小数,直接退出,准备补充最大数
            digits[fixIndex] = suitN
            fixIndex += 1
            break;
        }
    }
    
    if fixIndex > 0 {
        for i in fixIndex..<digits.count {
            digits[i] = set.last!
        }
    } else {
        // 最高位最后用不了。 除了本身最高位找不到,还存在回退的情况 2111  [2, 7, 9]
        let size = digits.count
        digits.removeAll()
        for _ in 0..<(size - 1) {
            digits.append(set.last!)
        }
    }
    
    var res = 0
    for (i, num) in digits.enumerated() {
        res += num * ( pow(10, digits.count - i - 1) as NSDecimalNumber).intValue
    }
    
    return res
}

_ = maxNumber(777, [1, 4, 7]) 
// 这些case都能通过
//n=23132, a=[2,4,9], ans=22999
//n=21132, a=[2,4,9], ans=9999
//n=24132, a=[2,4,9], ans=22999
//n=24132, a=[1,4,9], ans=19999
//n=14132, a=[2,4,9], ans=9999
//n=14132, a=[1,4,9], ans=14119
//n=778, a=[1,4,7], ans=777
//n=777, a=[1,4,7], ans=774

2. LRU cache

注意使用虚拟节点,边界就会好处理很多。

class LRUCache {
    public class ListNode {
        var val: Int
        // 这里记录下key,方便删除最后一个元素
        var key = 0
        var next: ListNode?
        var pre: ListNode?
        init(_ val: Int, _ next: ListNode?, _ pre: ListNode?) {
            self.val = val
            self.next = next
            self.pre = pre
        }
        convenience init(_ val: Int) {
            self.init(val, nil, nil)
        }
    }
    let capacity: Int
    var map = [Int: ListNode]()
    let dummyHead = ListNode(0)
    var dummyLast = ListNode(0)
    var count = 0

    init(_ capacity: Int) {
        self.capacity = capacity
        dummyHead.next = dummyLast
        dummyLast.pre = dummyHead
    }
    
    func get(_ key: Int) -> Int {
        if let node = map[key] {
            // 删除链表节点
            deleteNodeInNodeList(node)
            // 插入链表头部
            insertNodeToHead(node)
            
            return node.val
        }
        return -1
    }
    
    func put(_ key: Int, _ value: Int) {
        if let node = map[key] {
            node.val = value
            // 删除链表节点
            deleteNodeInNodeList(node)
            // 插入链表头部
            insertNodeToHead(node)
        } else {
            let node = ListNode(value)
            node.key = key
            map[key] = node
            // 插入链表头部
            insertNodeToHead(node)
            // 判断移除超界限元素
            if count == capacity, let last = dummyLast.pre {
                // 删除链表节点
                deleteNodeInNodeList(last)
                map.removeValue(forKey: last.key)
            } else {
                count += 1
            }
        }
    }
    
    func deleteNodeInNodeList(_ node: ListNode) {
        node.pre?.next = node.next
        node.next?.pre = node.pre
        
        node.next = nil
        node.pre = nil
    }
    
    func insertNodeToHead(_ node: ListNode) {
        node.next = dummyHead.next
        dummyHead.next?.pre = node
        
        dummyHead.next = node
        node.pre = dummyHead
    }
}

3. 割绳子

给定一个整数数组 ribbons 和一个整数 k,数组每项 ribbons[i] 表示第 i 条绳子的长度。
对于每条绳子,你可以将任意切割成一系列长度为正整数的部分,或者选择不进行切割。

例如,如果给你一条长度为 4 的绳子,你可以:
保持绳子的长度为 4 不变;  
切割成一条长度为 3 和一条长度为 1 的绳子;  
切割成两条长度为 2 的绳子;  
切割成一条长度为 2 和两条长度为 1 的绳子;  
切割成四条长度为 1 的绳子。  
你的任务是最终得到 k 条完全一样的绳子,他们的长度均为相同的正整数。  
如果绳子切割后有剩余,你可以直接舍弃掉多余的部分。  

对于这 k 根绳子,返回你能得到的绳子最大长度;  
如果你无法得到 k 根相同长度的绳子,返回 0。 

示例 1:
输入: ribbons = [9,7,5], k = 3
输出: 5
解释:
- 把第一条绳子切成两部分,一条长度为 5,一条长度为 4;
- 把第二条绳子切成两部分,一条长度为 5,一条长度为 2;
- 第三条绳子不进行切割;
现在,你得到了 3 条长度为 5 的绳子。

示例 2:
输入: ribbons = [7,5,9], k = 4
输出: 4
解释:
- 把第一条绳子切成两部分,一条长度为 4,一条长度为 3;
- 把第二条绳子切成两部分,一条长度为 4,一条长度为 1;
- 把第二条绳子切成三部分,一条长度为 4,一条长度为 4,还有一条长度为 1;
现在,你得到了 4 条长度为 4 的绳子。

示例 3:
输入: ribbons = [5,7,9], k = 22
输出: 0
解释: 由于绳子长度需要为正整数,你无法得到 22 条长度相同的绳子。
 
提示:
1 <= ribbons.length <= 10^5
1 <= ribbons[i] <= 10^5
1 <= k <= 10^9

思路来自这里
二分查找的应用。

func maxRopeLength(_ L: [Int], _ k: Int) -> Int {
    guard !L.isEmpty, k > 0 else { return 0 }
    var high = L.max()!
    var low = 0
    var res = 0
    while low <= high {
        let mid = low + (high - low) / 2
        if mid == 0 { break }
        var count = 0
        for line in L {
            count += (line / mid)
            if count >= k {
                res = mid
                break
            }
        }
        if count >= k {
            low = mid + 1
        } else {
            high = mid - 1
        }
    }
    return res
}

// 持续补充