LeetCode 剑指 Offer 专项练习 (第一阶段)

1、整数除法(二进制)
在这里插入图片描述

不得使用 *、/、% ,那只能使用加减以及位运算模拟乘除。

首先容易想到的是 通过减法, a减去n个b后,当差值小于b时,n即为a与b的商。但该算法计算n次,时间复杂度为O(n)。由题目可知,当a为 -2^31, b为-1时,循环次数达到20亿次,一定会超时。所以需要比逐个减更快速的“除法”。

提高运算效率,先看式子:a/b = r …… c 即 b * r + c = a,即整数(a - c)是可以由若干个b表示的。这可以联想到二进制,任意整数可以用若干个二进制数相加得到,同理,a-c可以由若干的二进制数 乘 b 构成。由于二进制数可以使用位运算计算所得,所以计算一个二进制数的时间复杂度为O(lgN),通过循环的减法计算a-b需要的次数也为O(lgN),得到总的时间复杂度为O(lg2N)。N的取值范围为0到31,所以改解法最大循环次数不会超过千次,符合时间要求。
在这里插入图片描述
需要注意题目中有负数,需要先对数据取绝对值。

class Solution {
    func divide(_ a: Int, _ b: Int) -> Int {
        var count:Int = 0
        var tmpA:Int = abs(a)
        var tmpB:Int = abs(b)
        var del:Int = tmpA
        while (tmpA >= tmpB)
        {
            del = tmpA - tmpB
            count += 1
            var k:Int = 1
            while (del >= tmpB)
            {
                tmpB = tmpB << 1
                if (del - tmpB) < 0
                {
                    break
                }
                k = k << 1
                count += k
                del = del - tmpB
            }
            tmpB = abs(b)
            tmpA = del
        }
        
        if a < 0
        {
            count = 0 - count
        }
        
        if b < 0
        {
            count = 0 - count
        }
        
        if count >= 2147483648
        {
            count -= 1
        }
        
        return count
    }
}

2、和为k的子数组(前缀和、哈希表)
在这里插入图片描述

数列的求和问题,如果从第一个数开始,逐个相加再比较是否相等,需要比较次n2,时间复杂度O(n2),显然不符合要求的。
现用an表示数列的第n项,Sn表示数列前n项的求和。则序列和等于k有以下情况:(1)Sn 等于 k;(2)Sn - Sm(0< m < n) 等于 k;第一种情况,只需遍历求和即可判断得到;而第二种情况则需要使用哈希表存放序列求和对应的个数,才可以得到。
举例:数组为[1, 2, -1, -2, 3]
在这里插入图片描述
遍历数组,将符合上述两种情况的数量相加即可得到最终的答案,时间复杂度为O(n)。

class Solution {
   func subarraySum(_ nums: [Int], _ k: Int) -> Int {
       var count:Int = 0
        var sumNums:Int = 0
        var numsMap:Dictionary<Int, Int> = Dictionary<Int, Int>()
        
        var index:Int = 0
        while (index < nums.count)
        {
            sumNums += nums[index]
            let del = sumNums - k
            if del == 0
            {
                count += 1
            }
            
            count += (numsMap[del] ?? 0)
            
            numsMap[sumNums] = (numsMap[sumNums] ?? 0) + 1
            
            index += 1
        }
        return count
    }
}

3、含有所有字符的最短字符串(双指针、哈希表)

在这里插入图片描述
该题主要为两个步骤:(1)在s中找到含有t所有字符的子字符串;(2)记录子字符串的长度。步骤二并没有循环,所以主要考虑的问题是如何快速完成步骤一。如果从字符串的开端开始,逐个拼接寻找符合的要求的子串,需要遍历n2次,时间复杂度O(n2)显然超出时间的限制。所以可以考虑双指针配合哈希表使用。
先设置两个指针:start 指针指向子串的开始,end 指针指向子串的结尾。如何判断start到end的区间内的字符串包含了t的所有字符?可以先遍历字符串t,获取t不重复的字符个数(countOfT) 以及t每个字符对应的数量的哈希表。遍历字符串s时,记录s当前字符的个数,当个数与t中对应字符的对应个数相等时,记录s的计数(countOfS)。当countOfS 与 countOfT 相等时,说明此时start 到 end区间内到字符串已经含有t的所有字符,此时需要将start右移,以缩短子字符串的长度,直到该子字符串不能包含t中所有字符为止, 时间复杂度为O(n)。
举例:s = “ADOBECBANC”, t = “ABC” 则countOfT = 3, HashOfT:
在这里插入图片描述
在这里插入图片描述

class Solution {
    func minWindow(_ s: String, _ t: String) -> String {
        let sArr:Array<Character> = Array<Character>(s)
        let tArr:Array<Character> = Array<Character>(t)
        
        var sMap:Array<Int> = Array<Int>.init(repeating: 0, count: 60)
        var tMap:Array<Int> = Array<Int>.init(repeating: 0, count: 60)
        
        let A:Character = "A"
        var countOfT:Int = 0
        
        for item in tArr
        {
            let t2Num:Int = Int(item.asciiValue! - A.asciiValue!)
            tMap[t2Num] += 1
            if tMap[t2Num] == 1
            {
                countOfT += 1
            }
        }
        
        var start:Int = 0
        var end:Int = 0
        
        var lastStart:Int = -1
        var lastEnd:Int = -1
        var countOfS:Int = 0
        
        while (end < sArr.count)
        {
            let s2Num:Int = Int(sArr[end].asciiValue! - A.asciiValue!)
            sMap[s2Num] += 1
            if sMap[s2Num] == tMap[s2Num]
            {
                countOfS += 1
            }
            
            while (countOfS == countOfT)
            {
                // 记录
                if lastEnd == -1 || ((lastEnd - lastStart) > (end - start))
                {
                    lastEnd = end
                    lastStart = start
                }
                let s2NumStart:Int = Int(sArr[start].asciiValue! - A.asciiValue!)
                sMap[s2NumStart] -= 1
                if sMap[s2NumStart] < tMap[s2NumStart]
                {
                    countOfS -= 1
                }
                start += 1
            }
            end += 1
        }
        
        if lastEnd == -1
        {
            return ""
        }
        let result:String = String(s.dropFirst(lastStart).prefix((lastEnd - lastStart + 1)))
        return result
    }
}

4、LRU (最近最少使用置换算法)
在这里插入图片描述
要求对容器的访问、加入新元素、修改元素的时间复杂度都为O(1)。需要使用哈希配合双链表实现。假设缓存块为页面,哈希表存放关键字key对应的页面节点。使用head指针指向链表开头、tail指针指向结尾,双链表可以让链表刷新时找到前后页面。当访问或修改某个关键字对应页面时,将该页面放置到链表末尾,然后更新tail指针。当加入新关键字时,若当前链表长度达到容量上限,移除head指针指向元素,然后更新head指针即可。所以无论访问、修改或添加元素,时间复杂的都为O(1)。
在实现上,可以在链表的头尾分别加上 虚拟的头尾指针,避免移除head节点或添加tail节点时需要单独考虑。空间复杂度方面,双向链表最大节点量为capacity + 2,哈希表最大节点量为capacity,所以空间复杂度为O(capacity)。

// 内存节点
public class cacheNode
{
    public var index:Int
    public var value:Int
    public var next:cacheNode?
    public var pre:cacheNode?
    
    init(_ id_:Int, _ value_: Int)
    {
        index = id_
        value = value_
        next = nil
        pre = nil
    }
}
// LRU算法
class LRUCache {
    private var head:cacheNode?
    private var map:Dictionary<Int, cacheNode>
    private var capacity_:Int
    private var count:Int
    private var tail:cacheNode?
    
    init(_ capacity: Int) {
        capacity_ = capacity
        head = cacheNode(-1, -1)
        head?.next =  cacheNode(-2, -1)
        tail = head?.next
        tail?.pre = head
        map = Dictionary<Int, cacheNode>()
        count = 0
    }

    private func delete()
    {
        map.removeValue(forKey: head!.next!.index)
        head!.next!.next?.pre = head
        head!.next = head!.next!.next
        count -= 1
    }
    
    private func refresh(_ key:Int)
    {
        let cur = map[key]
        
        if cur?.next === tail
        {
            return
        }
        
        cur?.pre?.next = cur?.next
        cur?.next?.pre = cur?.pre
        cur?.pre = tail?.pre
        tail?.pre?.next = cur
        tail?.pre = cur
        cur?.next = tail
        map[key] = tail?.pre
    }

    func get(_ key: Int) -> Int {
        if map.keys.contains(key) == true
        {
            refresh(key)
        }
        return map[key]?.value ?? -1
    }

    func put(_ key: Int, _ value: Int) {
        if map.keys.contains(key) == true
        {
            map[key]?.value = value
            refresh(key)
        }
        else
        {
            count += 1
            if count > capacity_
            {
                delete()
            }
            
            let tmp:cacheNode? = tail?.pre
            tmp?.next = cacheNode(key, value)
            tmp?.next?.pre = tmp
            tail?.pre = tmp?.next
            tmp?.next?.next = tail
            map[key] = tail?.pre
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值