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
}
}
}