IT名企算法与数据结构题目最优解--栈和队列

前言

最近在看算法相关的书,无意中看到了这本《程序员代码面试指南:IT名企算法与数据结构题目最优解》

由于书中的代码使用Java实现的,所以我这里用swift去实现了一遍,即当作锻炼,也为了以后复习方便点(毕竟我看swift比看Java容易)。还有就是这里我只贴了该书的题目,并没有贴详细的推导过程,虽然代码里面有部分的注释,但是还是希望大家能买书去参考。废话不多说,直接看题.

设计一个有getMin功能的栈

  • 【题目】
    • 实现一个特殊的栈,在实现栈的基本功能的基础上,再实现返回栈中最小元素的操作。
  • 【解题】
    • 由于Swift中没有提供现成的栈这种数据结构,所以我们先实现一个基础的栈,然后再实现一个能返回一个最小元素的栈。我们在这里通过协议来实现,首先我们定义两个协议:
//这里我们只关注几个基础操作就行了
protocol Stack {
    associatedtype Element
    var isEmpty: Bool { get }
    var size: Int { get }
    var peek: Element? { get }
    mutating func push(_ newElement: Element)
    mutating func pop() -> Element?
}

//这里是获取最小值
protocol getMin {
    associatedtype Element
    mutating func min() -> Element?
}
复制代码

用这两个协议来实现一个存放Integer的栈:

struct IntStack: Stack {
    typealias Element = Int
    
    private var stack = [Element]()
    
    var isEmpty: Bool {
        return stack.isEmpty
    }
    
    var size: Int {
        return stack.count
    }
    
    var peek: Int? {
        return stack.last
    }
    
    mutating func push(_ newElement: Int) {
        stack.append(newElement)
    }
    
    mutating func pop() -> Int? {
        return stack.popLast()
    }
}
复制代码

进行到这一步的时候,我们已经拥有一个具备几个基础操作的栈了,然后我们使用这种栈来实现一个可以返回最小值的特殊栈:

struct getMinStack: Stack, getMin {
    typealias Element = Int
    
    private var stackData = IntStack()
    private var stackMin = IntStack()
    
    var isEmpty: Bool {
        return stackData.isEmpty
    }
    
    var size: Int {
        return stackData.size
    }
    
    var peek: Int? {
        return stackData.peek
    }
    
    mutating func push(_ newElement: Int) {
        //stackData
        stackData.push(newElement)
        //stackMin
        if stackMin.isEmpty {
            stackMin.push(newElement)
        }else {
            if let minObject = min() {
                if newElement <= minObject {
                    stackMin.push(newElement)
                }
            }
        }
    }
    
    mutating func pop() -> Int? {
        return stackData.pop()
    }
    
    func min() -> getMinStack.Element? {
        if !stackMin.isEmpty {
            return stackMin.peek
        }else {
            return nil
        }
    }
}
复制代码

现在我们来测试一下:

let testArray = [3, 4, 5, 7, 6, 9, 2, 10]
let minStack = getMinStack()
for num in testArray {
    minStack.push(num)
}
if !minStack.isEmpty {
    debugPrint(minStack.min()!)// 输出2
}
复制代码

由两个栈组成的队列

  • 【题目】
    • 编写一个类,用两个栈实现队列,支持队列的基本操作(add、poll、peek)。
  • 【解答】
    • 基于上题的代码,我们这里再定义一个Queue协议
protocol Queue {
    associatedtype Element
    mutating func add(_ newElement: Element)
    mutating func poll() -> Element?
    mutating func peek() -> Element?
}
复制代码

实现一个QueueFromStack结构体:

struct QueueFromStack: Queue {
    typealias Element = Int
    var stackPush = IntStack()
    var stackPop = IntStack()
    
    mutating func add(_ newElement: Int) {
        stackPush.push(newElement)
    }
    
    mutating func poll() -> Int? {
        if stackPush.isEmpty && stackPop.isEmpty {
            debugPrint("Queue is empty!")
            return nil
        }else if stackPop.isEmpty {
            while !stackPush.isEmpty {
                stackPop.push(stackPush.pop()!)
            }
        }
        return stackPop.pop()
    }
    
    mutating func peek() -> Int? {
        if stackPush.isEmpty && stackPop.isEmpty {
            debugPrint("Queue is empty!")
            return nil
        }else if stackPop.isEmpty {
            while !stackPush.isEmpty {
                stackPop.push(stackPush.pop()!)
            }
        }
        return stackPop.peek
    }
}
复制代码

现在我们来测试一下:

let testArray = [1, 2, 3, 4, 5, 6, 7, 8, 9]
var queue = QueueFromStack()
for num in testArray {
	queue.add(num)
}
        
debugPrint(queue.peek()!)// 输出1
debugPrint(queue.poll()!)// 输出1
debugPrint(queue.peek()!)// 输出2
复制代码

仅用递归函数和栈操作逆序一个栈

  • 【题目】
    • 一个栈依次压入1、2、3、4、5,那么从栈顶到栈底分别为5、4、3、2、1。将这个栈转置后,从栈顶到栈底为1、2、3、4、5,也就是实现栈中元素的逆序,但是只能用递归函数来实现,不能用其他数据结构。
  • 【解题】
    • 先实现一个移除栈底元素,并返回栈底元素的函数:
func getAndRemoveLastElement(stack: inout IntStack) -> Int {
    guard let result = stack.pop() else { return -1}
    if stack.isEmpty {
        return result
    }else {
        let last = getAndRemoveLastElement(stack: &stack)
        stack.push(result)//此时stack已经是移除栈底元素的stack
        return last
    }
}
复制代码

接下来对栈进行逆序操作,需要用到上面的递归函数:

func reverse(stack: inout IntStack) {
    if stack.isEmpty {
        //经过getAndRemoveLastElement(stack:)后,每次都会移除栈底元素,最终栈中元素会清空,就会来到这里
        return
    }
    /*
     多次返回栈底元素
     1->2->3
    */
    let i = getAndRemoveLastElement(stack: &stack)
    reverse(stack: &stack)
    /*
     此时stack已经为空,然后一次逆序push上一个栈返回的栈底元素
     push i = 3 -> [3]
     push i = 2 -> [3, 2]
     push i = 3 -> [3, 2, 1]
     */
    stack.push(i)
}
复制代码

现在我们来测试一下:

let testArray = [1, 2, 3]
var stack = IntStack()
for num in testArray {
    stack.push(num)
}
reverse(stack: &stack)
print(stack)//[3, 2, 1]
复制代码

猫狗队列

  • 【题目】
    • 宠物、狗和猫的类如下:
class Pet {
    private var type: String?
    init(type: String) {
        self.type = type
    }
    
    public func getType() -> String? {
        return type
    }
}

class Dog: Pet {
    init() {
        super.init(type: "Dog")
    }
}

class Cat: Pet {
    init() {
        super.init(type: "Cat")
    }
}
复制代码

实现一种狗猫队列的结构,要求如下:

  • 用户可以调用add方法将cat类或dog类的实例放入队列中;
  • 用户可以调用pollAll方法,将队列中所有的实例按照进队列的先后顺序依次弹出;
  • 用户可以调用pollDog方法,将队列中dog类的实例按照进队列的先后顺序依次弹出;
  • 用户可以调用pollCat方法,将队列中cat类的实例按照进队列的先后顺序依次弹出;
  • 用户可以调用isEmpty方法,检查队列中是否还有dogcat的实例;
  • 用户可以调用isDogEmpty方法,检查队列中是否有dog类的实例;
  • 用户可以调用isCatEmpty方法,检查队列中是否有cat类的实例。
  • 【解题】
class PetEnterQueue {
    private var pet: Pet?
    private var count: Int?
    init(pet: Pet, count: Int) {
        self.pet = pet
        self.count = count
    }
    
    public func getPet() -> Pet? {
        return self.pet
    }
    
    public func getCount() -> Int? {
        return self.count
    }
    
    public func getEnterPetType() -> String? {
        return self.pet?.getType()
    }
}

class DogCatQueue {
    private var dogQ: LinkedList<PetEnterQueue>!
    private var catQ: LinkedList<PetEnterQueue>!
    private var count = 0
    
    init() {
        dogQ = LinkedList<PetEnterQueue>()
        catQ = LinkedList<PetEnterQueue>()
    }
    
    public func add(pet: Pet) {
        let timeInterval: TimeInterval = Date().timeIntervalSince1970
        let timeStamp = Int(timeInterval)
        if pet.getType() == "Dog" {
            dogQ.appendToTail(value: PetEnterQueue(pet: pet, count: timeStamp))
        }else if pet.getType() == "Cat" {
            catQ.appendToTail(value: PetEnterQueue(pet: pet, count: timeStamp))
        }else {
            fatalError("error, not dog or cat")
        }
    }
    
    public func pollAll() -> Pet? {
        if !dogQ.isEmpty && !catQ.isEmpty {
            let dog = dogQ.last?.value
            let cat = catQ.last?.value
            if (dog?.getCount())! < (cat?.getCount())! {
                return dogQ?.last?.value.getPet()
            }else {
                return catQ?.last?.value.getPet()
            }
        }else if !dogQ.isEmpty {
            return dogQ.last?.value.getPet()
        }else if !catQ.isEmpty {
            return catQ?.last?.value.getPet()
        }else {
            fatalError("error, queue is empty!")
        }
    }
    
    public func pollDog() -> Dog {
        if !isDogQueueEmpty() {
            return dogQ.first?.value.getPet() as! Dog
        }else {
            fatalError("Dog queue is empty!")
        }
    }

    public func pollCat() -> Cat {
        if !isCatQueueEmpty() {
            return catQ.first?.value.getPet() as! Cat
        }else {
            fatalError("Cat queue is empty!")
        }
    }
    
    public func isEmpty() -> Bool {
        return dogQ.isEmpty && catQ.isEmpty
    }
    
    public func isDogQueueEmpty() -> Bool {
        return dogQ.isEmpty
    }
    
    public func isCatQueueEmpty() -> Bool {
        return catQ.isEmpty
    }
}	
复制代码

用一个栈实现另一个栈的排序

  • 【题目】
    • 一个栈中元素的类型为整型,现在想将该栈从顶到底按从大到小的顺序排序,只许申请一个栈。除此之外,可以申请新的变量,但不能申请额外的数据结构。如何完成排序?
  • 【解题】
func sortStackByStack(stack: IntStack) -> IntStack {
    var tempStack = stack
    //申请的辅助栈
    var helpStack = IntStack()
    while !tempStack.isEmpty {//如果原来栈的元素全都push到辅助栈上,则证明辅助栈从栈顶到栈底已经按小到大排好序了,则停止循环
        guard let cur = tempStack.pop() else { return tempStack }
        if helpStack.isEmpty {//辅助栈为空
            helpStack.push(cur)
        }else {//辅助栈不为空
            while helpStack.peek! <= cur {
                if let topElement = helpStack.pop() {
                    tempStack.push(topElement)
                }
            }
            helpStack.push(cur)
        }
    }
    //将辅助栈的元素逐一pop出来,push回到原来的栈上(原来栈已空),则原来栈就从栈顶到栈底就是按照从大到小的排序
    while !helpStack.isEmpty {
        if let element = helpStack.pop() {
            tempStack.push(element)
        }
    }
    return tempStack
}
复制代码

现在我们来测试一下:

var stack = IntStack()
let testArray = [3, 4, 5, 7, 6, 9, 2, 10]
for num in testArray {
    stack.push(num)
}
print(sortStackByStack(stack: stack))//IntStack(stack: [2, 3, 4, 5, 6, 7, 9, 10])
复制代码

用栈来求解汉诺塔问题

  • 【题目】
    • 汉诺塔问题比较经典,这里修改一下游戏规则:现在限制不能从最左侧的塔直接移动到最右侧,也不能从最右侧直接移动到最左侧,而是必须经过中间。求当塔有N层的时候,打印最优移动过程和最优移动总步数。例如,当塔数为两层时,最上层的塔记为1,最下层的塔记为2,则打印:

    Move 1 from left to mid

    Move 1 from mid to right

    Move 2 from left to mid

    Move 1 from right to mid

    Move 1 from mid to left

    Move 2 from mid to right

    Move 1 from left to mid

    Move 1 from mid to right

    It will move 8 steps.

  • 【要求】
    • 用以下两种方法解决。
    • 方法一:递归的方法;
    • 方法二:非递归的方法,用栈来模拟汉诺塔的三个塔。
  • 【解答一】:使用递归
func hanoiProblem(num: Int, left: String, mid: String, right: String) -> Int {
    if num < 1 {
        return 0
    }
    //这里是从左边挪到右边	
    return process(num, left, mid, right, from: left, to: right)
}

func process(_ num: Int, _ left: String, _ mid: String, _ right: String, from: String, to: String) -> Int {
    if num == 1 {//一层
        if from == mid || to == mid {//中->左、中->右、左->中、右->中
            print("Move 1 from \(from) to \(to)")
            return 1
        }else {//左->右、右->左
            print("Move 1 from \(from) to \(mid)")
            print("Move 1 from \(mid) to \(to)")
            return 2
        }
    }
    
    //多层
    if from == mid || to == mid {//中->左、中->右、左->中、右->中
        let another = (from == left || to == left) ? right : left
        let part1 = process(num - 1, left, mid, right, from: from, to: another)
        let part2 = 1
        print("Move \(num) from \(from) to \(to)")
        let part3 = process(num - 1, left, mid, right, from: another, to: to)
        return part1 + part2 + part3
    }else {//左->右、右->左
        let part1 = process(num - 1, left, mid, right, from: from, to: to)
        let part2 = 1
        print("Move \(num) from \(from) to \(mid)")
        let part3 = process(num - 1, left, mid, right, from: to, to: from)
        let part4 = 1
        print("Move \(num) from \(mid) to \(to)")
        let part5 = process(num - 1, left, mid, right, from: from, to: to)
        return part1 + part2 + part3 + part4 + part5
    }
}
复制代码

现在我们来测试一下:

let num = hanoiProblem(num: 2, left: "左", mid: "中", right: "右")
print("It will move \(num) steps.")

/*打印结果
Move 1 from 左 to 中
Move 1 from 中 to 右
Move 2 from 左 to 中
Move 1 from 右 to 中
Move 1 from 中 to 左
Move 2 from 中 to 右
Move 1 from 左 to 中
Move 1 from 中 to 右
It will move 8 steps.
*/
复制代码
  • 【解答二】:不使用递归,使用栈
//移动方式
public enum Action {
    case No  //初始状态
    case LToM//左->中
    case MToL//中->左
    case MToR//中->右
    case RToM//右->中
}
/*
 相邻不可逆原则:假设这一步是左->中,那么下一步就不可能是中->左,因为这样的话就不是移动次数最小的最优解
 小压大原则:一个动作的放生的前提条件是移出栈的栈顶元素不能大于移入栈的栈顶元素
 */
func hanoiProblem2(num: Int, left: String, mid: String, right: String) -> Int {
    //左栈
    var lS = IntStack()
    //中间栈
    var mS = IntStack()
    //右栈
    var rS = IntStack()
    //先往每个栈底放一个最大的整数
    lS.push(Int.max)
    mS.push(Int.max)
    rS.push(Int.max)
    
    for i in (1...num).reversed() {
        lS.push(i)
    }
    
    var record = [Action.No]
    var step = 0
    while rS.size != num + 1 {//当右边的栈等于(层数 + 1)就停止循环,表明所有的层都移到右边的栈里面去了
        /*
         1.第一个动作一定是:左->中
         2.根据”相邻不可逆“和”小压大“两个原则,所以第二个动作不肯是中->左和左->中
         3.那剩下的只有中->右,右->中两种移动方式,又根据”小压大“原则,这两种方式里面,只有一种是符合要求的
         综上,每一步只有一个方式符合,那么每走一步都根据两个原则来查看所有方式,只需执行符合要求的方式即可
         */
        step += fStackTotStack(record: &record, preNoAct: .MToL, nowAct: .LToM, fStack: &lS, tStack: &mS, from: left, to: mid)
        step += fStackTotStack(record: &record, preNoAct: .LToM, nowAct: .MToL, fStack: &mS, tStack: &lS, from: mid, to: left)
        step += fStackTotStack(record: &record, preNoAct: .RToM, nowAct: .MToR, fStack: &mS, tStack: &rS, from: mid, to: right)
        step += fStackTotStack(record: &record, preNoAct: .MToR, nowAct: .RToM, fStack: &rS, tStack: &mS, from: right, to: mid)
    }
    return step
}

func fStackTotStack(record: inout [Action], preNoAct: Action, nowAct: Action, fStack: inout IntStack, tStack: inout IntStack, from: String, to: String) -> Int {
    guard let fTop = fStack.peek else { return 0 }
    guard let tTop = tStack.peek else { return 0 }
    if record[0] != preNoAct && fTop < tTop {//相邻不可逆原则 && 小压大原则
        if let topElement = fStack.pop() {
            tStack.push(topElement)
        }
        guard let tTop2 = tStack.peek else { return 0 }
        print("Move \(tTop2) from \(from) to \(to)")
        record[0] = nowAct
        return 1
    }
    return 0
}
复制代码

现在我们来测试一下:

let step = hanoiProblem2(num: 3, left: "左", mid: "中", right: "右")
print("It will move \(step) steps.")
/*打印结果
Move 1 from 左 to 中
Move 1 from 中 to 右
Move 2 from 左 to 中
Move 1 from 右 to 中
Move 1 from 中 to 左
Move 2 from 中 to 右
Move 1 from 左 to 中
Move 1 from 中 to 右
Move 3 from 左 to 中
Move 1 from 右 to 中
Move 1 from 中 to 左
Move 2 from 右 to 中
Move 1 from 左 to 中
Move 1 from 中 to 右
Move 2 from 中 to 左
Move 1 from 右 to 中
Move 1 from 中 to 左
Move 3 from 中 to 右
Move 1 from 左 to 中
Move 1 from 中 to 右
Move 2 from 左 to 中
Move 1 from 右 to 中
Move 1 from 中 to 左
Move 2 from 中 to 右
Move 1 from 左 to 中
Move 1 from 中 to 右
It will move 26 steps.
*/
复制代码

生成窗口最大值数组

  • 【题目】
    • 有一个整型数组arr和一个大小为w的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置。例如,数组为[4,3,5,4,3,3,6,7],窗口大小为3时:

[4 3 5] 4 3 3 6 7 窗口中最大值为5

4 [3 5 4] 3 3 6 7 窗口中最大值为5

4 3 [5 4 3] 3 6 7 窗口中最大值为5

4 3 5 [4 3 3] 6 7 窗口中最大值为4

4 3 5 4 [3 3 6] 7 窗口中最大值为6

4 3 5 4 3 [3 6 7] 窗口中最大值为7

如果数组长度为n,窗口大小为w,则一共产生n-w+1个窗口的最大值。请实现一个函数。

输入:整型数组arr,窗口大小为w。

输出:一个长度为n-w+1的数组res,res[i]表示每一种窗口状态下的最大值。以本题为例,结果应该返回{5,5,5,4,6,7}。

  • 【解答】
func getMaxWindow(array: Array<Int>, windowSize w: Int) -> Array<Int> {
    if array.isEmpty || w < 1 || array.count < w {
        return []
    }
    
    /*
     1.用来存放array的下标
     2.通过不断的更新,qmax里面存放的第一个下标所对应array中的元素就是当前窗口子数组的最大元素
     */
    var qmax = [Int]()
    //初始化一个长度为(array.count - w + 1)的数组
    var res = Array.init(repeating: 0, count: (array.count - w + 1))
    var index: Int = 0
    
    for i in (0..<array.count) {
        /*************qmax的放入规则***************/
        //qmax不为空
        while !qmax.isEmpty && array[qmax.last!] <= array[i] {
            qmax.removeLast()
        }
        
        //如果qmax为空,直接把下标添加进去
        //如果array[qmax.last!] > array[i],直接把下标添加进去
        qmax.append(i)
        
        /*************qmax的弹出规则***************/
        //如果下标过期,则删除最前面过期的元素
        if let firstIndex = qmax.first {
            if firstIndex == (i - w) {
                qmax.removeFirst()
            }
        }
        
        /*
         这个条件是确保第一个窗口中的每个元素都已经遍历完
         这个例子中:第一个窗口对应的子数组是[4, 3, 5],所以i要去到2的时候才知道5是子数组里面的最大值,这样才能把最大值取出,放入res数组中。之后每移动一步都会产生一个最大
         */
        if i >= w - 1 {
            if index <= res.count - 1 {//确保不越界
                if let fistIndex = qmax.first {
                    res[index] = array[fistIndex]
                }
                index += 1
            }
        }
    }
    return res
}
复制代码

现在我们来测试一下:

let array1 = [4, 3, 5, 4, 3, 3, 6, 7]
print(getMaxWindow(array: array1, windowSize: 3))
let array2 = [44, 31, 53, 14, 23, 93, 46, 27]
print(getMaxWindow(array: array2, windowSize: 4))
/*
打印结果为:
[5, 5, 5, 4, 6, 7]
[53, 53, 93, 93, 93]
*/
复制代码

最大值减去最小值小于或等于num的子数组数量

  • 【题目】
    • 给定数组arr和整数num,共返回有多少个子数组满足如下情况: max(arr[i..j])-min(arr[i..j])<=num max(arr[i..j])表示子数组arr[i..j]中的最大值,min(arr[i..j])表示子数组arr[i..j]中的最小值。
  • 【要求】
    • 如果数组长度为N,请实现时间复杂度为O(N)的解法。
  • 【解答】
func getNum(array: Array<Int>, num: Int) -> Int {
    if array.isEmpty || array.count == 0 {
        return 0
    }
    //首元素代表当前子数组中的最大值的下标
    var qmax = [Int]()
    //首元素代表当前子数组中的最小值的下标
    var qmin = [Int]()
    
    //表示数组的范围
    var i = 0
    var j = 0
    //满足条件的子数组数量
    var res = 0
    
    while i < array.count {
        while j < array.count {
            while !qmin.isEmpty && array[qmin.last!] >= array[j] {
                qmin.removeLast()
            }
            qmin.append(j)
            while !qmax.isEmpty && array[qmax.last!] <= array[j] {
                qmax.removeLast()
            }
            qmax.append(j)
            //不满足题目的要求,j就停止向右扩
            if array[qmax.first!] - array[qmin.first!] > num {
                break
            }
            //j向右扩
            j += 1
        }
        
        if let firstIndex = qmin.first {
            if firstIndex == i {
                qmin.removeFirst()
            }
        }
        
        if let firstIndex = qmax.first {
            if firstIndex == i {
                qmax.removeFirst()
            }
        }
        
        // 假设i = 0,j = 5时:[0..4], [0..3], [0..2], [0..1], [0..0], 一共(j - i)个符合条件的子数组
        //j向右扩停止后,所以array[i..j-1]->array[i..i]子数组都符合条件,所以res += j - i
        res += (j - i)
       
        i += 1
    }
    return res
}
复制代码

现在我们来测试一下:

let array = [3, 4, 5, 7]
print(getNum(array: array, num: 2))
/*
符合要求的数组有:
[3]
[4]
[5]
[7]
[3, 4]
[4, 5]
[5, 7]
[3, 4, 5]
*/
复制代码

到此就是本文的结尾,如果大家觉得乱的话,可以直接通过Xcode来进行一步步调试,这里附上我写的代码

后记

本篇只实现了第一章部分的题目,等以后有空再补回来。对于剩下的章节,会随着我的看书进度,也会慢慢补上。如果你们喜欢本文的话,希望能打赏个赞,也当作是未来的动力。谢谢!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值