【LeetCode专项】链表、栈、队列

365 阅读5分钟

206. 反转链表

题目

leetcode.cn/problems/re…

代码

func reverseList(head *ListNode) *ListNode {
    var pre, next *ListNode
    for head != nil {
        next = head.Next
        head.Next = pre
        pre = head
        head = next
    }
    return pre
}

题解

juejin.cn/post/722266…

性能

image.png

160. 相交链表

题目

leetcode.cn/problems/in…

代码

func getIntersectionNode(headA, headB *ListNode) *ListNode {
    pa, pb := headA, headB
    for pa != pb {
        if pa==nil {
            pa = headB
        } else {
            pa = pa.Next
        }

        if pb==nil {
            pb = headA
        } else {
            pb = pb.Next
        }
    }
    return pa
}

题解

双指针,
从两个头节点开始,走一遍自己的,再走一遍对方的,
相等则返回。
能解决相同节点数不相交的问题,并且可以保证只走一次,不会走多次。

用例

image.png

性能

image.png

21. 合并两个有序链表

题目

leetcode.cn/problems/me…

代码

func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode {
    head := &ListNode{}
    cur := head
    
    for list1!=nil || list2!=nil {
        if list1==nil {
            cur.Next = list2
            list2 = list2.Next
        } else if list2==nil {
            cur.Next = list1
            list1 = list1.Next
        } else if list1.Val < list2.Val {
            cur.Next = list1
            list1 = list1.Next
        } else {
            cur.Next = list2
            list2 = list2.Next
        }

        cur = cur.Next
    }
    cur.Next = nil

    return head.Next
}

题解

比较左右两个链表,把小的放进去

性能

image.png

86. 分隔链表

题目

leetcode.cn/problems/pa…

代码

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func partition(head *ListNode, x int) *ListNode {
    small := &ListNode{}
    big := &ListNode{}

    cur1 := small
    cur2 := big
    for head != nil {
        if head.Val<x {
            cur1.Next = head
            cur1 = head
        } else {
            cur2.Next = head
            cur2 = head
        }
        head = head.Next
    }
    cur2.Next = nil
    cur1.Next = big.Next

    return small.Next
}

题解

用两个链表存储,small存储比给定值小的,big存储大于等于给定值的。
最后记得链上,将next设置为nil,即可。

性能

非常有趣...这个代码的性能是这样的
image.png 但是我本来写的代码是这样的

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func partition(head *ListNode, x int) *ListNode {
    small := &ListNode{}
    big := &ListNode{}

    cur1 := small
    cur2 := big
    for head != nil {
        if head.Val<x {
            cur1.Next = head
            cur1 = cur1.Next
        } else {
            cur2.Next = head
            cur2 = cur2.Next
        }
        head = head.Next
    }
    cur2.Next = nil
    cur1.Next = big.Next

    return small.Next
}

性能是这样的:
image.png 可以看到,区别只有

cur1 = cur1.Next
cur2 = cur2.Next

cur1 = head
cur2 = head

性能会有4ms的差异,65%的人能避免......还是挺神奇的

142. 环形链表 II

题目

leetcode.cn/problems/li…

代码

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func detectCycle(head *ListNode) *ListNode {
    slow, fast := head, head
    for {
        if fast==nil || fast.Next==nil {
            return nil
        } else {
            fast = fast.Next.Next
        }
        slow = slow.Next

        if slow==fast {
            break
        }
    }

    fast = head
    for slow != fast {
        slow=slow.Next
        fast=fast.Next
    }

    return slow
}

思路

双指针

  • 首先确定是否有环。
    • 慢指针slow走一步,快指针fast走两步。
    • 如果快指针到了nil,那就证明无环。
    • 如果两者相等,那就证明有环。
  • 然后找到环入口,这是一个数学问题。
    • 先上结论:让其中一个指针从head开始一步一步走,两者重新相遇的地方就是环的入口。
    • 假设,
      • head到环入口的距离是x,
      • 环入口到接触点的距离是y,
      • 接触点到环入口的距离是z。
    • 即:
      • 环的长度是y+z
      • 慢指针走了x+y的长度(慢指针一定最多只走了一圈,可以自己推导)
      • 快指针走了x + n*(y+z) + y,(n是走的圈数,不重要),也等于2 * (x+y)
    • 我们想找到环的入口,也就是x。
    • 证明结论的方法:
      • x+n(y+z)+y=2x+2y
      • x+y=n(y+z)
      • x+y=(n-1)(y+z)+y+z
      • x=(n-1)(y+z)+y
    • 忽略n-1圈,x就等于y。
    • 也就是说,fast指针重新指向head(只是为了复用变量,实际走的一步,已经不是快指针了)。然后走了x步,slow指针在圈内走了n-1圈,额外走了y步,两者恰好相遇。
  • 编码
    • 第一个for循环没有写条件,fast!=slow,是因为两者最开始都等于head,不会执行for循环。所以放在里边
    • 第一个for循环先走fast,是因为需要校验fast的nil,省的校验slow的nil了

性能

image.png

92. 反转链表 II

题目

leetcode.cn/problems/re…

代码

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func reverseBetween(head *ListNode, left int, right int) *ListNode {
    if head==nil || left==right {
        return head
    }

    // 假设输入为:12.345.678,3,5
    // 期望输出为:12.543.678
    // 需要保存2,3,5,6
    // leftEnd, reverseBegin, reverseEnd, rightBegin

    HEAD := &ListNode{Next: head}

    tmp := HEAD
    i := 1
    for ; i<left; i++ {
        tmp = tmp.Next
        // fmt.Println("i:", i, ", tmp:", tmp.Val)
    }
    leftEnd := tmp
    reverseBegin := tmp.Next
    
    // fmt.Println()

    var pre, next *ListNode
    for i--; i<=right; i++ {
        next = tmp.Next
        tmp.Next = pre
        pre = tmp
        tmp = next
        // fmt.Println("i:", i, ", tmp:", tmp.Val)
    }
    reverseEnd := pre
    rightBegin := next

    leftEnd.Next = reverseEnd
    reverseBegin.Next = rightBegin

    // fmt.Println()
    // fmt.Println(leftEnd.Val)
    // fmt.Println(reverseBegin.Val)
    // fmt.Println(reverseEnd.Val)
    // fmt.Println(rightBegin.Val)

    return HEAD.Next
}

思路

  • 遍历一次
    • 先举个例子:
      • 假设输入为:12.345.678,3,5
      • 期望输出为:12.543.678
      • 需要保存2,3,5,6
    • 先找到入口
    • 需要记录四个内容:
      • leftEnd:待翻转内容,之前的最后一个指针
      • reverseBegin:待翻转的第一个指针
      • reverseEnd:待翻转的最后一个指针
      • rightBegin:待翻转内容,之后的第一个指针
    • 然后将leftEnd.Next指向reverseEnd,也就是2指向5
    • 然后将reverseBegin.Next指向rightBegin,也就是3指向6
  • 边界条件
    • 自己写case试出来的,注意i--

性能

image.png 很奇怪,我的代码加了注释跑到双百,不加注释内存66.4%

138. 复制带随机指针的链表

题目

leetcode.cn/problems/co…

代码

/**
 * Definition for a Node.
 * type Node struct {
 *     Val int
 *     Next *Node
 *     Random *Node
 * }
 */

func copyRandomList(head *Node) *Node {
    m := make(map[*Node]*Node, 1)

    cur := &Node{Next: head}

    ans := &Node{}
    tmp := ans

    for cur.Next!=nil {
        if m[cur.Next]==nil {
            tmp.Next = &Node{Val: cur.Next.Val}
            m[cur.Next] = tmp.Next
        } else {
            tmp.Next = m[cur.Next]
        }

        if cur.Random!=nil {
            if m[cur.Random]==nil {
                tmp.Random = &Node{Val: cur.Random.Val}
                m[cur.Random] = tmp.Random
            } else {
                tmp.Random = m[cur.Random]
            }
        }

        cur = cur.Next
        tmp = tmp.Next
    }

    tmp.Random = m[cur.Random]
    

    return ans.Next
}

思路

用一个哈希表,k为原始链表中的每一个节点,v为新链表中的每个节点。
cur为原始链表指针,tmp为新链表指针。
判断cur的next是否在map中,判断cur的random是否在map中。

性能

image.png

20. 有效的括号

题目

leetcode.cn/problems/va…

代码

func isValid(s string) bool {
    stack := make([]rune, 0, len(s)/4)

    l := 0
    for _, b := range s {
        if b=='(' || b=='{' || b=='[' {
            stack = append(stack, b)
        } else {
            l = len(stack)
            if l == 0 {
                return false
            }

            if b==')' && stack[l-1]=='(' {
                stack = stack[:l-1]
            } else if b==']' && stack[l-1]=='[' {
                stack = stack[:l-1]
            } else if b=='}' && stack[l-1]=='{' {
                stack = stack[:l-1]
            } else {
                return false
            }
        }
    }

    if len(stack)==0 {
        return true
    }
    return false
}

思路

用栈,在出栈的时候判断是否匹配。

            // 本来想这样写的
            // 但是:[是91,]是93,92是\,人麻了
            // if int(b) == int(stack[l-1])+1 {
            //     stack = stack[:l-1]
            // } else {
            //     return false
            // }

性能

image.png

155. 最小栈

题目

leetcode.cn/problems/mi…

代码

type MinStack struct {
    stack []int
    minStack []int
    len int
}

func Constructor() MinStack {
    return MinStack{
        stack : []int{},
        minStack : []int{},
        len : 0,
    }
}

func (this *MinStack) Push(val int)  {
    if this.len == 0 {
        this.minStack = append(this.minStack, val)
    } else {
        this.minStack = append(this.minStack, min(val, this.minStack[this.len-1]))
    }

    this.stack = append(this.stack, val)
    this.len++
}


func (this *MinStack) Pop()  {
    this.stack = this.stack[:this.len-1]
    this.minStack = this.minStack[:this.len-1]
    this.len--
}


func (this *MinStack) Top() int {
    return this.stack[this.len-1]
}


func (this *MinStack) GetMin() int {
    return this.minStack[this.len-1]
}

func min(x int, y int) int {
    if x<y {
        return x
    }
    return y
}

思路

维护一个最小栈,这个辅助栈的大小和主栈一致。
在插入一个元素时,除了入栈操作,还要判断当前值和最小栈的栈顶值谁小,
如果栈顶小,就再塞入一个栈顶,
如果栈顶大,就塞入一个新值。

性能

image.png

946. 验证栈序列

题目

leetcode.cn/problems/va…

代码

func validateStackSequences(pushed []int, popped []int) bool {
    n := len(pushed)
    i, j := 0, 0
    tmp := make([]int, 0, n/2)
    now := 0

    for i!=n || j!=n {
        now = len(tmp)
        
        if j<n && now>0 && tmp[now-1]==popped[j] {
            // 先判断能否出栈
            tmp = tmp[:now-1]
            j++
        } else if i<n {
            // 判断能否入栈
            tmp = append(tmp, pushed[i])
            i++
        } else {
            return false
        }
    }
    return true
}

思路

  • 先判断能否出栈
    • 出栈条件:栈不为空,且,等于出栈表的这一项
  • 然后判断能否入栈
    • 入栈条件:还有剩余的入栈表

性能

image.png

739. 每日温度

题目

leetcode.cn/problems/da…

代码

type StackStruct struct {
    index int
    value int
}

func dailyTemperatures(temperatures []int) []int {
    n := len(temperatures)
    stack := make([]StackStruct, 0)
    ans := make([]int, n)

    i:=0
    l := 0
    for i<n {
        l = len(stack)

        if l>0 && stack[l-1].value<temperatures[i] {
            // 判断出栈条件
            ans[stack[l-1].index] = i - stack[l-1].index
            stack = stack[:l-1]
        } else {
            // 入栈
            stack = append(stack, StackStruct{
                index: i,
                value: temperatures[i],
            })
            i++
        }
    }

    return ans
}

思路

用栈来存储结构体,结构体里有入栈的下标,和入栈的值的内容

  • 每次遍历到一个值,先判断是否出栈
    • 出栈条件:当前值比栈顶的值要大,则将栈顶弹出
    • 出栈操作:ans[栈顶元素.index] = i - 栈顶元素.index
  • 不出栈则入栈
    • 塞进当前index和value
  • 结束条件:表走到最后一步。这个时候栈里的内容不用管,都会是0,因为这个栈一定是从小到大的,栈顶是最小的。

性能

相同代码每次跑出来的耗时都不一样,每太想到哪里的内存还可以优化 image.png

224. 基本计算器

题目

leetcode.cn/problems/ba…

解法1

通过从前往后的顺序遍历,击穿括号,让符号正确

代码1

func calculate(s string) int {
    stack := []int{1}
    ans := 0
    now := 0
    op := 1
    

    for _, b := range s {
        if b==' ' {
            continue
        } else if isNum(b) {
            now = now*10 + int(b-'0')
        } else {
            ans += op*now
            now = 0
            
            if b=='+' {
                op = stack[len(stack)-1]
            } else if b=='-' {
                op = -stack[len(stack)-1]
            } else if b=='(' {
                stack = append(stack, op)
            } else if b==')' {
                stack = stack[:len(stack)-1]
                op = stack[len(stack)-1]
            }
        }
    }
    ans += op*now
    return ans
}

func isNum(r rune) bool {
    if r-'0'>=0 && r-'0'<=9 {
        return true
    }
    return false
}

思路1

func calculate(s string) int {
    // 通过从前往后的顺序遍历,击穿括号,让符号正确

    stack := []int{1} // 只存储值的正负号,并且只有在有括号时才操作
    ans := 0 // 最终结果
    now := 0 // 当前这个数的真实值
    op := 1 // 因为不是逆波兰,所以这里存储的op是上一个符号
    

    for _, b := range s {
        if b==' ' {
            continue
        } else if isNum(b) {
            now = now*10 + int(b-'0')
        } else {
            // 因为不是逆波兰,所以需要在确认一个数结束之后,计算上一个符号的结果,例如:
            /*
                ans+34-
                这个式子的时候,在检查到第二个符号(减号)的时候。
                可以确认34这个数结束了,所以可以计算前一个值(即ans+34的结果)。
                存储的op是减号之前的上一个符号,即加号
            */
            ans += op*now
            now = 0
            
            if b=='+' {
                // 如果遇到加号,那真实的符号应该是上一个符号
                op = stack[len(stack)-1]
            } else if b=='-' {
                // 如果遇到减号,那真实的符号应该是上一个符号取反
                op = -stack[len(stack)-1]
            } else if b=='(' {
                // 如果遇到括号,需要把上一个遇到的符号放进stack中,击穿的效果就在如此,每次塞进去的都是真实的符号,相当于是去掉了括号
                stack = append(stack, op)
            } else if b==')' {
                // 如果遇到括号,代表之前括号前的符号影响消失了,应该出栈
                stack = stack[:len(stack)-1]
                op = stack[len(stack)-1]
            }
        }
    }

    ans += op*now
    return ans
}

func isNum(r rune) bool {
    if r-'0'>=0 && r-'0'<=9 {
        return true
    }
    return false
}

性能1

image.png

解法2

两个栈,分别存数和符号,每次将当前能处理的处理掉

代码2

import (
    "fmt"
    "strings"
)

func calculate(s string) int {
    s = strings.Replace(s, " ", "", -1)
    n := len(s)
    i := 0

    ops := []byte{}
    nums := []int{0}
    tmp := 0

    for i=0; i<n; i++ {
        if s[i]=='(' {
            ops = append(ops, '(')
        } else if s[i] == ')' {
            for len(ops)>=0 {
                if ops[len(ops)-1]=='(' {
                    ops = ops[:len(ops)-1]
                    break
                } else {
                    calcu(&nums, &ops)
                }
            }
        } else {
            if isNum(s[i]) {
                tmp=0
                for i<n && isNum(s[i]) {
                    tmp*=10
                    tmp += int(s[i]-'0')
                    i++
                }
                nums = append(nums, tmp)
                i--
            } else {
                // 解决1-(-2)这种case
                if i>0 && s[i-1]=='(' {
                    nums = append(nums, 0)
                }

                // 必须在拿到符号的时候算,不能在拿到数字的时候算
                for len(ops)!=0 && ops[len(ops)-1]!='(' {
                    calcu(&nums, &ops)
                }
                ops = append(ops, s[i])
            }
        }
    }
    for len(ops)!=0 {
        calcu(&nums, &ops)
    }
    return nums[len(nums)-1]
}

func calcu(nums *[]int, ops *[]byte) {
    // 注意取地址,不然的话是新
    n1 := len(*nums)
    n2 := len(*ops)

    if (*ops)[n2-1] == '+' {
        (*nums)[n1-2] += (*nums)[n1-1]
    } else {
        (*nums)[n1-2] -= (*nums)[n1-1]
    }

    *nums = (*nums)[:n1-1]
    *ops = (*ops)[:n2-1]
}

func isNum(b byte) bool {
    if b-'0'>=0 && b-'0'<=9 {
        return true
    }
    return false
}

思路2

注意:必须在拿到符号的时候算,不能在拿到数字的时候算

性能2

image.png

解法3

自己写出来的蠢笨方法,只将当前能处理的处理掉。
比如无括号的,一直往前算,如果是括号内的值,一直算到遇到左括号。

代码3

import "strings"

func calculate(s string) int {
    s = strings.Replace(s, " ", "", -1)
    n := len(s)

    nums := []int{}
    ops := []int{}
    if s[0]=='-' {
        nums = append(nums, 0)
    }
    
    tmpNum := 0
    i := 0
    lenNums := 0
    lenOps := 0

    for i<n {
        if !isNum(s[i]) {

            if s[i]=='+' {
                ops = append(ops, 1)
                i++
            } else if s[i]=='-' {
                ops = append(ops, 2)
                i++
            } else if s[i] == '(' {
                ops = append(ops, 3)
                i++

                if s[i]=='-' {
                    ops = append(ops, 2)
                    nums = append(nums, 0)
                    i++
                }
            } else if s[i]==')' {
                if ops[len(ops)-1] == 3 {
                    ops = ops[:len(ops)-1]
    
                    for lenOps = len(ops); lenOps>0; lenOps = len(ops) {

                        lenNums = len(nums)

                        if ops[lenOps-1] == 1 {
                            ops = ops[:lenOps-1]

                            nums[lenNums-2] += nums[lenNums-1]
                            nums = nums[:lenNums-1]
                        } else if ops[lenOps-1] == 2 {
                            ops = ops[:lenOps-1]

                            nums[lenNums-2] = nums[lenNums-2]-nums[lenNums-1]
                            nums = nums[:lenNums-1]
                        } else {
                            break
                        }
                    }

                }
                i++
            }
            continue
        } else {
            tmpNum = 0
            for i<n && isNum(s[i]) {
                tmpNum*=10
                tmpNum += int(s[i]-'0')
                i++
            }

            nums = append(nums, tmpNum)

        }

        for lenOps = len(ops); lenOps>0; lenOps = len(ops) {
            lenNums = len(nums)

            if ops[lenOps-1] == 1 {
                ops = ops[:lenOps-1]

                nums[lenNums-2] += nums[lenNums-1]
                nums = nums[:lenNums-1]
            } else if ops[lenOps-1] == 2 {
                ops = ops[:lenOps-1]

                nums[lenNums-2] = nums[lenNums-2]-nums[lenNums-1]
                nums = nums[:lenNums-1]
            } else {
                break
            }
        }
    }

    for lenOps = len(ops); lenOps>0; lenOps = len(ops) {
        lenNums = len(nums)
        if ops[lenOps-1] == 1 {
            ops = ops[:lenOps-1]

            nums[lenNums-2] += nums[lenNums-1]
            nums = nums[:lenNums-1]
        } else if ops[lenOps-1] == 2 {
            ops = ops[:lenOps-1]

            nums[lenNums-2] = nums[lenNums-2]-nums[lenNums-1]
            nums = nums[:lenNums-1]
        } else {
            ops = ops[:len(ops)-1]
        }
    }
    
    return nums[0]
}

func isNum(r byte) bool {
    if r-'0'>=0 && r-'0'<=9 {
        return true
    }
    return false
}

思路3

冗余度很高,但是没有想到很好的解法,因为遇到左括号的时候的处理方式不一样,所以没法封装成方法。

性能3

image.png

42. 接雨水

题目

leetcode.cn/problems/tr…

解法1

代码1

func trap(height []int) int {
    n := len(height)
    rightHigh := make([]int, n)
    ans := 0
    
    rightHigh[n-1]=height[n-1]
    i := n-2
    for ; i>=0; i-- {
        if height[i]>rightHigh[i+1] {
            rightHigh[i]=height[i]
        } else {
            rightHigh[i]=rightHigh[i+1]
        }
    }

    leftHighTmp := height[0]
    for i=1; i<n; i++ {
        if height[i]<leftHighTmp && height[i]<rightHigh[i] {
            if leftHighTmp<rightHigh[i] {
                ans += leftHighTmp-height[i]
            } else {
                ans += rightHigh[i]-height[i]
            }
        }

        if height[i]>leftHighTmp {
            leftHighTmp = height[i]
        }
    }

    return ans
}

思路1

当前位置能存多少水=MIN(MAX(左边的每一个),MAX(右边的每一个高度))-当前高度 注意左右都不能为0,卡不住水

性能1

之前时间是99.54% image.png

解法2

栈 // TODO

232. 用栈实现队列

题目

leetcode.cn/problems/im…

代码

type MyQueue struct {
    queue []int
}


func Constructor() MyQueue {
    return MyQueue {
        queue : []int{},
    }
}


func (this *MyQueue) Push(x int)  {
    tmp := make([]int, 0, len(this.queue))

    for len(this.queue)!=0 {
        tmp = append(tmp, this.queue[len(this.queue)-1])
        this.queue = this.queue[:len(this.queue)-1]
    }

    tmp = append(tmp, x)

    for len(tmp)!=0 {
        this.queue = append(this.queue, tmp[len(tmp)-1])
        tmp = tmp[:len(tmp)-1]
    }
}


func (this *MyQueue) Pop() int {
    tmp := this.queue[len(this.queue)-1]
    this.queue = this.queue[:len(this.queue)-1]
    return tmp
}


func (this *MyQueue) Peek() int {
    return this.queue[len(this.queue)-1]
}


func (this *MyQueue) Empty() bool {
    return len(this.queue)==0
}


/**
 * Your MyQueue object will be instantiated and called as such:
 * obj := Constructor();
 * obj.Push(x);
 * param_2 := obj.Pop();
 * param_3 := obj.Peek();
 * param_4 := obj.Empty();
 */

思路

用go写确实很蠢。
思路和普通的一样,两个栈来回倒实现队列。
我选择了在push的时候用临时栈来倒,原因是因为peek和pop是两个,push是一个。

性能

image.png

239. 滑动窗口最大值

代码

func maxSlidingWindow(nums []int, k int) []int {
    n := len(nums)
    ans := make([]int, 0, n-k+1)

    deque := make([]int, 0, k)

    var pushAndGet func(i int)int
    pushAndGet = func(i int)int {
        for len(deque)>0 && nums[deque[len(deque)-1]]<nums[i] {
            deque = deque[:len(deque)-1]
        }
        deque = append(deque, i)
        for len(deque)>0 && deque[0]<i-k+1 {
            deque = deque[1:]
        }
        return deque[0]
    }
    
    i:=0
    for ; i<k-1; i++ {
        pushAndGet(i)
    }

    for ; i<n; i++ {
        ans = append(ans, nums[pushAndGet(i)])
    }
    return ans
}

思路

  • 原因
    • 如果要进来一个又晚(向右滑动,index一定增加,一定更晚弹出滑动窗口),又相对大的数(可能比一些数大) 。
    • 那么一定不用考虑之前,又小,又相对早的数。
  • 做法
    • 构建一个递减队列
    • 从后往前看,如果当前的数比队尾的数大,把队尾数扔掉
      • 扔掉的这些数,就是又小,又早的数。它们未来绝对不会是需要返回的数。
    • 将当前的数放在队尾
      • 如果接下来的所有数都比这个数小,随着窗口右滑,前边大的数被弹出,就会获取到这个数。
    • 检验队首的值是否合法(窗口范围内),将不合法的值删除后,返回队首的值
      • 只需要最大的数,已经满足它是一个递减队列,所以从队首获取第一个合法的值即可。
      • 只需要让队首的第一个数合法,不需要考虑后边的值是否合法。因为需要返回的是最大的值,等到队首这个数非法的时候,再去判断其他的数是否合法,找合法里的最大的值即可。

性能

性能不准的,相同的代码每次跑结果都不一样 image.png

234. 回文链表

用O(n)时间复杂度和O(1)空间复杂度

代码

func isPalindrome(head *ListNode) bool {
    fast, slow := head, head
    for {
        if fast.Next==nil {
            break
        }
        if fast.Next.Next==nil {
            break
        }
        fast=fast.Next.Next
        slow=slow.Next
    }

    right := slow.Next
    
    var pre,next *ListNode
    for right!=nil {
        next=right.Next
        right.Next=pre
        pre=right
        right=next
    }

    for pre!=nil && head!=nil {
        if pre.Val!=head.Val {
            return false
        }
        pre=pre.Next
        head=head.Next
    }

    return true
}

思路

  • 首先,这种链表的,还说能O(1)空间复杂度解决的,都考虑一下是否快慢指针好使
  • 当快指针不能再走的时候,从慢指针截断,将后续链表翻转,然后一个个比较即可。
    • 因为截断是快指针到末尾时发生的,所以两个链表最多相差一个,不用考虑最后一个(回文特性)

性能

同样,性能不准,每次跑出来的结果都不一致 image.png

剑指 Offer 22. 链表中倒数第k个节点

代码

func getKthFromEnd(head *ListNode, k int) *ListNode {
    fast, slow := head, head
    i := 0
    for fast != nil && i < k {
        fast=fast.Next
        i++
    }

    for fast != nil {
        fast=fast.Next
        slow=slow.Next
    }
    return slow
}

思路

双指针,fast指针先走k个,然后慢指针开始走,当fast到nil的时候,slow指针指向的就是倒数第k个。

以示例为例:链表: 1->2->3->4->5,k=2,期望返回4->5

fast,slow都指向1。
fast走k步,也就是2步。第一步,fast指向2,第二步,fast指向3。
现在fast指向3,slow指向1,两者一起走,直到fast指向nil。
第一步,fast指向4,slow指向2。
第二步,fast指向5,slow指向3.
第三部,fast指向nil,slow指向4.

直接返回slow即可

性能

image.png

25. K 个一组翻转链表

代码

func reverseKGroup(head *ListNode, k int) *ListNode {
    HEAD := ListNode{Next:head}

    now:=&HEAD
    var realPre, futureLast, pre, next *ListNode
    i:=0
    for now!=nil {
        realPre=now
        i=0
        for i<k && now.Next!=nil {
            now=now.Next
            i++
        }

        if i<k {
            break
        }

        futureLast=realPre.Next
        pre=now.Next
        now=realPre.Next
        i=0
        for i<k {
            next=now.Next
            now.Next=pre
            pre=now
            now=next
            i++
        }

        realPre.Next=pre
        now=futureLast
    }


    return HEAD.Next
}

思路

  • 链表问题,该画图一定要画图。链表问题,可以新建一个头结点解决很多问题。
  • 以题目中的1,2,3,4,5。k=2为例。
新建一个头结点HEAD为0next指向1,用now指向HEAD这个节点。初始状态是0,1,2,3,4,5
我们期望第一次的翻转的结果是这样的:0,2,1,3,4,5

以第一次翻转为例,在翻转前希望保留几个节点,用于翻转后的连接。
0:用于指向翻转后的头节点,我起名realPre,是真正的前节点。
2:翻转后的头节点,用于被前节点连接,不用提前存储,因为反转链表后的pre会指向这里。
1:翻转后的最后一个节点,我起名futureLast。有两个作用,第一个作用,用于连接下一个待翻转的头,第二个作用,由于是从0开始的,所以下一个循环反转链表也应该从这里开始。
3:下一个待翻转的头,用于被上一个反转的最后一个节点连接,不用提前存储,因为反转链表前的next指向这里可以直接处理好。


最开始now指向0。
    先保存now:0到realPre。
    然后保存now.Next1为futureLast,它是反转前的头结点,是翻转后的尾节点。
now向后走k步,也就是指向2。
    (如果没有走到,通过走的步数不够会直接break,不会翻转)
2不需要提前存储,因为最后的pre会指向这里。
3不需要提前存储,通过给pre赋初始值指向这里。

pre指向now.Next:3,now指向翻转的起点,也就是1。
开始翻转,翻转k次。
翻转后,pre指向2。

将整个链表连接。
    前一个节点realPre:0指向新的头结点pre:2.
    (已经连接好)翻转前的头节点/翻转后的尾节点,futureLast:1指向后续节点3
    
刚刚的开头是0,也就是开始翻转的前一个节点,所以现在的now也需要指向下一个翻转的前节点,也就是futureLast:1。
    这一次翻转的内容是1,2变成2,1
    下一次翻转的内容是3,4变成4,3

性能

同样,性能的值没有意义,以下是完全相同的代码。 image.png image.png image.png image.png