必刷top101

题目来源:牛客网面试必刷TOP101

链表 #

反转链表 #

给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。

数据范围: 0≤n≤10000≤n≤1000

要求:空间复杂度 O(1)O(1) ,时间复杂度 O(n)O(n) 。

如当输入链表{1,2,3}时,

经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。

以上转换过程如下图所示:

输入:{1,2,3}
返回值:{3,2,1}
func ReverseList( pHead *ListNode ) *ListNode {
    if pHead==nil||pHead.Next==nil{
        return pHead
    }
    p:=&ListNode{Val:-1,Next:pHead} //设置一个头节点,防止冲突
    pHead=p
    p=pHead.Next
    q:=p
    for p.Next!=nil{
        q=p.Next
        p.Next=q.Next
        q.Next=pHead.Next     //这道题的重点在这里=头节点的下一个
        pHead.Next=q
    }
    return pHead.Next
}

链表内指定区间反转 #

将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n)O(n),空间复杂度 O(1)O(1)。

例如:
给出的链表为 1→2→3→4→5→NULL1→2→3→4→5→NULL, m=2,n=4,
返回 1→4→3→2→5→NULL1→4→3→2→5→NULL.
func reverseBetween( head *ListNode ,  m int ,  n int ) *ListNode {
    // write code here
    if head.Next==nil||m==n||head==nil{ //m=n相当于没有翻转
      return head
    }
    p:=&ListNode{Val:-1,Next:head}
    head=p
    for i:=1;i<m;i++{  // 这里的m,n不是链表里面的值 是第几个数  傻逼
      p=p.Next
    }
    q:=p.Next
    cur:=q
    for i:=0;i<n-m;i++{
      cur=q.Next
      q.Next=cur.Next
      cur.Next=p.Next
      p.Next=cur   
    }
    return head.Next
}
func reverseBetween(head *ListNode, left, right int) *ListNode {
  pre:=&ListNode{-1,head}//设置头结点,防止left为1干扰
  head=pre
  for i:=0;i<left-1;i++{
    pre=pre.Next
  }
  cur:=pre.Next
  for i:=left;i<right;i++{  //翻转链表 直到 right
    next:=cur.Next
    cur.Next=next.Next
    next.Next=pre.Next
    pre.Next=next 
  }
  return head.Next
}

链表中的节点每K个一组翻转 #

将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表 如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样 你不能更改节点中的值,只能更改节点本身。

给定的链表是 1→2→3→4→51→2→3→4→5

对于 k=2 , 你应该返回 2→1→4→3→5

对于 k=3 , 你应该返回 3→2→1→4→5

输入:{1,2,3,4,5},2
返回值:{2,1,4,3,5}
func reverseKGroup( head *ListNode ,  k int ) *ListNode {
    if head==nil||k==1||head.Next==nil{   //特殊情况返回
      return head
    }
    p:=&ListNode{Val:-1,Next:head}
    head=p
    q:=head
    i:=0
    for p.Next!=nil{
      p=p.Next
      i++
      if i==k{    //相等了开始翻转
        xx:=p.Next   //找一个指针让他等于p的后面
        p.Next=nil   //这里断开,不然会搞乱
        Left,Right:=reversNode(q.Next)  //得到左右节点指针
        q.Next=Left   //接上去
        Right.Next=xx
        i=0      //i清空
        p=Right   //放到开头位置
        q=Right
      }
    }
    return head.Next
}
func reversNode(left *ListNode)(Left,Right *ListNode){  //反转链表函数,返回头和尾
  pre:=&ListNode{Val:-1,Next:left}
  left=pre
  pre=left.Next
  q:=pre
  for pre.Next!=nil{
    q=pre.Next
    pre.Next=q.Next
    q.Next=left.Next
    left.Next=q
  }
  return left.Next,pre
}

合并两个排序的链表 #

输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。

如输入{1,3,5},{2,4,6}时,合并后的链表为{1,2,3,4,5,6},所以对应的输出为{1,2,3,4,5,6},转换过程如下图所示:

或输入{-1,2,4},{1,3,4}时,合并后的链表为{-1,1,2,3,4,4},所以对应的输出为{-1,1,2,3,4,4},转换过程如下图所示:

输入:{1,3,5},{2,4,6}
返回值:{1,2,3,4,5,6}
func Merge( pHead1 *ListNode ,  pHead2 *ListNode ) *ListNode {//把一个链表往另一里面插
    p1:=&ListNode{Val:-1,Next:pHead1}
    pHead1=p1
    q2:=pHead2
    for p1.Next!=nil&&pHead2!=nil{
        if p1.Next.Val<pHead2.Val{ //小则进一步
            p1=p1.Next
        }else{                      //大则 插入  各进一步
            q2=pHead2.Next
            pHead2.Next=p1.Next
            p1.Next=pHead2
            pHead2=q2
            p1=p1.Next
        }
    }
    if pHead2!=nil{                  //看看没完的话 加到后面
        p1.Next=pHead2
    }
    return pHead1.Next
}

合并K个已排序的链表 #

合并 k 个升序的链表并将结果作为一个升序的链表返回其头节点。

输入:[{1,2},{1,4,5},{6}]
返回值:{1,1,2,4,5,6}
func mergeKLists( lists []*ListNode ) *ListNode {
    if len(lists)==0{   //排除特殊值
        return nil
    }
    if len(lists)==1{
        return lists[0]
    }
    pHead1:=lists[0]
    for i:=1;i<len(lists);i++{   //没啥好说的 两两结合
        pHead1=Merge(pHead1,lists[i])
    }
    return pHead1

}
func Merge( pHead1 *ListNode ,  pHead2 *ListNode ) *ListNode {//把一个链表往另一里面插
    p1:=&ListNode{Val:-1,Next:pHead1}
    pHead1=p1
    q2:=pHead2
    for p1.Next!=nil&&pHead2!=nil{
        if p1.Next.Val<pHead2.Val{ //小则进一步
            p1=p1.Next
        }else{                      //大则 插入  各进一步
            q2=pHead2.Next
            pHead2.Next=p1.Next
            p1.Next=pHead2
            pHead2=q2
            p1=p1.Next
        }
    }
    if pHead2!=nil{                  //看看没完的话 加到后面
        p1.Next=pHead2
    }
    return pHead1.Next
}

判断链表中是否有环 #

判断给定的链表中是否有环。如果有环则返回true,否则返回false。

func hasCycle( head *ListNode ) bool { //快慢指针 有环就会追上相等
    if head==nil{
        return false
    }
    p,q:=head,head.Next
    for p!=nil&&q!=nil&&q.Next!=nil{
        if p==q{
            return true
        }
        p=p.Next
        q=q.Next.Next
    }
    return false
}

链表中环的入口结点 #

给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。

例如,输入{1,2},{3,4,5}时,对应的环形链表如下图所示:

可以看到环的入口结点的结点值为3,所以返回结点值为3的结点。

输入:{1,2},{3,4,5}
返回值:3
说明:返回环形链表入口结点,我们后台程序会打印该环形链表入口结点对应的结点值,即3   
//先快慢指针找到有环,然后让p从头节点继续往下走,会在入口点相遇
func EntryNodeOfLoop(pHead *ListNode) *ListNode{
    slow,fast,p:=pHead,pHead,pHead
    for fast!=nil&&fast.Next!=nil{
        slow=slow.Next
        fast=fast.Next.Next
        if slow==fast{   //快慢指针相遇,证明有环
            for fast!=p{   //两个继续走 会在入口点相遇
                fast=fast.Next
                p=p.Next
            }
            return p
        }
    }
    return nil
}

链表中倒数最后K个结点 #

输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。

如果该链表长度小于k,请返回一个长度为 0 的链表。

例如输入{1,2,3,4,5},2时,对应的链表结构如下图所示:

其中蓝色部分为该链表的最后2个结点,所以返回倒数第2个结点(也即结点值为4的结点)即可,系统会打印后面所有的节点来比较。

输入:{1,2,3,4,5},2
返回值:{4,5}
func FindKthToTail( pHead *ListNode ,  k int ) *ListNode {//快慢指针
    pre:=pHead
    i:=0
    for pre!=nil{
        pre=pre.Next
        i++
        if i==k{ //相等就两个同步往后
            s:=pHead
            for pre!=nil{  //为空跳出
                pre=pre.Next
                s=s.Next
            }
            return s  //返回
        }
    }
    return nil  //否则证明k太大,返回nil
}

删除链表点倒数第n个节点 #

给定一个链表,删除链表的倒数第 n 个节点并返回链表的头指针

例如,

给出的链表为: 1→2→3→4→5, n=2. 删除了链表的倒数第 n个节点之后,链表变为1→2→3→5.

输入:{1,2},2    
返回值:{2} 
func removeNthFromEnd( head *ListNode ,  n int ) *ListNode {//快慢指针
    pre:=&ListNode{Val:-1,Next:head} //在他前面加一个
    head=pre
    i:=0
    for pre!=nil{
      pre=pre.Next
      i++
      if i==n{
        slow:=head
        for pre.Next!=nil{ //跳出循环到头了,此时slow 到达它前一个位置
          slow=slow.Next
          pre=pre.Next
        }
        p:=slow.Next
        slow.Next=slow.Next.Next
        p.Next=nil  //释放出来
        return head.Next
      }
    }
    return head.Next
}

两个链表的第一个公共节点 #

输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

例如,输入{1,2,3},{4,5},{6,7}时,两个无环的单向链表的结构如下图所示:

可以看到它们的第一个公共结点的结点值为6,所以返回结点值为6的结点。

输入:{1,2,3},{4,5},{6,7}
返回值:{6,7}
func FindFirstCommonNode( pHead1 *ListNode ,  pHead2 *ListNode ) *ListNode {
    if pHead1==nil||pHead2==nil{
        return nil
    }
    p1,p2:=pHead1,pHead2
    for p1!=p2{  //两个链表长度的和是相等的
        if p1==nil{   //一个到头了让他走另一个走过的路
            p1=pHead2
        }else{
            p1=p1.Next
        }
        if p2==nil{
            p2=pHead1
        }else{
            p2=p2.Next
        }
    }
    return p1
}

链表相加(二) #

假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。

给定两个这种链表,请生成代表两个整数相加值的结果链表。

输入:[9,3,7],[6,3]
返回值:{1,0,0,0}
func addInList( head1 *ListNode ,  head2 *ListNode ) *ListNode {  //翻转链表,从后面加
    head1=reversList(head1)  //翻转
    head2=reversList(head2)
    head:=&ListNode{Val:-1}  //创建链表
    dummy:=head
    ans:=0
    for head1!=nil||head2!=nil||ans!=0{
        if head1!=nil{
            ans=ans+head1.Val
            head1=head1.Next
        }
        if head2!=nil{
            ans=ans+head2.Val
            head2=head2.Next
        }
        dummy.Next=&ListNode{Val: ans%10}  //给Val赋值
        ans=ans/10      //大于10的取余留下来
        dummy=dummy.Next   //往后退 连起来
    }
    return reversList(head.Next)  //最后答案也要翻转
}
func reversList(head *ListNode)*ListNode{ //翻转链表
      if head1==nil{ 
        return nil
    }
    pre:=&ListNode{Val:-1,Next:head}
    head=pre
    pre=pre.Next
    q:=pre
    for pre.Next!=nil{
        q=pre.Next
        pre.Next=q.Next
        q.Next=head.Next
        head.Next=q
    }
    return head.Next
}

单链表的排序 #

给定一个节点数为n的无序单链表,对其按升序排序。

输入:{1,3,2,4,5}
返回值:{1,2,3,4,5}
输入:{-1,0,-2}
返回值:{-2,-1,0}
func sortInList( head *ListNode ) *ListNode {//二路归并排序
   return sortList(head,nil)
}
func sortList(head,tail *ListNode)*ListNode{ //头和尾 这里尾是空 指下一个
    if head==nil{
        return head
    }
    if head.Next==tail{ //代表只剩下1个值  这一步将链表最终一个一个的单链表 每个只有一个值
        head.Next=nil
        return head
    }
    fast,slow:=head,head
    for fast!=tail&&fast.Next!=tail{ //最快的到达末尾 slow 刚好在中间
        fast=fast.Next
        slow=slow.Next
    }
    mid:=slow
    return merger(sortList(head,mid),sortList(mid,tail))//合并递归   最主要的地方
}
func merger(head1,head2 *ListNode)*ListNode{
    head:=&ListNode{Val:-1}   //新建一个链表
    p:=head
    for head1!=nil&&head2!=nil{   //两个都不为空时
        if head1.Val<=head2.Val{   //那个小就连那个
            head.Next=head1
            head1=head1.Next
        }else{
            head.Next=head2
            head2=head2.Next
        }
        head=head.Next
    }
    if head2!=nil{             //剩余的接上去
        head.Next=head2
    }
    if head1!=nil{
        head.Next=head1
    }
    return p.Next
}

判断一个链表是否为回文结构 #

给定一个链表,请判断该链表是否为回文结构。

回文是指该字符串正序逆序完全一致。

输入:{1}
返回值:true
func isPail( head *ListNode ) bool {
    fast,slow:=head,head
    marry:=[]int{}
    for fast!=nil&&fast.Next!=nil{  //找中间值的时候把前半部分放入切片
        marry=append(marry,slow.Val)
        slow=slow.Next
        fast=fast.Next.Next
    }
    if fast!=nil{  //防止总数为奇数,在前进一下
        slow=slow.Next
    }
    for i:=len(marry)-1;i>=0;i--{//按个比较
        if marry[i]!=slow.Val{
            return false
        }
        slow=slow.Next  //记得这里也要后退
    }
    return true
}

链表的奇偶重排 #

给定一个单链表,请设定一个函数,将链表的奇数位节点和偶数位节点分别放在一起,重排后输出。

要求:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)

输入:{1,2,3,4,5,6}
返回值:{1,3,5,2,4,6}
func oddEvenList( head *ListNode ) *ListNode {
    if head==nil||head.Next==nil{
        return head
    }
    odd,odd1,even,even1:=head,head,head.Next,head.Next
    for odd!=nil&&odd.Next!=nil&&even!=nil&&even.Next!=nil{  //将奇偶 链表分开
        odd.Next=odd.Next.Next
        odd=odd.Next
        even.Next=even.Next.Next
        even=even.Next
    }
    odd.Next=even1 //把偶接到后面
    return odd1
}

删除有序链表中重复的元素1 #

删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次

进阶:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)

输入:{1,1,2}
返回值:{1,2}
func deleteDuplicates( head *ListNode ) *ListNode {
    if head==nil||head.Next==nil{
      return head
    }
    pre:=head
    for pre!=nil&&pre.Next!=nil{ //往后查
      if pre.Val==pre.Next.Val{ //如果相等
        pre.Next=pre.Next.Next  //让下一个指向下下一个
      }else{
        pre=pre.Next            //如果不想等,再进一步 防止{1,1,1,1}
      }
    }
    return head
}

删除有序链表中重复的元素2 #

给出一个升序排序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。

进阶:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)

输入:{1,1,2}
返回值:{1}
func deleteDuplicates( head *ListNode ) *ListNode {
    if head==nil||head.Next==nil{
      return head
    }
    p:=&ListNode{Val:-1,Next:head}
    head=p
    q:=p
    x:=false   //标记值,true代表这个数字重复,要去掉
    q=q.Next  //p一直在q的前一个位置
    for q!=nil&&q.Next!=nil{
      if q.Val==q.Next.Val{  //相等 就让q跳到下下一个先去重
        q.Next=q.Next.Next
        x=true
      }else{             //不在重复时
        if x==true{     //看是否有标记
          q=q.Next       //q 正常
          p.Next=p.Next.Next   //如果有,p跳到下下一个
          x=false
        }else{         //如果没有标记 正常后退
          q=q.Next
          p=p.Next
        }
      }
    }
    if x==true{       //看一下有没有遗漏的
      p.Next=p.Next.Next
    }
    return head.Next
}

二分查找/排序 #

二分查找1 #

请实现无重复数字的升序数组的二分查找

给定一个 元素升序的、无重复数字的整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标(下标从 0 开始),否则返回 -1

输入:[-1,0,3,4,6,10,13,14],13
返回值:6
func search( nums []int ,  target int ) int {
    left,right:=0,len(nums)-1
    mid:=0
    for left<=right{
        mid=left+(right-left)/2    //二分查找精髓
        if nums[mid]==target{
            return mid
        }
        if nums[mid]>target{
            right=mid-1
        }else{
            left=mid+1
        }
    }
    return -1
}

二维数组中的查找 #

在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

[

[1,2,8,9], [2,4,9,12], [4,7,10,13], [6,8,11,15]

]

给定 target = 7,返回 true。

给定 target = 3,返回 false。

数据范围:矩阵的长宽满足 0≤n,m≤5000≤n,m≤500 , 矩阵中的值满足 0≤val≤1090≤val≤109 进阶:空间复杂度 O(1)O(1) ,时间复杂度 O(n+m)O(n+m)

输入:
7,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]
返回值:true
func Find( target int ,  array [][]int ) bool { //关键在于左边的比你小,你下面的比你大,故从右上开始
    m,n:=len(array),len(array[0])
    for i,j:=0,n-1;i<m&&j>=0;{
        if array[i][j]==target{
            return true
        }
        if array[i][j]>target{   //往左下查找
            j--
        }else{
            i++
        }
    }
    return false
}

寻找峰值 #

给定一个长度为n的数组nums,请你找到峰值并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个所在位置即可。

1.峰值元素是指其值严格大于左右相邻值的元素。严格大于即不能有等于 //注意相邻不会出现等于

2.假设 nums[-1] = nums[n] = −∞

3.对于所有有效的 i 都有 nums[i] != nums[i + 1]

4.你可以使用O(logN)的时间复杂度实现此问题吗?

如输入[2,4,1,2,7,8,4]时,会形成两个山峰,一个是索引为1,峰值为4的山峰,另一个是索引为5,峰值为8的山峰,如下图所示:

输入:[2,4,1,2,7,8,4]
复制返回值:1
func findPeakElement(nums []int) int {  //利用二分法
    n := len(nums)
    // 辅助函数,输入下标 i,返回 nums[i] 的值
    // 方便处理 nums[-1] 以及 nums[n] 的边界情况
    get := func(i int) int {     //判断函数,如果是-1或者n,输出最小的值
        if i == -1 || i == n {
            return math.MinInt64
        }
        return nums[i]
    }

    left, right := 0, n-1
    for {
        mid := left+(right-left) / 2
        if get(mid-1) < get(mid) && get(mid) > get(mid+1) {  //如果是则输出
            return mid
        }
        if get(mid) < get(mid+1) {  //不是则改变left和right的值
            left = mid + 1
        } else {
            right = mid - 1
        }
    }
}
func findPeakElement( nums []int ) int {  //跟上面很像
    left,right:=0,len(nums)-1
    i:=0
    for left<right{
        i=left+(right-left)/2  //中间值
        if i-1>-1&&i+1<len(nums){  //判断不在两边的情况
            if nums[i]>nums[i+1]&&nums[i]>nums[i-1]{    
                return i
            }
            if nums[i]<nums[i+1]{
                left=i+1
            }else{
                right=i-1
            }
        }
        if i-1==-1{  //如果在最左边
            if nums[i]>nums[i+1]{
                return i
            }
            left=i+1   //这里++可能退出循环,left==right
        }
        if i+1==len(nums){  //如果在最右边
            if nums[i]>nums[i-1]{
                return i
            }
            right=i-1 //这里--可能退出循环,left==right
        }
    }
    return left  //所以要输入left的值
}

数组中的逆序对 #

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P mod 1000000007

要求:空间复杂度 O(n),时间复杂度 O(nlogn)

输入:[1,2,3,4,5,6,7,0]
返回值:7
func InversePairs(data []int) int {
	return mergeSort(data, 0, len(data)-1) % 1000000007
}
func mergeSort(data []int, left int, right int) int {
	if left >= right {
		return 0
	}
	mid := left + (right-left)/2
	cnt := mergeSort(data, left, mid) + mergeSort(data, mid+1, right) //计数+分开
	tmp := []int{}
	i, j := left, mid+1 //指向两个数组开头,开始合并
	for i <= mid && j <= right {
		if data[i] <= data[j] {
			tmp = append(tmp, data[i]) //插入
			i++
		} else {
			tmp = append(tmp, data[j])
			cnt = cnt + mid - i + 1 //第一个数组是有序的 此时有mid-i个数比右边大,再+1个
			j++
		}
	}
	for ; i <= mid; i++ { //第一个数组有剩余
		tmp = append(tmp, data[i])
	}
	for ; j <= right; j++ { //第二个数组有剩余
		tmp = append(tmp, data[j])
	}
	for i := left; i <= right; i++ { //要改变data里面排序的结果 赋值
		data[i] = tmp[i-left]
	}
	return cnt
}

旋转数组的最小数字 #

有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。

要求:空间复杂度:O(1),时间复杂度:O(logn)

输入:[3,4,5,1,2]
返回值:1
func minNumberInRotateArray( rotateArray []int ) int {
    n:=len(rotateArray)
    if n==0{
        return 0
    }
    left,right:=0,n-1
    mid:=0
    for left<right{
        mid=left+(right-left)/2
        if rotateArray[mid]>rotateArray[right]{ //中间比最右边大 一定在右边
            left=mid+1
        }else if rotateArray[mid]<rotateArray[right]{
            right=mid   //不能mid-1,因为有可能mid就是最小的值
        }else{
            right=right-1   //相等的话 减一个 
        }
    }
    return rotateArray[left]
}

比较版本号 #

牛客项目发布项目版本时会有版本号,比如1.02.11,2.14.4等等

现在给你2个版本号version1和version2,请你比较他们的大小

版本号是由修订号组成,修订号与修订号之间由一个".“连接。1个修订号可能有多位数字组成,修订号可能包含前导0,且是合法的。例如,1.02.11,0.1,0.2都是合法的版本号

每个版本号至少包含1个修订号。

修订号从左到右编号,下标从0开始,最左边的修订号下标为0,下一个修订号下标为1,以此类推。

比较规则:

一. 比较版本号时,请按从左到右的顺序依次比较它们的修订号。比较修订号时,只需比较忽略任何前导零后的整数值。比如"0.1"和"0.01"的版本号是相等的

二. 如果版本号没有指定某个下标处的修订号,则该修订号视为0。例如,“1.1"的版本号小于"1.1.1”。因为"1.1"的版本号相当于"1.1.0”,第3位修订号的下标为0,小于1

三. version1 > version2 返回1,如果 version1 < version2 返回-1,不然返回0.

进阶: 空间复杂度 O(1) , 时间复杂度 O(n)

输入:"1.1","2.1"
返回值:-1
func compare( version1 string ,  version2 string ) int {
    n1,n2:=len(version1),len(version2)
    i,j:=0,0
    for i<n1||j<n2{  //双指针
        x:=0
        for ;i<n1&&version1[i]!='.';i++{ //i<n1,且没有到.的时候
            x=x*10+int(version1[i]-'0')
        }
        i++ //跳过.号
        y:=0
        for ;j<n2&&version2[j]!='.';j++{
            y=y*10+int(version2[j]-'0')
        }
        j++ //跳过.号
        if x>y{
            return 1
        }
        if x<y{
            return -1
        }
    }
    return 0    //相等
}

二叉树 #

二叉树的前序遍历 #

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

数据范围:二叉树的节点数量满足 1≤n≤100,二叉树节点的值满足 1≤val≤100,树的各节点的值各不相同

输入:{1,#,2,3}
返回值:[1,2,3]
func preorderTraversal( root *TreeNode ) []int {
    marry:=[]int{}
    var preNode func(root *TreeNode)
    preNode=func(root *TreeNode){
        if root==nil{     //为空则返回 要记着
            return
        }
        marry=append(marry,root.Val)
        preNode(root.Left)
        preNode(root.Right)
    }
    preNode(root)
    return marry
}

二叉树的中序遍历 #

func inorderTraversal( root *TreeNode ) []int {
    marry:=[]int{}
    var inoderNode func(root *TreeNode)
    inoderNode=func(root *TreeNode){
        if root==nil{
            return
        }
        inoderNode(root.Left)
        marry=append(marry,root.Val)
        inoderNode(root.Right)
    }
    inoderNode(root)
    return marry
}

二叉树的后序遍历 #

func postorderTraversal( root *TreeNode ) []int {
    marry:=[]int{}
    var postNode func(root *TreeNode)
    postNode=func(root *TreeNode){
        if root==nil{
            return
        }
        postNode(root.Left)
        postNode(root.Right)
        marry=append(marry,root.Val)
    }
    postNode(root)
    return marry 
}

二叉树的层序遍历 #

给定一个二叉树,返回该二叉树层序遍历的结果,(从左到右,一层一层地遍历) 例如: 给定的二叉树是{3,9,20,#,#,15,7},

该二叉树层序遍历的结果是
[
[3],
[9,20],
[15,7] 
]
func levelOrder( root *TreeNode ) [][]int {
    marry:=[][]int{}
    if root==nil{
        return marry
    }
    arry:=[]*TreeNode{}
    arry=append(arry,root)
    for len(arry)>0{
        length:=len(arry)
        ry:=[]int{}
        for i:=0;i<length;i++{
            ry=append(ry,arry[i].Val)
            if arry[i].Left!=nil{
                arry=append(arry,arry[i].Left)
            }
            if arry[i].Right!=nil{
                arry=append(arry,arry[i].Right)
            }
        }
        arry=arry[length:]
        marry=append(marry,ry)
    }
    return marry
}

按之字形顺序打印二叉树 #

要求:空间复杂度:O(n)O(n),时间复杂度:O(n)O(n)

输入:{1,2,3,#,#,4,5}
返回值:[[1],[3,2],[4,5]]
func Print( pRoot *TreeNode ) [][]int {
    marry:=[][]int{}
    if pRoot==nil{
        return marry
    }
    queue:=[]*TreeNode{}
    queue=append(queue,pRoot)
    arry:=[]int{}
    del:=true
    for len(queue)>0{
        length:=len(queue)
        for i:=0;i<length;i++{
            arry=append(arry,queue[i].Val)
                if queue[i].Left!=nil{
                    queue=append(queue,queue[i].Left)
                } 
                if queue[i].Right!=nil{
                    queue=append(queue,queue[i].Right)
                }    
        }
        queue=queue[length:]
        if del==true{
            marry=append(marry,arry)
            del=!del
        }else{            
            for i,j:=0,len(arry)-1;i<j;i++{   //翻转数组
                arry[i],arry[j]=arry[j],arry[i]
                j--
            }
            marry=append(marry,arry)
        }        
        arry=[]int{} //清空
        del=!del
    }
    return marry
}
func zigzagLevelOrder(root *TreeNode) [][]int {
    marry:=[][]int{}
    if root==nil{
        return marry
    }
    queue:=[]*TreeNode{root}  //创建一个队列 // 当前层
    arry:=[]int{}
    x:=true   // 初始方向
    for len(queue)>0{
        queuelength:=len(queue)
        queue2:= []*TreeNode{} // 构造下一层
        for i:=0;i<queuelength;i++{
            if x==true{
                arry=append(arry,queue[i].Val)
            }else{
                arry=append([]int{queue[i].Val},arry...)// 添加元素到头部
            }
            if queue[i].Left!=nil{
                queue2=append(queue2,queue[i].Left)
            }
            if queue[i].Right!=nil{
                queue2=append(queue2,queue[i].Right)
            }  
        }
        marry=append(marry,arry)
        arry=[]int{}  //清空
        x=!x    //改变方向
        queue=queue2 // 更新当前层
    }
    return marry
}
执行用时0 ms, 在所有 Go 提交中击败了100.00%的用户
内存消耗2.8 MB, 在所有 Go 提交中击败了9.26%的用户

二叉树的最大深度 #

求给定二叉树的最大深度,

深度是指树的根节点到任一叶子节点路径上节点的数量。

最大深度是所有叶子节点的深度的最大值。

(注:叶子节点是指没有子节点的节点。)

数据范围:0≤n≤1000000,树上每个节点的val满足 ∣val∣≤100 要求: 空间复杂度 O(1),时间复杂度 O(n)

func maxDepth( root *TreeNode ) int {
    if root==nil{
        return 0
    }
    Leftdepth:=maxDepth(root.Left) //左子树深度
    Rightdepth:=maxDepth(root.Right) //右子树深度
    if Leftdepth>Rightdepth{   //那个大
        return Leftdepth+1   //加上本层的
    }
    return Rightdepth+1
}

二叉树中和为某一值的路径(一) #

给定一个二叉树root和一个值 sum ,判断是否有从根节点到叶子节点的节点值之和等于 sum 的路径。

1.该题路径定义为从树的根结点开始往下一直到叶子结点所经过的结点

2.叶子节点是指没有子节点的节点

3.路径只能从父节点到子节点,不能从子节点到父节点

4.总节点数目为n 例如: 给出如下的二叉树, sum=22

返回true,因为存在一条路径 5→4→11→2的节点值之和为 22

数据范围:

1.树上的节点数满足 0≤n≤10000

2.每 个节点的值都满足 ∣val∣≤1000

要求:空间复杂度 O(n),时间复杂度 O(n)

进阶:空间复杂度 O(树的高度),时间复杂度 O(n)

func hasPathSum( root *TreeNode ,  sum int ) bool {  
    return PathSum(root,0,sum)
}
func PathSum(root *TreeNode,m int,sum int)bool{
   if root==nil{
     return false
   }
    root.Val=root.Val+m
    if root.Left==nil&&root.Right==nil&&root.Val==sum{
      return true
    }
    return PathSum(root.Left,root.Val,sum)||PathSum(root.Right,root.Val,sum)
}
func hasPathSum(root *TreeNode, targetSum int) bool {
    if root==nil{
        return false
    }
    if root.Left==nil&&root.Right==nil{
        return targetSum-root.Val==0
    }
    return hasPathSum(root.Left,targetSum-root.Val)||hasPathSum(root.Right,targetSum-root.Val)
}

二叉搜索树与双向链表 #

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。如下图所示

数据范围:输入二叉树的节点数 0≤n≤1000,二叉树中每个节点的值 0≤val≤1000 要求:空间复杂度O(1)(即在原树上操作),时间复杂度 O(n)

注意: 
1.要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继
2.返回链表中的第一个节点的指针
3.函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构 
4.你不用输出双向链表,程序会根据你的返回值自动打印输出
输入描述:二叉树的根节点
返回值描述:双向链表的其中一个头节点。
//二叉搜索树直接想到中序遍历,把中序遍历先写出来。
func Convert( pRootOfTree *TreeNode ) *TreeNode {
    // write code here
    var dfs func(node *TreeNode)
    dfs = func(node *TreeNode) {
        if node == nil {
            return
        }
        dfs(node.Left)
        //  插入代码
        dfs(node.Right)
    }
    dfs(pRootOfTree)
    return ...
}
//之后有三点需要注意:
//1、当前代码中有什么?——当前代码中只有当前节点。
//2、连接两个节点需要什么?——需要当前节点的前一个节点。
//3、输出结果需要什么?——需要保存head
//所以需要pre和head变量,确定最左下角节点为head,同时也可以确定pre节点,head节点不需要和其他节点连接,所以head后直接右子节点。
//遍历到其他节点时,直接与pre相连
func Convert( pRootOfTree *TreeNode ) *TreeNode {
    var pre, head *TreeNode
    var dfs func(node *TreeNode)
    dfs = func(node *TreeNode) {
        if node == nil {
            return
        }
        dfs(node.Left)
        if pre == nil {
            pre = node
            head = node
        } else {
            pre.Right = node
            node.Left = pre
            pre = node
        }
        dfs(node.Right)
    }
    dfs(pRootOfTree)
    return head
}

对称的二叉树 #

给定一棵二叉树,判断其是否是自身的镜像(即:是否对称)

要求:空间复杂度 O(n),时间复杂度 O(n)

func isSymmetrical( pRoot *TreeNode ) bool {
    if  pRoot==nil{
        return true
    }
    return istrical(pRoot.Left,pRoot.Right) 
}
func istrical(Left,Right *TreeNode)bool{
    if Left==nil&&Right==nil{
        return true
    }
    if Left==nil||Right==nil{
        return false
    }
    if Left.Val!=Right.Val{
        return false
    }
    return istrical(Left.Left,Right.Right)&&istrical(Left.Right,Right.Left)
}

合并二叉树 #

已知两颗二叉树,将它们合并成一颗二叉树。合并规则是:都存在的结点,就将结点值加起来,否则空的位置就由另一个树的结点来代替。进阶:空间复杂度 O(1),时间复杂度 O(n)

func mergeTrees( t1 *TreeNode ,  t2 *TreeNode ) *TreeNode {
    t:=t1
    mergeT(t1,t2)
    return t
}
func mergeT(t1 *TreeNode,t2 *TreeNode){
    if t1==nil&&t2==nil{
        return
    }
    if t1!=nil&&t2==nil{
        return 
    }
    if t1!=nil&&t2!=nil{
        t1.Val=t1.Val+t2.Val
    }
    if t1.Left==nil{ //t1左为空         
        t1.Left=t2.Left  //t2的子树给t1
        t2.Left=nil   //t2断开 
    }
    if t1.Right==nil{ 
        t1.Right=t2.Right
        t2.Right=nil        
    }
    mergeTrees(t1.Left,t2.Left)
    mergeTrees(t1.Right,t2.Right)
}

二叉树的镜像 #

操作给定的二叉树,将其变换为源二叉树的镜像。

要求: 空间复杂度 O(n)。本题也有原地操作,即空间复杂度 O(1)的解法,时间复杂度 O(n)

比如:

源二叉树

镜像二叉树

func Mirror( pRoot *TreeNode ) *TreeNode {
    p:=pRoot
    mirr(pRoot)
    return p
}
func mirr(pRoot *TreeNode){   //巧妙递归
    if pRoot==nil{    //为空则返回
        return 
    }
    pRoot.Left,pRoot.Right=pRoot.Right,pRoot.Left  //转换左右子树
    Mirror(pRoot.Left)  //递归
    Mirror(pRoot.Right)
}

判断是不是二叉搜索树 #

给定一个二叉树根节点,请你判断这棵树是不是二叉搜索树。

二叉搜索树满足每个节点的左子树上的所有节点均小于当前节点且右子树上的所有节点均大于当前节点。

输入:{1,2,3}
返回值:false
func isValidBST( root *TreeNode ) bool {
    if root==nil{
        return false
    }
    c:=true
    var pre *TreeNode    //从二叉树变双链表学的 建立前指针
    var dfs func(root *TreeNode)
    dfs=func(root *TreeNode){
        if root==nil{
            return
        }
        dfs(root.Left)
        if pre==nil{
            pre=root
        }else{
            if pre.Val>root.Val{  //如果大
                c=false    //改变值
            }else{
                pre=root   //否则 改变pre位置
            }
        }
        dfs(root.Right)
    }
    dfs(root)
    return c
}

判断是不是完全二叉树 #

给定一个二叉树,确定他是否是一个完全二叉树。

完全二叉树的定义:若二叉树的深度为 h,除第 h 层外,其它各层的结点数都达到最大个数,第 h 层所有的叶子结点都连续集中在最左边,这就是完全二叉树。(第 h 层可能包含 [1~2h] 个节点)

输入:{1,2,3,4,5,#,6}
返回值:false
func isCompleteTree( root *TreeNode ) bool {  //层序遍历套路
    if root==nil{
        return false
    }
    queue:=[]*TreeNode{}
    queue=append(queue,root)
    sent2:=true
    for len(queue)>0{
        length:=len(queue)
        for i:=0;i<length;i++{
            if queue[i].Left!=nil{    //左不为空
                if sent2==false{    //如果左不为空 右标记 证明下一层了 不行
                    return false
                }
                queue=append(queue,queue[i].Left)   //左加入
                if queue[i].Right!=nil{                    
                    queue=append(queue,queue[i].Right)  //右不为空右加入
                }else{
                    sent2=false  //给一个标记 这一行没完    //如果右为空 给个标记不能再加入了
                }           
            }else{
                if queue[i].Right!=nil{    //如果左为空,右不为空
                    return false
                }else{                       //左为空,右也为空,给个标记 不能再加入了
                    sent2=false
                }
            }      
        }
        queue=queue[length:]
    }
    return true
}

判断是不是平衡二叉树 #

输入一棵节点数为 n 二叉树,判断该二叉树是否是平衡二叉树。

在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树

平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

func IsBalanced_Solution( pRoot *TreeNode ) bool {
    if pRoot==nil{
        return true
    }
    if solution(pRoot)==-1{
        return false
    }
    return true   
}
func solution(root *TreeNode)int{
        if root==nil{            //最底层都让他等于1
            return 1
        }
        l:=solution(root.Left)   //左边树高度
        r:=solution(root.Right)  //右边树高度
        if l==-1||r==-1{         //-1就结束了 往上传
            return -1
        }
        if l-r>1||r-l>1{         //超了  就-1
            return -1
        }
        if l>r{
            return l+1   //找最大的+1
        }
        return r+1
}

二叉搜索树的最近公共祖先 #

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

1.对于该题的最近的公共祖先定义:对于有根树T的两个节点p、q,最近公共祖先LCA(T,p,q)表示一个节点x,满足x是p和q的祖先且x的深度尽可能大。在这里,一个节点也可以是它自己的祖先.

2.二叉搜索树是若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值; 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值

3.所有节点的值都是唯一的。

4.p、q 为不同节点且均存在于给定的二叉搜索树中。

数据范围:

3<=节点总数<=10000

0<=节点值<=10000

输入:{7,1,12,0,4,11,14,#,#,3,5},1,12
返回值:7
说明:节点1 和 节点12的最近公共祖先是7 
func lowestCommonAncestor( root *TreeNode ,  p int ,  q int ) int {
    if p>q{     //如果第一个大 交换一下顺序
        p,q=q,p
    }
    var m int   //定义一个记录值
    var cestor func(root *TreeNode,p int,q int)
    cestor=func(root *TreeNode,p int,q int){
        if p<root.Val&&q<root.Val{   //都比根小 记录根  往左
            m=root.Val
            cestor(root.Left,p,q)           
        }
        if p>root.Val&&q>root.Val{   //都比根大,记录根 往右
            cestor(root.Right,p,q)
        }
        if p<root.Val&&q>root.Val{   //一左一右 刚刚好 输出根
            m=root.Val
            return 
        }
        if p==root.Val||q==root.Val{  //相等了  也刚刚好 ,输出
            m=root.Val
            return 
        }
    }
    cestor(root,p,q)
    return m
}

在二叉树中找到两个节点的最近公共祖先 #

给定一棵二叉树(保证非空)以及这棵树上的两个节点对应的val值 o1 和 o2,请找到 o1 和 o2 的最近公共祖先节点。

数据范围:树上节点数满足 1≤n≤105 , 节点值val满足区间 [0,n)

要求:时间复杂度 O(n)

输入:{3,5,1,6,2,0,8,#,#,7,4},5,1
返回值:3
func lowestCommonAncestor(root *TreeNode, o1 int, o2 int) int { //两次查询
	var p, q *TreeNode
	p, q = root, root
	marry1 := lowest(p, o1)
	marry2 := lowest(q, o2)
    n1,n2:=0,0
    if len(marry1)>len(marry2){   //给他排个序
        n1=len(marry1)
        n2=len(marry2)
    }else{
        n2=len(marry1)
        n1=len(marry2)
    }
	for i := 0; i < n1; i++ {  
        if i==n2{                  //这个是防止他前后都一样,例如 9    9,3,4 输出9
            return marry1[i-1]
        }
		if marry1[i] != marry2[i] {
			return marry1[i-1]
		}
	}
	return 0
}
func lowest(root *TreeNode, o int) []int { //找到那个点 并记录他的路径 返回
	marry := []int{}
	x := true
	var find func(root *TreeNode)
	find = func(root *TreeNode) {
		if root == nil {
			marry = append(marry, -1)   //防止回退点时候退过头了
			return
		}
		if x == true {
			marry = append(marry, root.Val)
		}  
		if root.Val == o {           //找到之后给个标识,后面就不继续了
			x = false
			return
		}
		find(root.Left)
		if x == true {
			marry = marry[:len(marry)-1]  //回退
		}
		find(root.Right)
		if x == true {
			marry = marry[:len(marry)-1]
		}
	}
	find(root)
	return marry
}
func lowestCommonAncestor(root *TreeNode, o1 int, o2 int) int { //递归
    if root == nil {   //到底了返回-1
        return -1
    }
    if root.Val == o1|| root.Val == o2 {  //找到了 返回root.val
        return root.Val
    }
    left := lowestCommonAncestor(root.Left, o1, o2)
    right := lowestCommonAncestor(root.Right, o1, o2)
    if left != -1 && right != -1 {  //证明左右子树都返回的不是-1两边都找到了 返回
        return root.Val
    }
    if left == -1 {  //左边找到了返回左边 左边没找到返回右边
        return right
    }
    return left
}

序列化二叉树 #

请实现两个函数,分别用来序列化和反序列化二叉树,不对序列化之后的字符串进行约束,但要求能够根据序列化之后的字符串重新构造出一棵与原二叉树相同的树。

二叉树的序列化(Serialize)是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树等遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#)

二叉树的反序列化(Deserialize)是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

层序序列化(即用函数Serialize转化)如上的二叉树转为"{1,2,3,#,#,6,7}",再能够调用反序列化(Deserialize)将"{1,2,3,#,#,6,7}“构造成如上的二叉树。

当然你也可以根据满二叉树结点位置的标号规律来序列化,还可以根据先序遍历和中序遍历的结果来序列化。不对序列化之后的字符串进行约束,所以欢迎各种奇思妙想。

数据范围:节点数 n≤100,树上每个节点的值满足 0≤val≤150

要求:序列化和反序列化都是空间复杂度 O(n),时间复杂度 O(n)

输入:{1,2,3,#,#,6,7}
返回值:{1,2,3,#,#,6,7}

重建二叉树 #

给定节点数为 n 的二叉树的前序遍历和中序遍历结果,请重建出该二叉树并返回它的头结点。

例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出如下图所示。

要求:空间复杂度 O(n),时间复杂度 O(n)

输入:[1,2,4,7,3,5,6,8],[4,7,2,1,5,3,8,6]
返回值:{1,2,3,4,#,5,6,#,7,#,#,8}
func reConstructBinaryTree( pre []int ,  vin []int ) *TreeNode {
    if len(pre)==0||len(vin)==0{
        return nil
    }
    n:=findnode(vin,pre[0])//在中序遍历中找到前序遍历的第一个值的位置
    root:=&TreeNode{       //递归构造树
        Val:pre[0],
        Left:reConstructBinaryTree(pre[1:n+1],vin[:n]),  //注意pre[1:n+1]他的左子树就是到n+1
        Right:reConstructBinaryTree(pre[n+1:],vin[n+1:]), //不要写成pre[1:] 后面就不管了
    }
    return root
}
func findnode(vin []int,n int)int{  //找到他返回下标,方便下面分割
    for i:=0;i<len(vin);i++{
        if vin[i]==n{
            return i
        }
    }
    return -1
}

输出二叉树的右视图 #

请根据二叉树的前序遍历,中序遍历恢复二叉树,并打印出二叉树的右视图

数据范围: 0≤n≤100000≤n≤10000 要求: 空间复杂度 O(n),时间复杂度 O(n)

如输入[1,2,4,5,3],[4,2,5,1,3]时,通过前序遍历的结果[1,2,4,5,3]和中序遍历的结果[4,2,5,1,3]可重建出以下二叉树:

输入:[1,2,4,5,3],[4,2,5,1,3]
返回值:[1,3,5]
func solve( xianxu []int ,  zhongxu []int ) []int {  //构建二叉树加层序遍历
    root:=treeNode(xianxu,zhongxu)
    arry:=[]int{}
    if root==nil{       //开始层序遍历
        return arry
    }
    queue:=[]*TreeNode{}
    queue=append(queue,root)
    for len(queue)>0{   //注意这里条件
        length:=len(queue)
        arry=append(arry,queue[length-1].Val)    //输出最右边的
        for j:=0;j<length;j++{           
            if queue[j].Left!=nil{
                queue=append(queue,queue[j].Left)
            }
            if queue[j].Right!=nil{
                queue=append(queue,queue[j].Right)
            }
        }        
        queue=queue[length:]
    }  
    return arry
}
func treeNode(xianxu []int ,  zhongxu []int)*TreeNode{ //构建树
    if len(xianxu)==0||len(zhongxu)==0{
        return nil
    }
    n:=find(zhongxu,xianxu[0])    
    root:=&TreeNode{
        Val:xianxu[0],
        Left:treeNode(xianxu[1:n+1],zhongxu[:n]),
        Right:treeNode(xianxu[n+1:],zhongxu[n+1:]),
    }
    return root
}
func find(zhongxu []int,val int)int{   //查找根节点在中序遍历中的位置
    for i:=0;i<len(zhongxu);i++{
        if zhongxu[i]==val{
            return i
        }
    }
    return -1
}

堆/栈/队列 #

用两个栈实现队列 #

用两个栈来实现一个队列,使用n个元素来完成 n 次在队列尾部插入整数(push)和n次在队列头部删除整数(pop)的功能。 队列中的元素为int类型。保证操作合法,即保证pop操作时队列内已有元素。

数据范围: n≤1000n≤1000

要求:存储n个元素的空间复杂度为 O(n) ,插入与删除的时间复杂度都是 O(1)

package main
var stack1 [] int
var stack2 [] int
func Push(node int) {   //入栈就是其中一个入
    stack1=append(stack1,node)
}
func Pop() int{    //出的时候 如果第二个不为空,出第二个,如果第二个为空,将第一个全部放到第二个,然后出
    if len(stack2)>0{
        m:=stack2[len(stack2)-1]
        stack2=stack2[:len(stack2)-1]
        return m
    }else{   //如果stack2为空
        for i:=len(stack1)-1;i>=0;i--{
            stack2=append(stack2,stack1[i])        
        }
        stack1=[]int{}
        n:=stack2[len(stack2)-1]
        stack2=stack2[:len(stack2)-1]
        return n
    }
}

包含min函数的栈 #

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的 min 函数,输入操作时保证 pop、top 和 min 函数操作时,栈中一定有元素。

此栈包含的方法有:

push(value):将value压入栈中

pop():弹出栈顶元素

top():获取栈顶元素

min():获取栈中最小元素

数据范围:操作数量满足 0≤n≤300 0≤n≤300 ,输入的元素满足 ∣val∣≤100000 进阶:栈的各个操作的时间复杂度是 O(1),空间复杂度是 O(n)

输入:["PSH-1","PSH2","MIN","TOP","POP","PSH1","TOP","MIN"]
返回值:-1,2,1,-1
package main
var stack []int
func Push(node int) {
    stack=append(stack,node)
}
func Pop() {
    stack=stack[:len(stack)-1]
}
func Top() int {
    m:=stack[len(stack)-1]
    return m
}
func Min() int {
    if len(stack)>0{
        m:=stack[0]
        for i:=1;i<len(stack);i++{
            if m>stack[i]{
                m=stack[i]
            }
        }
        return m
    }
    return 0
}

有效括号序列 #

给出一个仅包含字符’(’,’)’,’{’,’}’,’[‘和’]’,的字符串,判断给出的字符串是否是合法的括号序列 括号必须以正确的顺序关闭,”()“和”()[]{}“都是合法的括号序列,但”(]“和”([)]“不合法。

数据范围:字符串长度 0≤n≤10000

要求:空间复杂度 O(n),时间复杂度 O(n)

输入:"["
返回值:false
func isValid( s string ) bool {
    marry:=[]byte{}
    for _,i:=range s{
      if byte(i)=='('||byte(i)=='['||byte(i)=='{'{
        marry=append(marry,byte(i))
      }else{
        if len(marry)==0{ //如果栈空,就得到},返回false
          return false
        }
        if marry[len(marry)-1]=='('&&byte(i)==')'||marry[len(marry)-1]=='['&&byte(i)==']'||marry[len(marry)-1]=='{'&&byte(i)=='}'{   //如果成对 出栈      
              marry=marry[:len(marry)-1]
        }else{  //不成对 返回false
          return false
        }
      }
    }
    if len(marry)!=0{  //如果栈不为空返回false
      return false
    }
    return true
}
type Stack struct {            //定义栈
	size int
	top  int
	data []string
}
func isValid(s string) bool {
	s1 := Stack{}                  //初始化栈
	s1.size = len(s)
	s1.top = -1
	s1.data = make([]string, len(s))
	for _, a := range s {             //遍历s
		var b string
		if string(a) == ")" {           //设置出栈条件
			b = "("
		}
		if string(a) == "}" {
			b = "{"
		}
		if string(a) == "]" {
			b = "["
		}
		if s1.top > -1 && s1.data[s1.top] == b { //相等出栈
			s1.top--
		} else {                                //不等入栈
			s1.top++
			s1.data[s1.top] = string(a)
		}
	}
	if s1.top == -1 {                             //判断栈空为true
		return true
	} else {
		return false
	}
}

滑动窗口的最大值 #

给定一个长度为 n 的数组 num 和滑动窗口的大小 size ,找出所有滑动窗口里数值的最大值。

例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

窗口大于数组长度或窗口长度为0的时候,返回空。

数据范围: 1≤n≤10000,0≤size≤10000,数组中每个元素的值满足 ∣val∣≤10000

要求:空间复杂度 O(n),时间复杂度 O(n)

输入:[2,3,4,2,6,2,5,1],3
返回值:[4,4,6,6,6,5]
func maxInWindows(num []int, size int) []int {
	marry := []int{}
	if size > len(num) || size == 0 {
		return marry
	}
	if size == 1 {
		return num
	}
	queue := []int{}
	queue = append(queue, num[0])
	max := num[0]               //最大值
	x := 0                      //最大值下标
	for i := 1; i < size; i++ { //第一个滑块插入队列
		queue = append(queue, num[i])
		if num[i] > max {
			max = num[i]
			x = i
		}
	}
	marry = append(marry, max) //第一个滑块的最大值先加进去
	for i := size; i < len(num); i++ {
		queue = queue[1:]
		queue = append(queue, num[i])
		if i-x < size { //证明它没出去
			if num[i] > max {
				max = num[i]
				x = i
			}
		} else {
			max, x = find(queue, i)  //查找最大值和下标
		}
		marry = append(marry, max)  //加入最大值
	}
	return marry
}
func find(queue []int, n int) (max int, x int) {   //这样搞如果全是递减的话 时间复杂度就超了
	maxc := queue[0]
	xx := 0
	for i := 1; i < len(queue); i++ {
		if queue[i] > maxc {
			maxc = queue[i]
			xx = i
		}
	}
	return maxc, n - (len(queue) - 1) - xx
}
func maxInWindows(num []int, size int) []int {
	  marry := []int{}
    if size==0||len(num)==0||size>len(num){
        return marry
    }
	  queue := []int{}
    var push func(i int) //插入队列
    push=func(i int){  //你是大的就循环把你前面小的拿走 //因为小的没用
		for len(queue)>0&&num[i]>num[queue[len(queue)-1]]{ //如果比最右边大,则把最右的拿出来,再加入,比你小直接加入,比你大的话你就没用了 ,比你小怕你下一个就出去
            queue=queue[:len(queue)-1]
        }
        queue = append(queue, i)  //下标插进去 
    }
    for i:=0;i<size;i++{ //现将前几个都插入
        push(i)
    }
    marry=append(marry,num[queue[0]]) //第一个最大值插入
    for i:=size;i<len(num);i++{
        push(i)
        for queue[0]<=i-size{ //看你是不是被划出去了 如果是,队列退出一个
            queue=queue[1:]
        }
        marry=append(marry,num[queue[0]])
    }
	return marry
}

最小的K个数 #

给定一个长度为 n 的可能有重复值的数组,找出其中不去重的最小的 k 个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4(任意顺序皆可)。

数据范围:0≤k,n≤10000,数组中每个数的大小0≤val≤1000

要求:空间复杂度 O(n) ,时间复杂度 O(nlogk)

输入:[4,5,1,6,2,7,3,8],4 
输出:[1,2,3,4]
说明:
返回最小的4个数即可,返回[1,3,2,4]也可以
func GetLeastNumbers_Solution( input []int ,  k int ) []int {
    arry:=[]int{}
    length:=len(input)
    if k==0{
        return arry
    }
    if k>length||k==length{
        return input
    }
    for i:=0;i<k;i++{  //将前k个数写入arry
        arry=append(arry,input[i])
    }
    arry=heapScortMax(arry) //先做大根堆
    for j:=k;j<length;j++{ //从k开始往后
        if input[j]<arry[0]{  //遇到小的
            arry[0]=input[j]  //替换里面最大的
            arry=heapScortMax(arry) //调整堆
        }
    }
    return arry
}
func heapScortMax(arry []int)[]int{
    length:=len(arry)
    depth:=length/2-1
    for i:=depth;i>=0;i--{
        topmax:=i
        left:=i*2+1 //左子树
        right:=i*2+2 //右子树
        if left<=length-1&&arry[left]>arry[topmax]{
            topmax=left  //找到最大值
        }
        if right<=length-1&&arry[right]>arry[topmax]{
            topmax=right //找到最大值
        }
        if topmax!=i{
            arry[i],arry[topmax]=arry[topmax],arry[i]  //跟父节点做交换
        }
    }
    return arry
}

寻找第K大 #

有一个整数数组,请你根据快速排序的思路,找出数组中第 k 大的数。

给定一个整数数组 a ,同时给定它的大小n和要找的 k ,请返回第 k 大的数(包括重复的元素,不用去重),保证答案存在。

要求:时间复杂度 O(nlogn),空间复杂度 O(1)

数据范围:0≤n≤1000, 1≤K≤n,数组中每个元素满足 0≤val≤10000000

输入:[1,3,5,2,2],5,3
返回值:2
func findKth( a []int ,  n int ,  K int ) int {
    // write code here
    sort(a,0,n-1) //快速排序
    return a[K-1]
}
func sort(arry []int, left, right int) { //数组,左右下标
    if left>=right{  //防止越界
        return
    }
    i:=left
    j:=right
    topmax:=arry[i]  //定义topmax
    for i<j{
        for i<j&&arry[j]<topmax{ //后面的比较小
            j--   //继续
        }
        if i<j{ //arry[j]>=topmax  //证明后面比较大
            arry[i]=arry[j]  //换位置
            i++
        }
        for i<j&&arry[i]>topmax{  //前面比较大
            i++         //继续
        } 
        if i<j{  //arry[i]<=topmax  //换位置
            arry[j]=arry[i]
            j--
        }
    }
    arry[i]=topmax
    sort(arry,left,i-1)  //递归 
    sort(arry,i+1,right)
}

数据流中的中位数 #

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

数据范围:数据流中数个数满足 1≤n≤1000 ,大小满足 1≤val≤1000

进阶: 空间复杂度 O(n) , 时间复杂度 O(nlogn)

输入:[5,2,3,4,1,6,7,0,8]
返回值:"5.00 3.50 3.00 3.50 3.00 3.50 4.00 3.50 4.00 "
说明:数据流里面不断吐出的是5,2,3...,则得到的平均数分别为5,(5+2)/2,3...  
package main
import "sort"
var arry []int
func Insert(num int){
	arry=append(arry, num)
}
func GetMedian() float64{
	length:=len(arry)
    sort.Ints(arry) //排序  快速排序的时间复杂度为O(nlogn) 或者这里可以写个快排
    if length%2==0{
        return float64(arry[length/2]+arry[(length/2)-1])/2 //偶数时,中间相加/2
    }else{
        return float64(arry[length/2])  //奇数时,中间
    }
}

表达式求值 #

请写一个整数计算器,支持加减乘三种运算和括号。

数据范围:0≤∣s∣≤100,保证计算结果始终在整型范围内

要求:空间复杂度: O(n),时间复杂度 O(n)

输入:"(2*(3-4))*5"
返回值:-10
func solve(s string) int {  //最烂的代码
	length := len(s)
	arry := infixToSuffix(s, length)
	mm := []int{}
	m := 0
	for i := 0; i < len(arry); i++ {
		leng := len(mm)
		switch arry[i] {
		case "+":
			a := mm[leng-2]
			b := mm[leng-1]
			m = a + b
			mm[leng-2] = m
			mm = mm[:leng-1]
		case "-":
			a := mm[leng-2]
			b := mm[leng-1]
			m = a - b
			mm[leng-2] = m
			mm = mm[:leng-1]
		case "*":
			a := mm[leng-2]
			b := mm[leng-1]
			m = a * b
			mm[leng-2] = m
			mm = mm[:leng-1]
		case "/":
			a := mm[leng-2]
			b := mm[leng-1]
			m = a / b
			mm[leng-2] = m
			mm = mm[:leng-1]
		default:
			c, _ := strconv.Atoi(arry[i])
			mm = append(mm, c)
		}

	}
	return m

}
func infixToSuffix(s string, length int) []string { //将字符串转换为逆波兰算法
	arry := []string{}
	marry := []byte{} //设置一个栈
	for i := 0; i < length; i++ {
		if s[i] == '+' || s[i] == '-' { //第二优先级,
			if len(marry) != 0 && (marry[len(marry)-1] == '*' || marry[len(marry)-1] == '/') {
				for len(marry) != 0 && (marry[len(marry)-1] == '*' || marry[len(marry)-1] == '/') { //如果栈顶是第一优先级的
					arry = append(arry, string(marry[len(marry)-1])) //则出栈
					marry = marry[:len(marry)-1]
				}
				marry = append(marry, s[i])
			} else if len(marry) != 0 && (marry[len(marry)-1] == '+' || marry[len(marry)-1] == '-') {
				arry = append(arry, string(marry[len(marry)-1])) //则出栈
				marry[len(marry)-1] = s[i]
			} else {
				marry = append(marry, s[i])
			}
			
		}
		if s[i] == '(' || s[i] == '*' || s[i] == '/' { //第一优先级 直接入栈
			marry = append(marry, s[i])
		}
		if s[i] == ')' { //遇到右)证明有对()已配对,则从栈中拿符号 到arry,直到遇到(

			for j := len(marry) - 1; marry[j] != '('; j-- {
				arry = append(arry, string(marry[j]))
				marry = marry[:j]

			}
			marry = marry[:len(marry)-1] //证明遇到"("了,把它取出

		}
		if s[i] >= '0' && s[i] <= '9' { //如果是数字
			var ss string
			for i < length && s[i] >= '0' && s[i] <= '9' {
				ss = ss + string(s[i])
				i++
			}
			i--
			arry = append(arry, ss)
		}
	}
	if len(marry) > 0 {
		for j := len(marry) - 1; j >= 0; j-- {
			if marry[j] != '(' {
				arry = append(arry, string(marry[j]))
				marry = marry[:j]
			}
		}
	}
	return arry
}
type Stack struct {  //更加烂的代码
	size int
	top  int
	data []byte
}
func (s *Stack) IsEmpty() bool {
	return s.top == -1
}
func (s *Stack) IsFull() bool {
	return s.top == s.size-1
}
func (s *Stack) Push(data byte) bool {
	if s.IsFull() {
		fmt.Println("栈满了")
		return false
	}
	s.top++
	s.data[s.top] = data
	return true
}
func (s *Stack) Pop() byte {
	if s.IsEmpty() {
		fmt.Println("栈空了")
		return 0
	}
	tmp := s.data[s.top]
	s.top--
	return tmp
}
func (s *Stack) Popp() byte {
	if s.IsEmpty() {
		fmt.Println("栈空了")
		return 0
	}
	tmp := s.data[s.top]
	return tmp
}
func solve(s string) int {
	arry := infixToSuffix(s, len(s)) //将字符串转换为逆波兰算法
	mm := []int{}
	m := 0
	var cc func(mm []int, leng int) (a int, b int)
	cc = func(mm []int, leng int) (a int, b int) {
		a = mm[leng-2]
		b = mm[leng-1]
		return a, b
	}
	for i := 0; i < len(arry); i++ {
		leng := len(mm)
		switch arry[i] {
		case "+":   //遇到什么 先拿出两个计算 然后放入一个 继续
			a, b := cc(mm, leng)
			m = a + b
			mm[leng-2] = m
			mm = mm[:leng-1]
		case "-":
			a, b := cc(mm, leng)
			m = a - b
			mm[leng-2] = m
			mm = mm[:leng-1]
		case "*":
			a, b := cc(mm, leng)
			m = a * b
			mm[leng-2] = m
			mm = mm[:leng-1]
		case "/":
			a, b := cc(mm, leng)
			m = a / b
			mm[leng-2] = m
			mm = mm[:leng-1]
		default:  //如果不是符号则入栈
			c, _ := strconv.Atoi(arry[i]) //字符串转换为int
			mm = append(mm, c)
		}
	}
	return m
}
func infixToSuffix(s string, length int) []string { //将字符串转换为逆波兰算法
	arry := []string{}
	s1 := Stack{ //初始化一个栈
		size: len(s),
		top:  -1,
		data: make([]byte, len(s)+1),
	}
	for i := 0; i < length; i++ {
		if s[i] == '+' || s[i] == '-' { //第二优先级,
			if s1.IsEmpty() { //如果栈为空
				s1.Push(s[i]) //入栈
				continue
			} else { //不为空
				if s1.Popp() == '*' || s1.Popp() == '/' {
					for s1.Popp() == '*' || s1.Popp() == '/' { //如果栈顶为第一优先级的符号,则先加入切片,然后入栈
						ss := s1.Pop()
						arry = append(arry, string(ss)) //栈顶放入
					}
					s1.Push(s[i])
					continue
				}
				if s1.Popp() == '+' || s1.Popp() == '-' {//如果栈顶是第二优先级的,则拿出一个 放入一个
					arry = append(arry, string(s1.Pop()))
					s1.Push(s[i])
					continue
				}
				s1.Push(s[i]) //入栈
				continue
			}
		}
		if s[i] == '(' || s[i] == '*' || s[i] == '/' { //第一优先级 直接入栈
			s1.Push(s[i]) //入栈
			continue
		}
		if s[i] == ')' { //遇到右)证明有对()已配对,则从栈中拿符号 到arry,直到遇到(
			for s1.Popp() != '(' { //栈顶元素不是(  则将栈顶元素加入arry
				arry = append(arry, string(s1.Pop()))
			}
			s1.Pop() //证明遇到"("了,把它取出
			continue
		}
		if s[i] >= '0' && s[i] <= '9' { //如果是数字
			var ss string
			for i < length && s[i] >= '0' && s[i] <= '9' { //字符串拼接
				ss = ss + string(s[i])
				i++
			}
			i--
			arry = append(arry, ss)
			continue
		}
	}
	for s1.IsEmpty() == false { //将栈清空加入arry
		arry = append(arry, string(s1.Pop()))
	}
	return arry
}

哈希 #

两数之和 #

给出一个整型数组 numbers 和一个目标值 target,请在数组中找出两个加起来等于目标值的数的下标,返回的下标按升序排列。

(注:返回的数组下标从1开始算起,保证target一定可以由数组里面2个数字相加得到)

数据范围:2≤len(numbers)≤105,−10≤numbersi≤109,0≤target≤109

要求:空间复杂度 O(n),时间复杂度 O(nlogn)

输入:[3,2,4],6
返回值:[2,3]
说明:因为 2+4=6 ,而 2的下标为2 , 4的下标为3 ,又因为 下标2 < 下标3 ,所以返回[2,3]  
func twoSum( numbers []int ,  target int ) []int {
    mmap:=map[int]int{}
    for i,v:=range numbers{
        if p,ok:=mmap[target-v];ok{
            return []int{p,i+1}
        }
        mmap[v]=i+1
    }
    return nil
}

数组中出现次数超过一半的数字 #

给一个长度为 n 的数组,数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

例如输入一个长度为9的数组[1,2,3,2,2,2,5,4,2]。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。

数据范围:n≤50000,数组中元素的值 0≤val≤10000

要求:空间复杂度:O(1),时间复杂度 O(n)

输入:[1,2,3,2,2,2,5,4,2]
返回值:2
func MoreThanHalfNum_Solution( numbers []int ) int {
    length:=len(numbers)
    if length==1{   //排除只有一个的时候
        return numbers[0]
    }
    mmap:=map[int]int{}
    for _,v:=range numbers{
        if p,ok:=mmap[v];ok{
            mmap[v]=p+1
            if (p+1)>length/2{
                return v
            }
        }else{
            mmap[v]=1
        }
    }
    return -1
}

数组中只出现一次的两个数字 #

一个整型数组里除了两个数字只出现一次,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

数据范围:数组长度 2≤n≤1000,数组中每个数的大小 0<val≤1000000 要求:空间复杂度 O(1),时间复杂度 O(n)

提示:输出时按非降序排列。

输入:[1,4,1,6]
返回值:[4,6]
说明:返回的结果中较小的数排在前面   
func FindNumsAppearOnce( array []int ) []int {
    mmap:=map[int]int{}
    marry:=[]int{}
    for i:=0;i<len(array);i++{
        if _,ok:=mmap[array[i]];ok{
            delete(mmap,array[i])     //map删除
        }else{
             mmap[array[i]]=1
        }
    }
    for k,_:=range mmap{                //map遍历
        marry = append(marry, k)
    }
    if marry[0]>marry[1]{
        marry[0],marry[1]=marry[1],marry[0]
    }
    return marry
}

缺失的第一个正整数 #

给定一个无重复元素的整数数组nums,请你找出其中没有出现的最小的正整数

进阶: 空间复杂度 O(1),时间复杂度 O(n)

输入:[-2,3,4,1,5]
返回值:2

方法一 哈希表 #

空间复杂度 O(n),时间复杂度 O(n)

func minNumberDisappeared( nums []int ) int {  
    mmap:=map[int]int{}
    for _,v:=range nums{
        mmap[v]=1
    }
    for i:=1;i>0;{ //i从1开始
        if _,ok:=mmap[i];ok{
            i++
        }else{
            return i  //没查到就返回i
        }
    }
    return 0
}

方法二 原地哈希 #

空间复杂度 O(1),时间复杂度 O(n)

前面提到了数组要么缺失1~n中的某个数字,要么缺失n+1,而数组正好有下标0~n−1可以对应数字1~n1~n,因此只要数字1~n中某个数字出现,我们就可以将对应下标的值做一个标记,最后没有被标记的下标就是缺失的值。

func minNumberDisappeared( nums []int ) int {  
    for i,v:=range nums{  //第一遍,先把所有<=0的变成len(nums)+1
        if  v<=0{
            nums[i]=len(nums)+1
        }
    }
    for _,v:=range nums{//第二遍,把所有绝对值<=len(nums)的下标对应的值变为负数
       x:=int(math.Abs(float64(v)))//math.Abs(x float64)
        if x<=len(nums){
            nums[x-1]=-nums[x-1]
        }
    }
    for i,v:=range nums{ //第三遍,看那个不是负数,对应的下标+1就是答案
        if v>0{
            return i+1
        }
    }
    return len(nums)+1
}

三数之和 #

给出一个有n个元素的数组S,S中是否有元素a,b,c满足a+b+c=0?找出数组S中所有满足条件的三元组。

数据范围:0≤n≤1000,数组中各个元素值满足 ∣val∣≤100

空间复杂度:O(n2),时间复杂度 O(n2)

注意:

  1. 三元组(a、b、c)中的元素必须按非降序排列。(即a≤b≤c)
  2. 解集中不能包含重复的三元组。
输入:[-10,0,10,20,-10,-40]
返回值:[[-10,-10,20],[-10,0,10]]
func threeSum( num []int ) [][]int {
    length:=len(num)
    marry:=[][]int{}
    sort.Ints(num)//先对其进行排序
    for first:=0;first<length-2;first++{
        if first>0&&num[first]==num[first-1]{ //去重
            continue
        }
        second:=first+1 //第二个数
        third:=length-1 //第三个数
        for second<third{
            if second>first+1&&num[second]==num[second-1]{ //    去重,两个去重要注意
                second++
                continue
            }
            if num[first]+num[second]+num[third]<0{ //second太小了
                second++
                continue
            }
            if num[first]+num[second]+num[third]>0{//third 太大了
                third--
                continue
            }
            if num[first]+num[second]+num[third]==0{ //相等,放入,继续
                marry=append(marry,[]int{num[first],num[second],num[third]})
                second++
                continue
            }
        }
    }
    return marry
}

递归 #

没有重复项数字的全排列 #

给出一组数字,返回该组数字的所有排列

例如:

[1,2,3]的所有排列如下
[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2], [3,2,1].
(以数字在数组中的位置靠前为优先级,按字典序排列输出。)

数据范围:数字个数 0<n≤6

要求:空间复杂度 O(n!) ,时间复杂度 O(n!)

func permute( num []int ) [][]int { //烂
    marry:=[][]int{}
    arry:=[]int{}
    var backtrace func()
    backtrace=func() {
        if len(arry)==len(num){
            c:=make([]int,len(num))
            copy(c,arry)
            marry=append(marry,c)
        }
        for i:=0;i<len(num);i++{
            if lookarry(num[i],arry){   //如果这个值放入了则跳过
                continue 
            }
            arry=append(arry,num[i])
            backtrace()
            arry=arry[:len(arry)-1]
        }        
    }
    backtrace()
    return marry
}
func lookarry(a int,arry []int)bool{ //查询这个数字是否出现在arry中
    for i:=0;i<len(arry);i++{
        if arry[i]==a{
            return true
        }
    }
    return false
}
func permute(nums []int) [][]int {//比较难理解
	n := len(nums)
	num := make([][]int, 0)
	var backtrace func(path int)  //内置循环函数
	backtrace = func(path int) {
		if path == n {              //深度等于n 输出
			nu := make([]int, n)
			copy(nu, nums)             //不然会全部改变
			num = append(num, nu)
			return
		}
		for i := path; i < n; i++ {
			nums[path], nums[i] = nums[i], nums[path]   //交换位置
			backtrace(path + 1)                           //递归
			nums[path], nums[i] = nums[i], nums[path]     //撤销交换
		}
	}
	backtrace(0)
	return num
}

有重复项数字的全排列 #

给出一组可能包含重复项的数字,返回该组数字的所有排列。结果以字典序升序排列。

数据范围: 0<n≤8,数组中的值满足 −1≤val≤5

要求:空间复杂度 O(n!),时间复杂度 O(n!)

输入:[1,1,2]
返回值:[[1,1,2],[1,2,1],[2,1,1]]
func permuteUnique( num []int ) [][]int {
    sort.Ints(num) //先给它排序
    length:=len(num)
    marry:=[][]int{}
    arry:=[]int{}
    var backtrace func()
    backtrace=func(){
        if len(arry)==length{
            c:=make([]int,length)
            copy(c,arry)
            marry=append(marry,c)
        }
        for i:=0;i<len(num);i++{
            if i>0&&num[i]==num[i-1]{//证明这个选过了
                continue
            }
            cur:=num[i]
            arry=append(arry,num[i])
            num=append(num[:i],num[i+1:]...) //把这个数字从num中删除
            backtrace()
            num=append(num[:i],append([]int{cur},num[i:]...)...)//把这个数字又放回去
            arry=arry[:len(arry)-1]  //回退
        }
    }
    backtrace()
    return marry
}

岛屿数量 #

给一个01矩阵,1代表是陆地,0代表海洋, 如果两个1相邻,那么这两个1属于同一个岛。我们只考虑上下左右为相邻。

岛屿: 相邻陆地可以组成一个岛屿(相邻:上下左右) 判断岛屿个数。

输入
[
[1,1,0,0,0],
[0,1,0,1,1],
[0,0,0,1,1],
[0,0,0,0,0],
[0,0,1,1,1]
]
对应的输出为3
(注:存储的01数据其实是字符'0','1')
输入:[[1,1,0,0,0],[0,1,0,1,1],[0,0,0,1,1],[0,0,0,0,0],[0,0,1,1,1]]
返回值:3
func solve( grid [][]byte ) int {
    n:=0
    a:=len(grid)
    b:=len(grid[0])
    var backtrace func(i int,j int) //递归函数
    backtrace=func(i, j int) {
        grid[i][j]='0'                 //先把到的地方变为‘0’
        if i>=0&&i+1<a&&grid[i+1][j]=='1'{  //如果它[i+1][j]=='1'继续递归 变为0
            backtrace(i+1,j)
        }
        if i-1>=0&&i<a&&grid[i-1][j]=='1'{ //如果它[i-1][j]=='1'继续递归 变为0
            backtrace(i-1,j)
        }
        if j>=0&&j+1<b&&grid[i][j+1]=='1'{
            backtrace(i,j+1)
        }
        if j-1>=0&&j<b&&grid[i][j-1]=='1'{
            backtrace(i,j-1)
        }
    }
    for i:=0;i<a;i++{
        for j:=0;j<b;j++{
            if grid[i][j]=='1'{ //找到一个岛屿,n++然后递归
                n++                   
                backtrace(i,j) //深度遍历DFS递归函数 
            }
        }
    }
    return n  
}

字符串的排列 #

输入一个长度为 n 字符串,打印出该字符串中字符的所有排列,你可以以任意顺序返回这个字符串数组。

例如输入字符串ABC,则输出由字符A,B,C所能排列出来的所有字符串ABC,ACB,BAC,BCA,CBA和CAB。

数据范围:n<10n<10 要求:空间复杂度 O(n!),时间复杂度 O(n!)

输入:"aab"
返回值:["aab","aba","baa"]
输入:"abc"
返回值:["abc","acb","bac","bca","cab","cba"]
func Permutation(str string) []string {//和全排列二很像
	marry := []string{}
	length := len(str)
	T := []byte(str)                                          //把stc转成切片  注意这个写法
	sort.Slice(T, func(i, j int) bool { return T[i] < T[j] }) //排序 排序方法变了
	arry := []byte{}
	var backtrace func()
	backtrace = func() {
		if len(arry) == length {
			ss := make([]byte, length)
			copy(ss, arry)
			marry = append(marry, string(ss))
			return
		}
		for i := 0; i < len(T); i++ {
			if i > 0 && T[i] == T[i-1] { //去重
				continue
			}
			arry = append(arry, T[i])
			t := T[i]
			T = append(T[:i], T[i+1:]...) //T拿出一个
			backtrace()
			T = append(T[:i], append([]byte{t}, T[i:]...)...) //T又放回去
			arry = arry[:len(arry)-1]
		}
	}
	backtrace()
	return marry
}

N皇后问题 #

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。

括号生成 #

给出n对括号,请编写一个函数来生成所有的由n对括号组成的合法组合。

例如,给出n=3,解集为:
"((()))", "(()())", "(())()", "()()()", "()(())"

数据范围:0≤n≤10

要求:空间复杂度 O(n),时间复杂度 O(2n)

输入:2
返回值:["(())","()()"]
func generateParenthesis( n int ) []string {
    marry:=[]string{}  
    var backtrace func(l int,r int ,cur string)
    backtrace=func(l, r int, cur string) {
      if l==r&&l==n{ //当且仅当左右括号数=n时,进行收集答案
        marry=append(marry, cur)
      }
      if l<n{                      //如果左括号数<n,则 +1,cur+"("
        backtrace(l+1,r,cur+"(")
      }
      if r<l{                 //左括号数一定要比右括号大的情况下才能继续加右括号  才有用
        backtrace(l,r+1,cur+")")
      }
    }
    backtrace(0,0,"") //定义左右括号数,刚开始字符串为“”  
    return marry
}

矩阵最长递增路径 #

给定一个 n 行 m 列矩阵 matrix ,矩阵内所有数均为非负整数。 你需要在矩阵中找到一条最长路径,使这条路径上的元素是递增的。并输出这条最长路径的长度。

这个路径必须满足以下条件:

  1. 对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外。

  2. 你不能走重复的单元格。即每个格子最多只能走一次。

数据范围:1≤n,m≤1000,0≤matrix[i][j]≤1000

进阶:空间复杂度 O(nm),时间复杂度 O(nm)

例如:当输入为[[1,2,3],[4,5,6],[7,8,9]]时,对应的输出为5,

其中的一条最长递增路径如下图所示:

输入:[[1,2,3],[4,5,6],[7,8,9]]
返回值:5
说明:1->2->3->6->9即可。当然这种递增路径不是唯一的。
func solve( matrix [][]int ) int {//深度遍历优先
    num:=0  
    a,b:=len(matrix),len(matrix[0])
    var backtrace func(n,i,j,x int)
    backtrace=func(n,i,j,x int) { // n 是计数,i,j是下标,x,是上一位的值
        if n>num{ //得到最大值
            num=n
        }
        if i>=0&&i+1<a&&matrix[i+1][j]!=-1&&matrix[i+1][j]>x{//下一位不能是走过的,且大于上一位值
            y:=matrix[i+1][j] //这里不能再用x,会报错,为什么我不知道
            backtrace(n+1,i+1,j,y)
            matrix[i+1][j]=y        
        }
        if i>0&&matrix[i-1][j]!=-1&&matrix[i-1][j]>x{
            y:=matrix[i-1][j]
            backtrace(n+1,i-1,j,y)
            matrix[i-1][j]=y   
        }
        if j>=0&&j+1<b&&matrix[i][j+1]!=-1&&matrix[i][j+1]>x{
            y:=matrix[i][j+1]
            backtrace(n+1,i,j+1,y)
            matrix[i][j+1]=y    
        }
        if j>0&&matrix[i][j-1]!=-1&&matrix[i][j-1]>x{
            y:=matrix[i][j-1]
            backtrace(n+1,i,j-1,y)
            matrix[i][j-1]=y  
        }    
    }
    for i:=0;i<a;i++{
        for j:=0;j<b;j++{   //按个从头开始
            x:=matrix[i][j]  //先记录,然后改变值,证明这里来过
            matrix[i][j]=-1
            backtrace(1,i,j,x)  //递归
            matrix[i][j]=x   //恢复值
        }
    }
    return num
}

动态规划 #

斐波那契数列 #

大家都知道斐波那契数列,现在要求输入一个正整数 n ,请你输出斐波那契数列的第 n 项。

要求:空间复杂度 O(1),时间复杂度 O(n) ,本题也有时间复杂度 O(logn)的解法

输入:4
返回值:3
说明:根据斐波那契数列的定义可知,fib(1)=1,fib(2)=1,fib(3)=fib(3-1)+fib(3-2)=2,fib(4)=fib(4-1)+fib(4-2)=3,所以答案为3。   
func Fibonacci( n int ) int {
    if n<=2{
        return 1
    }
    num:=0
    x,y:=1,1
    for i:=3;i<=n;i++{
        num=x+y
        x=y
        y=num
    }
    return num
}

跳台阶 #

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

数据范围:1≤n≤40

要求:时间复杂度:O(n),空间复杂度: O(1)

输入:2
返回值:2
说明:青蛙要跳上两级台阶有两种跳法,分别是:先跳一级,再跳一级或者直接跳两级。因此答案为2      
func jumpFloor( number int ) int {
    switch number{
        case 1:
        return 1
        case 2:
        return 2
    }
    a,b,num:=1,2,0
    for i:=3;i<=number;i++{
        num=a+b
        a=b
        b=num
    }
    return num
}

最小花费爬楼梯 #

给定一个整数数组 cost ,其中 cost[i] 是从楼梯第i i 个台阶向上爬需要支付的费用,下标从0开始。一旦你支付此费用,即可选择向上爬一个或者两个台阶。

你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。

请你计算并返回达到楼梯顶部的最低花费。 数据范围:数组长度满足 1≤n≤105 ,数组中的值满足 1≤costi≤104

输入:[1,100,1,1,1,90,1,1,80,1]
返回值:6
说明:
你将从下标为 0 的台阶开始。
1.支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
2.支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
3.支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
4.支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
5.支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
6.支付 1 ,向上爬一个台阶,到达楼梯顶部。
总花费为 6 。    
func minCostClimbingStairs( cost []int ) int {
    length:=len(cost)
    if length==1{
        return cost[0]
    }
    if length==2{
        return minum(cost[0],cost[1])
    }
    num:=0
    arry:=make([]int,length)    //动态规划总有一个要记录的数组
    arry[0]=cost[0]
    arry[1]=cost[1]
    for i:=2;i<length;i++{
        if arry[i-1]<=arry[i-2]{        //那个小
            arry[i]=arry[i-1]+cost[i]   //对应位置记录最小值和本值的和
        }else{
            arry[i]=arry[i-2]+cost[i]
        }
    }
    num=minum(arry[length-1],arry[length-2])  //到数组的最后两位 找最小值
    return num
}
func minum(a,b int)int{ //得到最小值
    if a<=b{
        return a
    }
    return b
}

最长公共子序列2 #

给定两个字符串str1和str2,输出两个字符串的最长公共子序列。如果最长公共子序列为空,则返回”-1"。目前给出的数据,仅仅会存在一个最长的公共子序列

数据范围:0≤∣str1∣,∣str2∣≤2000

要求:空间复杂度 O(n2) ,时间复杂度 O(n2)

输入:"1A2C3D4B56","B1D23A456A"
返回值:"123456"

func LCS(s1 string, s2 string) string {
	a, b := len(s1), len(s2)
	if a == 0 || b == 0 {
		return "-1"
	}
	p := make([][]int, a+1)
	for i := range p {
		p[i] = make([]int, b+1)
	}
	dp := make([][]int, a+1) //dp[i][j]表示第一个字符串到第i位,第二个字符串到第j位为止的最长公共子序列长度
	for i := range dp {
		dp[i] = make([]int, b+1)
	}
	for i := 1; i < a+1; i++ { //主旨思想,遇到相等+1,不等,找最大值,往后记录
		for j := 1; j < b+1; j++ {
			if s1[i-1] == s2[j-1] { //遇到字符串相等 则+1
				dp[i][j] = dp[i-1][j-1] + 1  
				p[i][j] = 1 //表示来自左上方
			} else { //遇到的字符串不同
				if dp[i-1][j] > dp[i][j-1] { //左边的选择更大,即第一个字符串后退一位
					dp[i][j] = dp[i-1][j]
					p[i][j] = 2 //来自左方
				} else { //右边的选择更大,即第二个字符串后退一位
					dp[i][j] = dp[i][j-1]
					p[i][j] = 3 //来自上方
				}
			}
		}
	}
	var backtrace func(i, j int) string
	backtrace = func(i, j int) string {
		s := ""
		if p[i][j] == 1 {
			s = s + backtrace(i-1, j-1)
			s = s + string(s1[i-1])
		} else if p[i][j] == 2 {
			s = s + backtrace(i-1, j)
		} else if p[i][j] == 3 {
			s = s + backtrace(i, j-1)
		}
		return s
	}
	if backtrace(a, b) == "" {
		return "-1"
	} else {
		return backtrace(a, b)
	}
}

最长公共子串 #

给定两个字符串str1和str2,输出两个字符串的最长公共子串

题目保证str1和str2的最长公共子串存在且唯一。

数据范围: 1≤∣str1∣,∣str2∣≤5000 要求: 空间复杂度 O(n2),时间复杂度 O(n2)

输入:"1AB2345CD","12345EF"
返回值:"2345"
func LCS(s1 string ,  s2 string ) string {//这个比上一个简单多了
	a, b := len(s1), len(s2)
	if a == 0 || b == 0 {
		return ""
	}
	n:=0//记录长度
   sa:=0//记录下标
	dp := make([][]int, a+1) //dp[i][j]表示第一个字符串到第i位,第二个字符串到第j位为止的最长公共子序列长度
	for i := range dp {
		dp[i] = make([]int, b+1)
	}
	for i := 1; i < a+1; i++ {
		for j := 1; j < b+1; j++ {
			if s1[i-1] == s2[j-1] { //遇到字符串相等
				dp[i][j] = dp[i-1][j-1] + 1
				if dp[i][j]>n{ //出现比n大的,开始改变
           n=dp[i][j] 
           sa=i
         }
			} else { //遇到的字符串不同
				dp[i][j]=0
			}
		}
	}
    s:=[]byte(s1)
    s=s[sa-n:sa]
    return string(s)
}

不同路径的数目1 #

一个机器人在m×n大小的地图的左上角(起点)。

机器人每次可以向下或向右移动。机器人要到达地图的右下角(终点)。

可以有多少种不同的路径从起点走到终点?

备注:m和n小于等于100,并保证计算结果在int范围内

数据范围:0<n,m≤1000<n,m≤100,保证计算结果在32位整型范围内

要求:空间复杂度 O(nm),时间复杂度 O(nm)

进阶:空间复杂度 O(1),时间复杂度 O(min(n,m))

输入:2,2
返回值:2
func uniquePaths( m int ,  n int ) int {
    if m==1||n==1{
        return 1
    }
    dp:=make([][]int,m) //记录数组
    for i:=range dp{
        dp[i]=make([]int,n)
    }
    for i:=0;i<m;i++{
        for j:=0;j<n;j++{
            if i==0||j==0{
                dp[i][j]=1
            }else{
                dp[i][j]=dp[i-1][j]+dp[i][j-1]
            }
        }
    }
    return dp[m-1][n-1]
}

矩阵的最小路径和 #

给定一个 n * m 的矩阵 a,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,输出所有的路径中最小的路径和。

数据范围: 1≤n,m≤500,矩阵中任意值都满足 0≤ai,j≤100

要求:时间复杂度 O(nm)

例如:当输入[[1,3,5,9],[8,1,3,4],[5,0,6,1],[8,8,4,0]]时,对应的返回值为12,

所选择的最小累加和路径如下图所示:

输入:[[1,3,5,9],[8,1,3,4],[5,0,6,1],[8,8,4,0]]
返回值:12
func minPathSum( matrix [][]int ) int {
    m:=len(matrix)
    n:=len(matrix[0])
    for i:=0;i<m;i++{
        for j:=0;j<n;j++{
            if j==0&&i!=0{
                matrix[i][j]+=matrix[i-1][j]
            }
            if i==0&&j!=0{
                matrix[i][j]+=matrix[i][j-1]
            }
            if i!=0&&j!=0{
                matrix[i][j]+=matrixmin(matrix[i-1][j],matrix[i][j-1])
            }
        }
    }
    return matrix[m-1][n-1]
}
func matrixmin(a,b int)int{
    if a>b{
        return b
    }
    return a
}

把数字翻译成字符串 #

有一种将字母编码成数字的方式:‘a’->1, ‘b->2’, … , ‘z->26’。

现在给一串数字,返回有多少种可能的译码结果

数据范围:字符串长度满足 0<n≤90

进阶:空间复杂度 O(n),时间复杂度 O(n)

具体做法:

  • step 1:用辅助数组dp表示前i个数的译码方法有多少种。
  • step 2:对于一个数,我们可以直接译码它,也可以将其与前面的1或者2组合起来译码:如果直接译码,则dp[i]=dp[i−1];如果组合译码,则dp[i]=dp[i−2]
  • step 3:对于只有一种译码方式的,选上种dp[i−1]即可,对于满足两种译码方式(10,20不能)则是dp[i−1]+dp[i−2]
  • step 4:依次相加,最后的dp[length]即为所求答案。
func solve(nums string) int {
	length := len(nums)
	s := []byte(nums)
	if length == 1 {
		if s[0] == '0' {
			return 0
		}
		return 1
	}
	arry := make([]int, length)
	arry[0] = 1
	if (s[1] > '6' && s[0] > '1') || (s[0] > '2') || (s[1] == '0') {
		arry[1] = arry[0]
	} else {
		arry[1] = 2
	}
	for i := 2; i < length; i++ {
		if s[i] == '0' {
			if s[i-1] == '0' || s[i-1] > '2' {
				return 0
			}
		}
		if (s[i] > '6' && s[i-1] > '1') || (s[i-1] > '2') || (s[i-1] == '0') { //27 71 
			arry[i] = arry[i-1]
			continue
		}
		if s[i] == '0' && s[i-1] < '3' { //“20” 10
			arry[i] = arry[i-2]
			continue
		}
		arry[i] = arry[i-1] + arry[i-2]  //正常情况
	}
	return arry[length-1]
}
func solve( nums string ) int {
    if nums[0] == '0' {
        return 0
    }
    dp := make([]int, len(nums)+1)
    dp[0] = 1
    for i := 1; i <= len(nums); i++ {
        if nums[i-1] != '0' { 
            dp[i] = dp[i-1] //先把值赋过去
        }
        if i > 1 && nums[i-2:i] >= "10" && nums[i-2:i] <= "26" { //字符串数字还能这样比较??
            dp[i] += dp[i-2]
        }
    }
    return dp[len(nums)]
}

兑换零钱 #

给定数组arr,arr中所有的值都为正整数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个aim,代表要找的钱数,求组成aim的最少货币数。

如果无解,请返回-1.

数据范围:数组大小满足 0≤n≤10000, 数组中每个数字都满足 0<val≤10000,0≤aim≤5000

要求:时间复杂度 O(n×aim) ,空间复杂度 O(aim)

输入:[5,2,3],20
返回值:4

动态规划具体做法:

  • step 1:可以用dp[i]d**p[i]表示要凑出i元钱需要最小的货币数。
  • step 2:一开始都设置为最大值aim+1,因此货币最小1元,即货币数不会超过aim**.
  • step 3:初始化dp[0]=0。
  • step 4:后续遍历1元到aim元,枚举每种面值的货币都可能组成的情况,取每次的最小值即可,转移方程为dp[i]=min(dp[i],dp[i−arr[j]]+1).
  • step 5:最后比较dp[aim]d**p[aim]的值是否超过aim,如果超过说明无解,否则返回即可。
func minMoney( arr []int ,  aim int ) int {
    if aim<1{
        return 0
    }
    dp:=make([]int,aim+1)  //动态规划数组
    for i := 1; i < aim+1; i++ { //刚开始除0外 都赋最大值,因为后面要找最小值 ,除0 是如果一次找到
		dp[i] = aim+1
	  }
    for i:=1;i<=aim;i++{ //从1开始往上+
        for j:=0;j<len(arr);j++{ //遍历数组
            if arr[j]<=i{ //如果数组元素小于i,则找最小值
                dp[i]=mindp(dp[i],dp[i-arr[j]]+1)  //本题关键点
            }
        }
    }
    if dp[aim]>aim{ //如果大于,则无解
        return -1
    }else{
        return dp[aim]
    }
}
func mindp(a,b int)int{
    if a>b{
        return b
    }
    return a
}

贪心:本题确实不适合贪心思想,

考虑到有 [1,7,10] 这种用例,按照贪心思路 10 + 1 + 1 + 1 + 1 会比 7 + 7 更早找到

最长上升子序列1 #

给定一个长度为 n 的数组 arr,求它的最长严格上升子序列的长度。

所谓子序列,指一个数组删掉一些数(也可以不删)之后,形成的新数组。例如 [1,5,3,7,3] 数组,其子序列有:[1,3,3]、[7] 等。但 [1,6]、[1,3,5] 则不是它的子序列。

我们定义一个序列是 严格上升 的,当且仅当该序列不存在两个下标 ii 和 jj 满足 i<ji<j 且 arri≥arrj

数据范围: 0≤n≤1000

要求:时间复杂度 O(n2), 空间复杂度 O(n)

输入:[6,3,1,5,2,3,7]
返回值:4
说明:该数组最长上升子序列为 [1,2,3,7] ,长度为4
func LIS(arr []int) int {
	length := len(arr)
    if length==0{
        return 0
    }
	dp := make([]int, length)
	mmax := 1
	dp[0] = 1
	for i := 1; i < length; i++ {
		for j := 0; j < i; j++ { //从i前面开始遍历
			if arr[i] > arr[j] { //如果比前面的大 就按个找最大值
				dp[i] = dpMax(dp[j]+1, dp[i])
			} else {//如果小,就按个找 1和它的最大值
				dp[i] = dpMax(1, dp[i])
			}
		}
		if dp[i] > mmax {
			mmax = dp[i]
		}
	}
	return mmax
}
func dpMax(a, b int) int { //求最大值
	if a > b {
		return a
	}
	return b
}

连续子数组的最大和 #

输入一个长度为n的整型数组array,数组中的一个或连续多个整数组成一个子数组,子数组最小长度为1。求所有子数组的和的最大值。

数据范围:

1<=n<=2×1051<=n<=2×105

−100<=a[i]<=100−100<=a[i]<=100

要求:时间复杂度为 O(n),空间复杂度为 O(n)

进阶:时间复杂度为 O(n),空间复杂度为 O(1)

输入:[1,-2,3,10,-4,7,2,-5]
返回值:18
说明:经分析可知,输入数组的子数组[3,10,-4,7,2]可以求得最大和为18   
func FindGreatestSumOfSubArray( array []int ) int {
    length:=len(array)
    dp:=make([]int,length) //记录到此点的最大值
    dp[0]=array[0]  
    n:=dp[0]
    for i:=1;i<length;i++{
        if array[i]+dp[i-1]>array[i]{
            dp[i]=array[i]+dp[i-1]
        }else{
            dp[i]=array[i]
        }
        if dp[i]>n{
            n=dp[i]
        }
    }
    return n
}

最长回文子串 #

对于长度为n的一个字符串A(仅包含数字,大小写英文字母),请设计一个高效算法,计算其中最长回文子串的长度。

数据范围: 1≤n≤1000

要求:空间复杂度 O(1),时间复杂度 O(n2)

进阶: 空间复杂度 O(n),时间复杂度 O(n)

输入:"ababc"
返回值:3
说明:最长的回文子串为"aba"与"bab",长度都为3
func getLongestPalindrome(A string) int { //暴力法
	length := len(A)
	if length == 0 {
		return 0
	}
	n := 1
	for i := 0; i < length; i++ {
		for j := i + 1; j < length; j++ {
			if Palindrome(i, j, A) {
				if j-i+1 > n {
					n = j - i + 1
				}
			}
		}
	}
	return n
}
func Palindrome(start, end int, A string) bool { //判断是不是回文子串
	for start < end {
		if A[start] == A[end] {
			start++
			end--
		} else {
			return false
		}
	}
	return true
}
func getLongestPalindrome(A string) int { //动态规划
	length := len(A)
	if length == 0 {
		return 0
	}
	dp := make([][]bool, length) //二维布尔数组,记录从i-j是否为回文子串
	n := 0
	for i := range dp {
		dp[i] = make([]bool, length)
	}
	for c := 0; c < length; c++ {
		for i := 0; i < length-c; i++ {
			j := i + c //这样写可以先一步把 1,1  2,2 等变成true
			if A[i] == A[j] {
				if c <= 1 { //证明i,j相邻 或相等 代表一个字符
					dp[i][j] = true
				} else { //不相邻 则取决于内层子串是否相等
					dp[i][j] = dp[i+1][j-1]
				}
				if dp[i][j] {
					n = c + 1
				}
			}
		}
	}
	return n
}
func getLongestPalindrome(A string) int {//中心扩散
	length := len(A)
	if length < 2 {
		return length
	}
	n := 0
	for i := 0; i < length; i++ { //从头开始往后扩散
		n1 := maxdrome(helper(i, i, A), helper(i, i+1, A))
		n = maxdrome(n1, n)
	}
	return n
}
func helper(i, j int, A string) int { //中心扩散函数
	for i >= 0 && j < len(A) {
		if A[i] == A[j] {
			i--
			j++
			continue
		}
		break
	}
	return j - i + 1 - 2
  // "+1"是因为通过下标计算子串长度
  // "-2"是因为上边的while循环是当索引为left和right不想等才退出循环的
  // 因此此时的left和right是不满足的,需要舍弃
}
func maxdrome(a, b int) int {
	if a > b {
		return a
	}
	return b
}

数字字符转化成IP地址 #

现在有一个只包含数字的字符串,将该字符串转化成IP地址的形式,返回所有可能的情况。

例如:

给出的字符串为"25525522135",

返回[“255.255.22.135”, “255.255.221.35”]. (顺序没有关系)

数据范围:字符串长度 0≤n≤12

要求:空间复杂度 O(n!),时间复杂度 O(n!)

注意:ip地址是由四段数字组成的数字序列,格式如 “x.x.x.x”,其中 x 的范围应当是 [0,255]。

输入:"25525522135"
返回值:["255.255.22.135","255.255.221.35"]
func restoreIpAddresses( s string ) []string { //枚举法
    length:=len(s)
    s1:=[]byte(s) 
    num:=[]string{}
    for i:=1;i<4&&i<length-2;i++{//第一段从0 到倒数第四个 第一段长度不能超过4个 不能为0个  这里从1开始,<4,是为了后面好截取
        for j:=i+1;j<i+4&&j<length-1;j++{//第二段,从第一段后开始,到倒数第三个
            for k:=j+1;k<j+4&&k<length;k++{ //第三段
                if length-k>=4{ //剩下的字段长度超了
                    continue
                }
                a:=string(s1[0:i])
                b:=string(s1[i:j])
                c:=string(s1[j:k])
                d:=string(s1[k:])
                a1,_:=strconv.Atoi(a)
                b1,_:=strconv.Atoi(b)
                c1,_:=strconv.Atoi(c)
                d1,_:=strconv.Atoi(d)
                if a1>255||b1>255||c1>255||d1>255{ //排除数字大于255
                    continue
                }
                if (len(a)!=1&&a[0]=='0')||(len(b)!=1&&b[0]=='0')||(len(c)!=1&&c[0]=='0')||(len(d)!=1&&d[0]=='0') {  //排除先导0
                    continue
                }
                s:=a+"."+b+"."+c+"."+d
                num = append(num, s)
            }
        }
    }
    return num
}

编辑距离1 #

给定两个字符串 str1 和 str2 ,请你算出将 str1 转为 str2 的最少操作数。

你可以对字符串进行3种操作:

1.插入一个字符

2.删除一个字符

3.修改一个字符。

字符串长度满足 1≤n≤1000 ,保证字符串中只出现小写英文字母。

输入:"nowcoder","new"
返回值:6
说明:
"nowcoder"=>"newcoder"(将'o'替换为'e'),修改操作1次
"nowcoder"=>"new"(删除"coder"),删除操作5次  
func editDistance(str1 string, str2 string) int {
	n1 := len(str1)
	n2 := len(str2)
	dp := make([][]int, n1+1) //创建动态规划数组
	for i := range dp {
		dp[i] = make([]int, n2+1)
	}
	for i := 0; i < n1+1; i++ {
		for j := 0; j < n2+1; j++ {
			if i == 0 { //当其中一个字符串长度为0时,值就等于另一个字符串的长度,增加
				dp[i][j] = j
				continue
			}
			if j == 0 {
				dp[i][j] = i
				continue
			}
			if str1[i-1] == str2[j-1] { //如果相同,就等于上一个
				dp[i][j] = dp[i-1][j-1]
			} else { //不同找最小值,加一  如果这两个字符不相同,那么这两个字符需要编辑,但是此时的最短的距离不一定是修改这最后一位,也有可能是删除某个字符或者增加某个字符,因此我们选取这三种情况的最小值增加一个编辑距离
				dp[i][j] = min(dp[i-1][j-1], min(dp[i-1][j], dp[i][j-1])) + 1
			}
		}
	}
	return dp[n1][n2]
}
func min(a, b int) int {
	if a > b {
		return b
	}
	return a
}

正则表达式匹配 #

请实现一个函数用来匹配包括’.‘和’*‘的正则表达式。

1.模式中的字符’.‘表示任意一个字符

2.模式中的字符’*‘表示它前面的字符可以出现任意次(包含0次)。

在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"ab*a"均不匹配

数据范围:

1.str 只包含从 a-z 的小写字母。

2.pattern 只包含从 a-z 的小写字母以及字符 . 和 ,无连续的 ‘’。

3.0≤str.length≤26

4.0≤pattern.length≤26

输入:"aad","c*a*d"
返回值:true
说明:因为这里 c 为 0 个,a被重复一次, * 表示零个或多个a。因此可以匹配字符串 "aad"。 

正则表达式匹配详解

func match(str string, pattern string) bool {
	n1, n2 := len(str), len(pattern)
	dp := make([][]bool, n1+1)
	for i := range dp {
		dp[i] = make([]bool, n2+1)
	}
	dp[0][0] = true
	for j := 1; j < n2+1; j++ { //搞定第一行,*代表0个或多个,前两个能匹配上,它就能匹配上
		if pattern[j-1] == '*' {
			dp[0][j] = dp[0][j-2]
		}
	}
	for i := 1; i < n1+1; i++ {
		for j := 1; j < n2+1; j++ {
			if str[i-1] == pattern[j-1] || pattern[j-1] == '.' {//如果相等
				dp[i][j] = dp[i-1][j-1] //是否匹配取决于前一个是否匹配
			} else if pattern[j-1] == '*' { //如果不相等,切pattern[j-1] == '*'
				if str[i-1] != pattern[j-2] && pattern[j-2] != '.' { //前一个也不等,前一个也不是 . 
					dp[i][j] = dp[i][j-2]  //代表* 为0
				} else { //str[i-1] == pattern[j-2]  pattern前一个跟str相等,这里有0个或多个
					dp[i][j] = dp[i][j-2] || dp[i-1][j]// 
				}
			}
		}
	}
	return dp[n1][n2]
}

最长的括号子串 #

给出一个长度为 n 的,仅包含字符 ‘(’ 和 ‘)’ 的字符串,计算最长的格式正确的括号子串的长度。

例1: 对于字符串 “(()” 来说,最长的格式正确的子串是 “()” ,长度为 2 .

例2:对于字符串 “)()())” , 来说, 最长的格式正确的子串是 “()()” ,长度为 4 .

字符串长度:0≤n≤5∗105

要求时间复杂度 O(n),空间复杂度 O(n)

输入:"(())"
返回值:4
func longestValidParentheses( s string ) int {//动态规划
    maxAns:=0
    dp:=make([]int,len(s))
    for i:=1;i<len(s);i++{
      if s[i]==')'{
        if s[i-1]=='('{//()(()()
          if i>=2{
              dp[i]=dp[i-2]+2
          }else{
              dp[i]=2
          }
        }else if i-dp[i-1]>0&&s[i-dp[i-1]-1]=='('{//i-dp[i-1]-1 往前数多一个()的位置
            if i-dp[i-1]>=2{
                dp[i]=dp[i-1]+dp[i-dp[i-1]-2]+2
            }else{
                dp[i]=dp[i-1]+2
            }
        }
        maxAns=max(maxAns,dp[i])
      }
    }
    return maxAns
}
func max(a,b int)int{
    if a>b{
        return a
    }
    return b
}
  • 对于遇到的每个 ‘(’,我们将它的下标放入栈中
  • 对于遇到的每个 ‘)’ ,我们先弹出栈顶元素表示匹配了当前右括号:
    • 如果栈为空,说明当前的右括号为没有被匹配的右括号,我们将其下标放入栈中来更新我们之前提到的「最后一个没有被匹配的右括号的下标」
    • 如果栈不为空,当前右括号的下标减去栈顶元素即为「以该右括号为结尾的最长有效括号的长度」
func longestValidParentheses( s string ) int {//栈
    maxAns:=0
    n:=0
    dp:=[]int{-1} //里面先放一个-1,1--1=2 也防止刚上来就出栈,导致Panic
    for i:=0;i<len(s);i++{
        if s[i]=='('{//入栈
            dp=append(dp,i)
        }else{ //)
          dp=dp[:len(dp)-1] //先出栈 
        if len(dp)!=0{
            n=i-dp[len(dp)-1]
            }else{   //如果栈为空,则继续入栈, 故栈中一直有数据
            dp=append(dp, i)
            }
        }
        maxAns=max(maxAns,n)
    }
    return maxAns
}
func max(a,b int)int{
    if a>b{
        return a
    }
    return b
}

打家劫舍1 #

你是一个经验丰富的小偷,准备偷沿街的一排房间,每个房间都存有一定的现金,为了防止被发现,你不能偷相邻的两家,即,如果偷了第一家,就不能再偷第二家;如果偷了第二家,那么就不能偷第一家和第三家。

给定一个整数数组nums,数组中的元素表示每个房间存有的现金数额,请你计算在不被发现的前提下最多的偷窃金额。

数据范围:数组长度满足 1≤n≤2×105 ,数组中每个值满足 1≤num[i]≤5000

输入:[1,2,3,4]
返回值:6
说明:最优方案是偷第 2,4 个房间
func rob( nums []int ) int { 
    length:=len(nums)
    if length==1{ //排除长度为1
        return nums[0]
    }
    if length==2{//排除长度为2
        return max(nums[0],nums[1])
    }
    dp:=make([]int,length)
    dp[0],dp[1]=nums[0],nums[1]
    for i:=2;i<length;i++{//长度大于2时
        if i-3>=0{
            dp[i]=max(dp[i-2],dp[i-3])+nums[i] //dp[i]=前两或前三的最大值 加本值
        }
        if i==2{ //只有前二
            dp[i]=dp[0]+nums[i]
        }
    }
    return max(dp[length-1],dp[length-2]) //找最后两位的最大值
}
func max(a,b int)int{
    if a>b{
        return a
    }
    return b
}

具体做法:

  • step 1:用dp[i]表示长度为i的数组,最多能偷取到多少钱,只要每次转移状态逐渐累加就可以得到整个数组能偷取的钱。
  • step 2:(初始状态) 如果数组长度为1,只有一家人,肯定是把这家人偷了,收益最大,因此dp[1]=nums[0]。
  • step 3:(状态转移) 每次对于一个人家,我们选择偷他或者不偷他,如果我们选择偷那么前一家必定不能偷,因此累加的上上级的最多收益,同理如果选择不偷他,那我们最多可以累加上一级的收益。因此转移方程为dp[i]=max(dp[i−1],nums[i−1]+dp[i−2])。这里的i在dp中为数组长度,在nums中为下标。
func rob( nums []int ) int {
    length:=len(nums)
    dp:=make([]int,length+1)
    dp[1]=nums[0] //长度为1 只能偷一家
    for i:=2;i<length+1;i++{
        dp[i]=max(dp[i-1],dp[i-2]+nums[i-1]) //选择偷或者不偷这家的最大值
    }
    return dp[length]
}
func max(a,b int)int{
    if a>b{
        return a
    }
    return b
}

打家劫舍2 #

你是一个经验丰富的小偷,准备偷沿湖的一排房间,每个房间都存有一定的现金,为了防止被发现,你不能偷相邻的两家,即,如果偷了第一家,就不能再偷第二家,如果偷了第二家,那么就不能偷第一家和第三家。沿湖的房间组成一个闭合的圆形,即第一个房间和最后一个房间视为相邻。

给定一个长度为n的整数数组nums,数组中的元素表示每个房间存有的现金数额,请你计算在不被发现的前提下最多的偷窃金额。

数据范围:数组长度满足 1≤n≤2×105 ,数组中每个值满足 1≤nums[i]≤5000

输入:[1,2,3,4]
返回值:6
说明:最优方案是偷第 2 4 个房间
输入:[1,3,6]
返回值:6
说明:由于 1 和 3 是相邻的,因此最优方案是偷第 3 个房间      
func rob( nums []int ) int {
    if len(nums)==1{
        return nums[0]
    }
    if len(nums)==2{
        return max(nums[0],nums[1])
    }
    a:=robmax(nums[:len(nums)-1])//假设偷了第一件间 则把最好一间拿掉
    b:=robmax(nums[1:]) //假设没偷第一间,把第一间拿掉
    return max(a,b)  //返回最大值
}
func robmax(nums []int)int{//跟打家劫舍一类似
    dp:=make([]int,len(nums))
    dp[0]=nums[0]
    dp[1]=max(nums[0],nums[1])
    for i:=2;i<len(nums);i++{
        dp[i]=max(dp[i-1],dp[i-2]+nums[i])
    }
    return dp[len(nums)-1]
}
func max(a,b int)int{
    if a>b{
        return a
    }
    return b
}

打家劫舍3 #

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root

除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。

给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额

输入: root = [3,2,3,null,3,null,1]
输出: 7 
解释: 小偷一晚能够盗取的最高金额 3 + 3 + 1 = 7
func rob(root *TreeNode) int {
    return max(dfs(root))
}
func dfs(root *TreeNode)(a,b int){//a表示选择这个节点,b表示不选择这个节点
    if root==nil{
        return 0,0
    }
    a1,b1:=dfs(root.Left)
    a2,b2:=dfs(root.Right)
    //如果选择这个节点,则它左右子树就不能选择,则为这个节点值,加左右子树不选择这个点的值
    a=root.Val+b1+b2
    //如果不选择这个节点,则b为选择它左右子树节点和不选择它左右子树节点 的最大值的和
    b=max(a1,b1)+max(a2,b2)
    return a,b
}
func max(a,b int)int{
    if a>b{
        return a
    }
    return b
}

买卖股票的最好时机1 #

假设你有一个数组prices,长度为n,其中prices[i]是股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益

1.你可以买入一次股票和卖出一次股票,并非每天都可以买入或卖出一次,总共只能买入和卖出一次,且买入必须在卖出的前面的某一天

2.如果不能获取到任何利润,请返回0

3.假设买入卖出均无手续费

数据范围: 0≤n≤105,0≤val≤104

要求:空间复杂度 O(1),时间复杂度 O(n)

输入:[8,9,2,5,4,7,1]
返回值:5
说明:在第3天(股票价格 = 2)的时候买入,在第6天(股票价格 = 7)的时候卖出,最大利润 = 7-2 = 5 ,不能选择在第2天买入,第3天卖出,这样就亏损7了;同时,你也不能在买入前卖出股票。            
/*
dp[i][0]:规定了今天不持股,有以下两种情况:
    昨天不持股,今天什么都不做;
    昨天持股,今天卖出股票(现金数增加),
    状态转移方程:dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1]:规定了今天持股,有以下两种情况:
    昨天持股,今天什么都不做(现金数与昨天一样);
    昨天不持股,今天买入股票(注意:只允许交易一次,因此手上的现金数就是当天的股价的相反数)
    状态转移方程:dp[i][1] = Math.max(dp[i - 1][1], -prices[i]);
*/
func maxProfit( prices []int ) int {//动态规划
    length:=len(prices)
    if length<2{
      return 0
    }
    dp:=make([][2]int,length)
    dp[0][0]=0//下标为 i 这天结束的时候,不持股,手上拥有的现金数
    dp[0][1]=-prices[0]//下标为 i 这天结束的时候,持股,手上拥有的现金数
    for i:=1;i<length;i++{
        dp[i][0]=max(prices[i]+dp[i-1][1],dp[i-1][0])
        dp[i][1]=max(-prices[i],dp[i-1][1])
    }
    return dp[len(prices)-1][0]
}
func max(a,b int)int{
  if a>b{
    return a
  }
  return b
}
func maxProfit( prices []int ) int {
    minprices:=prices[0]//里面的最小值
    maxprices:=0 //最大值
    for i:=1;i<len(prices);i++{
      if prices[i]-minprices>maxprices{ //存在最大值
        maxprices=prices[i]-minprices //更新
      }
      if prices[i]<minprices{ //如果存在最小值
        minprices=prices[i]
      }
    }
    return maxprices
}

买卖股票的最好时机2 #

假设你有一个数组prices,长度为n,其中prices[i]是某只股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益

  1. 你可以多次买卖该只股票,但是再次购买前必须卖出之前的股票

  2. 如果不能获取收益,请返回0

  3. 假设买入卖出均无手续费

数据范围: 1≤n≤1×105, 1≤prices[i]≤104

要求:空间复杂度 O(n),时间复杂度 O(n)

进阶:空间复杂度 O(1),时间复杂度 O(n)

输入:[8,9,2,5,4,7,1]
返回值:7
说明:
在第1天(股票价格=8)买入,第2天(股票价格=9)卖出,获利9-8=1
在第3天(股票价格=2)买入,第4天(股票价格=5)卖出,获利5-2=3
在第5天(股票价格=4)买入,第6天(股票价格=7)卖出,获利7-4=3
总获利1+3+3=7,返回7        
func maxProfit( prices []int ) int {
    ans:=0
    for i:=1;i<len(prices);i++{
        if prices[i]>prices[i-1]{  //遇到比前一天大就自动买入买出
            ans=ans+prices[i]-prices[i-1]
        }
    }
    return ans
}
/*
step 1:
用dp[i][0]表示第i天不持股到该天为止的最大收益,
dp[i][1]表示第i天持股,到该天为止的最大收益。
step 2:
(初始状态) 第一天不持股,则总收益为0,
dp[0][0]=0;第一天持股,则总收益为买股票的花费,此时为负数,
dp[0][1]=−prices[0]。
step 3:
(状态转移) 对于之后的每一天,如果当天不持股,有可能是前面的若干天中卖掉了或是还没买,因此到此为止的总收益和前一天相同,也有可能是当天卖掉股票,我们选择较大的状态
dp[i][0]=max(dp[i−1][0],dp[i−1][1]+prices[i]);
step4:
如果当天持股,可能是前几天买入的还没卖,因此收益与前一天相同,也有可能是当天买入,减去买入的花费,同样是选取最大值:
dp[i][1]=max(dp[i−1][1],dp[i−1][0]−prices[i])。
*/
func maxProfit( prices []int ) int {
    length:=len(prices)
    dp:=make([][2]int,length)
    dp[0][0]=0//第一天不持股,总收益为0
    dp[0][1]=-prices[0]//第一天持股,总收益为减去该天的股价
    for i:=1;i<length;i++{
        dp[i][0]=max(dp[i-1][1]+prices[i],dp[i-1][0])
        dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i])
    }
    return dp[length-1][0]
}
func max(a,b int)int{
  if a>b{
    return a
  }
  return b
}

买卖股票的最好时机3 #

假设你有一个数组prices,长度为n,其中prices[i]是某只股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益

  1. 你最多可以对该股票有两笔交易操作,一笔交易代表着一次买入与一次卖出,但是再次购买前必须卖出之前的股票
  2. 如果不能获取收益,请返回0
  3. 假设买入卖出均无手续费

数据范围:1≤n≤105,股票的价格满足 1≤val≤104

要求: 空间复杂度 O(n),时间复杂度 O(n)

进阶:空间复杂度 O(1),时间复杂度 O(n)

输入:[8,9,3,5,1,3]
返回值:4
说明:
第三天(股票价格=3)买进,第四天(股票价格=5)卖出,收益为2
第五天(股票价格=1)买进,第六天(股票价格=3)卖出,收益为2
总收益为4。              
/*
这道题与BM80.买卖股票的最好时机(一)的区别在于最多可以买入卖出2次,那实际上相当于它的状态多了几个,对于每天有到此为止的最大收益和持股情况两个状态,持股情况有了5种变化,我们用:
dp[i][0]表示到第i天为止没有买过股票的最大收益
dp[i][1]表示到第i天为止买过一次股票还没有卖出的最大收益
dp[i][2]表示到第i天为止买过一次也卖出过一次股票的最大收益
dp[i][3]表示到第i天为止买过两次只卖出过一次股票的最大收益
dp[i][4]表示到第i天为止买过两次同时也买出过两次股票的最大收益
于是使用动态规划,有了如下的状态转移

具体做法:
step 1:(初始状态) 与上述提到的题类似,第0天有买入了和没有买两种状态:
dp[0][0]=0、dp[0][1]=−prices[0]。

step 2:状态转移: 对于后续的每一天,如果当天还是状态0,则与前一天相同,没有区别;

step 3:如果当天状态为1,可能是之前买过了或者当天才第一次买入,选取较大值:
dp[i][1]=max(dp[i−1][1],dp[i−1][0]−prices[i]);

step 4:如果当天状态是2,那必须是在1的状态下(已经买入了一次)当天卖出第一次,或者早在之前就卖出只是还没买入第二次,选取较大值:dp[i][2]=max(dp[i−1][2],dp[i−1][1]+prices[i]);

step 5:如果当天状态是3,那必须是在2的状态下(已经卖出了第一次)当天买入了第二次,或者早在之前就买入了第二次,只是还没卖出,选取较大值:dp[i][3]=max(dp[i−1][3],dp[i−1][2]−prices[i]);

step 6:如果当天是状态4,那必须是在3的状态下(已经买入了第二次)当天再卖出第二次,或者早在之前就卖出了第二次,选取较大值:dp[i][4]=max(dp[i−1][4],dp[i−1][3]+prices[i])。

step 7:最后我们还要从0、第一次卖出、第二次卖出中选取最大值,因为有可能没有收益,也有可能只交易一次收益最大。
*/

func maxProfit( prices []int ) int {
    length:=len(prices)
    dp:=make([][]int,length)
    for i:=range dp{
        dp[i]=[]int{-10000,-10000,-10000,-10000,-10000}
    }
    dp[0][0]=0
    dp[0][1]=-prices[0]
    for i:=1;i<length;i++{
        dp[i][0]=dp[i-1][0]//到第i天为止,没有买过股票的最大收益一直为0
        dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i])//表示到第i天为止买过一次股票还没有卖出的最大收益
        dp[i][2]=max(dp[i-1][2],dp[i-1][1]+prices[i])//表示到第i天为止买过一次股票也卖出过一次股票的最大收益
        dp[i][3]=max(dp[i-1][3],dp[i-1][2]-prices[i])//表示到第i天为止买过两次股票只卖出一次的最大收益
        dp[i][4]=max(dp[i-1][4],dp[i-1][3]+prices[i])//表示到第i天为止买过两次股票也卖出两次股票的最大收益
    }
    return max(dp[length-1][2],max(0,dp[length-1][4]))
}
func max(a,b int)int{
  if a>b{
    return a
  }
  return b
}
//简化版
func maxProfit(prices []int) int {
    n:=len(prices)
    a1,b1:=-prices[0],0//只进行过一次买操作,进行了一次买操作和一次卖操作,即完成了一笔交易;
    a2,b2:=-prices[0],0//在完成了一笔交易的前提下,进行了第二次买操作;完成了全部两笔交易。
    for i:=1;i<n;i++{
        a1=max(a1,-prices[i])
        b1=max(b1,a1+prices[i])
        a2=max(a2,b1-prices[i])
        b2=max(b2,a2+prices[i])
    }
    return max(b1,max(0,b2))
}
func max(a,b int)int{
  if a>b{
    return a
  }
  return b
}

字符串 #

字符串变形 #

对于一个长度为 n 字符串,我们需要对它做一些变形。

首先这个字符串中包含着一些空格,就像"Hello World"一样,然后我们要做的是把这个字符串中由空格隔开的单词反序,同时反转每个字符的大小写。

比如"Hello World"变形后就变成了"wORLD hELLO"。

数据范围: 1≤n≤106 , 字符串中包括大写英文字母、小写英文字母、空格。

进阶:空间复杂度 O(n), 时间复杂度 O(n)

输入:"This is a sample",16
返回值:"SAMPLE A IS tHIS"
func trans(s string, n int) string {
	s1 := []byte(s)
	for i:=0;i<n;i++{  //遇到小写变大写,大写变小写
        if s1[i] >= 'A' && s1[i] <= 'Z' {
			s1[i] = s1[i] - 'A' + 'a'
		} else if s1[i] >= 'a' && s1[i] <= 'z'{
			s1[i] = s1[i] - 'a' + 'A'
		}
	}
    s2:=strings.Split(string(s1)," ")//以“ ”为断点 先切片 返回字符串切片
    for i,j:=0,len(s2)-1;i<j;{
        s2[i],s2[j]=s2[j],s2[i]
        i++
        j--
    }
    return strings.Join(s2," ")//以“ ”为连接点,连接成字符串
}

最长公共前缀 #

给你一个大小为 n 的字符串数组 strs ,其中包含n个字符串 , 编写一个函数来查找字符串数组中的最长公共前缀,返回这个公共前缀。

数据范围: 0≤n≤5000, 0≤len(strsi)≤5000

进阶:空间复杂度 O(1),时间复杂度 O(n∗len)

输入:["abca","abc","abca","abc","abcc"]
返回值:"abc"

func longestCommonPrefix(strs []string) string {
	if len(strs) == 0 || strs == nil {
		return ""
	}
	for i := 0; i < len(strs[0]); i++ {
		s := strs[0][i]
		for j := 1; j < len(strs); j++ {
			if i == len(strs[j]) || s != strs[j][i] {//长度到了,或者出现问题,直接输出
				return string(strs[0][:i])
			}
		}
	}
	return strs[0] //其他的输出strs[0]
}

验证IP地址 #

编写一个函数来验证输入的字符串是否是有效的 IPv4 或 IPv6 地址

IPv4 地址由十进制数和点来表示,每个地址包含4个十进制数,其范围为 0 - 255, 用(".")分割。比如,172.16.254.1; 同时,IPv4 地址内的数不会以 0 开头。比如,地址 172.16.254.01 是不合法的。

IPv6 地址由8组16进制的数字来表示,每组表示 16 比特。这些组数字通过 (":")分割。比如, 2001:0db8:85a3:0000:0000:8a2e:0370:7334 是一个有效的地址。而且,我们可以加入一些以 0 开头的数字,字母可以使用大写,也可以是小写。所以, 2001:db8:85a3:0:0:8A2E:0370:7334 也是一个有效的 IPv6 address地址 (即,忽略 0 开头,忽略大小写)。

然而,我们不能因为某个组的值为 0,而使用一个空的组,以至于出现 (::) 的情况。 比如, 2001:0db8:85a3::8A2E:0370:7334 是无效的 IPv6 地址。 同时,在 IPv6 地址中,多余的 0 也是不被允许的。比如, 02001:0db8:85a3:0000:0000:8a2e:0370:7334 是无效的。

说明: 你可以认为给定的字符串里没有空格或者其他特殊字符。

数据范围:字符串长度满足 5≤n≤50

进阶:空间复杂度 O(n),时间复杂度 O(n)

输入:"172.16.254.1"
返回值:"IPv4"
说明:这是一个有效的 IPv4 地址, 所以返回 "IPv4" 
//自己写的菜代码
func judgement(ip1 []byte, c string) bool {//判断  
	if len(ip1) == 0 || len(ip1) > 4 {
		return false
	}
	if c == "IPv4" {
		a, _ := strconv.Atoi(string(ip1))
		if a > 255 || ip1[0] == '0' || ip1[0] != 0 && a == 0 {//ip1[0] != 0 && a == 0  判断1a1这种情况
			return false
		}
		return true
	}
	if c == "IPv6" {
		for i := 0; i < len(ip1); i++ {
			if ip1[i] >= 'A' && ip1[i] <= 'F' || ip1[i] >= 'a' && ip1[i] <= 'f' || ip1[i] >= '0' && ip1[i] <= '9' {
				continue
			} else {
				return false
			}
		}
		return true
	}
	return false
}
func solve(IP string) string {
	ip := []byte(IP)
	length := len(IP)
	var ipv4 string
	var ipv6 string
	i := 0
	for j := 0; j < length; j++ {
		if ip[j] == '.' {
			ipv4 = "IPv4"
			if judgement(ip[i:j], ipv4) {
				i = j + 1
				continue
			} else {
				return "Neither"
			}
		}
		if ip[j] == ':' {
			ipv6 = "IPv6"
			if judgement(ip[i:j], ipv6) {
				i = j + 1
				continue
			} else {
				return "Neither"
			}
		}
	}
	if ipv4 != "" {//最后剩下的加进去
		if judgement(ip[i:], ipv4) {
			return ipv4
		} else {
			return "Neither"
		}
	}
	if ipv6 != "" {
		if judgement(ip[i:], ipv6) {//最后剩下的加进去
			return ipv6
		} else {
			return "Neither"
		}
	}
	return ipv6
}
func judgementIPv4(IP string) bool { //判断
	ip := strings.Split(IP, ".")//"20EE:FGb8:85a3:0:0:8A2E:0370:7334"  长度则为1原样输出
	if len(ip) != 4 {
		return false
	}
	for i := 0; i < 4; i++ {
		if len(ip[i]) == 0 || len(ip[i]) > 3 { //有一个分割为零,说明两个点相连 255 2555
			return false
		}
		if len(ip[i]) != 1 && ip[i][0] == '0' { //先导不能为0
			return false
		}
		IP1, err := strconv.Atoi(ip[i]) //	转换为int型比较
		if err != nil {
			return false
		}
		if IP1 > 255 {
			return false
		}
	}
	return true
}
func judgementIPv6(IP string) bool {
	ip := strings.Split(IP, ":")
	if len(ip) != 8 {
		return false
	}
	for i := 0; i < 8; i++ {
		if len(ip[i]) == 0 || len(ip[i]) > 4 {
			return false
		}
		for j := 0; j < len(ip[i]); j++ {
			if !(ip[i][j] >= 'A' && ip[i][j] <= 'F' || ip[i][j] >= 'a' && ip[i][j] <= 'f' || ip[i][j] >= '0' && ip[i][j] <= '9') {
				return false
			}
		}
	}
	return true
}
func solve(IP string) string {
	if len(IP) == 0 {
		return "Neither"
	}
	if judgementIPv4(IP) {
		return "IPv4"
	}
	if judgementIPv6(IP) {
		return "IPv6"
	}
	return "Neither"
}

大数加法 #

以字符串的形式读入两个数字,编写一个函数计算它们的和,以字符串形式返回。

数据范围:s.length,t.length≤100000,字符串仅由'0’~‘9’构成

要求:时间复杂度 O(n)

输入:"1","99"
返回值:"100"
说明:1+99=100
func solve(s string, t string) string {
	slen, tlen := len(s)-1, len(t)-1
	sum := 0
	ss := ""
	for slen >= 0 || tlen >= 0 || sum != 0 {
		i, j := 0, 0
		if slen >= 0 {
      i = int(s[slen] - '0') //不能写int(s[slen])//byte类型有那个码  1代表的码不是1
			slen--
		}
		if tlen >= 0 {
			j = int(t[tlen] - '0')
			tlen--
		}
		sum = i + j + sum
    ss = strconv.Itoa(sum%10) + ss //不能写string(int)  转不过来,是乱码,注意
		sum = sum / 10
	}
	return ss
}

双指针 #

合并两个有序数组 #

给出一个有序的整数数组 A 和有序的整数数组 B ,请将数组 B 合并到数组 A 中,变成一个有序的升序数组

数据范围: 0≤n,m≤1000≤n,m≤100,∣Ai∣<=100, ∣Bi∣<=100 注意: 1.保证 A 数组有足够的空间存放 B 数组的元素, A 和 B 中初始的元素数目分别为 m 和 n,A的数组空间大小为 m+n

2.不要返回合并的数组,将数组 B 的数据合并到 A 里面就好了,且后台会自动将合并后的数组 A 的内容打印出来,所以也不需要自己打印

3.A 数组在[0,m-1]的范围也是有序的

输入:[4,5,6],[1,2,3]
返回值:[1,2,3,4,5,6]
说明:A数组为[4,5,6],B数组为[1,2,3],后台程序会预先将A扩容为[4,5,6,0,0,0],B还是为[1,2,3],m=3,n=3,传入到函数merge里面,然后请同学完成merge函数,将B的数据合并A里面,最后后台程序输出A数组            

func merge(A []int, m int, B []int, n int) {
	i, j, k := m-1, n-1, m+n-1 //三个指针,分别指向A,B, A后面的结尾
	for ; i >= 0 && j >= 0; k-- {
		if A[i] > B[j] {//那个大,就直接放到后面
			A[k] = A[i]
			i--
		} else {
			A[k] = B[j]
			j--
		}
	}
	for j >= 0 { //如果B没有放完,证明A放完了,继续放B
		A[k] = B[j]
		j--
		k = k - 1
	}
}

判断是否为回文字符串 #

给定一个长度为 n 的字符串,请编写一个函数判断该字符串是否回文。如果是回文请返回true,否则返回false。

字符串回文指该字符串正序与其逆序逐字符一致。

数据范围:0<n≤1000000

要求:空间复杂度 O(1),时间复杂度 O(n)

输入:"absba"
返回值:true
func judge( str string ) bool {
    length:=len(str)
    for i,j:=0,length-1;i<j;i++{
        if str[i]!=str[j]{
            return false
        }
        j--
    }
    return true
}

合并区间 #

给出一组区间,请合并所有重叠的区间。

请保证合并后的区间按区间起点升序排列。

数据范围:区间组数 0≤n≤2×105,区间内 的值都满足 0≤val≤2×105

要求:空间复杂度 O(n),时间复杂度 O(nlogn)

进阶:空间复杂度 O(val),时间复杂度O(val)

输入:[[10,30],[20,60],[80,100],[150,180]]
返回值:[[10,60],[80,100],[150,180]]
func merge( intervals []*Interval ) []*Interval {//运行没通过 说超时了,但没找到原因 自认为没问题
    if len(intervals) < 2 {      //leetcode可以通过
        return intervals
    }
    sort.Slice(intervals,func(i,j int)bool{
        return intervals[i].Start<intervals[j].Start
    })
    for i:=0;i<len(intervals);{
        if i+1 < len(intervals)&&intervals[i].End>=intervals[i+1].Start{//[a,b][c,d] b>=c
            if intervals[i].End>=intervals[i+1].End{//完全包含在里面 b>d
            intervals=append(intervals[:i+1],intervals[i+2:]...)//把i+1这一个 去掉  [c,d]去掉
            }else{//没有完全包含 d>b>c
                intervals[i].End=intervals[i+1].End//把后面的界限给第一个
                intervals=append(intervals[:i+1],intervals[i+2:]... )//把i+1这一个 去掉
            }
        }else{ //b<c
            i++
        }
    }
    return intervals
}
func merge( intervals []*Interval ) []*Interval {//通过答案
    n := len(intervals)
    if n < 2 {
        return intervals
    }
    sort.Slice(intervals,func(i,j int)bool{ //先排序
        return intervals[i].Start<intervals[j].Start
    })
    res := []*Interval{}
    prev := intervals[0]
    for i := 1; i < len(intervals); i++ {        //合并区间
        if prev.End < intervals[i].Start {//[a,b][c,d] b<c
            res = append(res, prev) //直接插入
            prev = intervals[i] //prev换成[c,d]
        } else {
            prev.End = max(prev.End, intervals[i].End)
        }
    }
    res = append(res, prev)//最后一个插入
    return res
}
func max(a, b int) int { 
    if a < b {
         return b 
    }
    return a 
}
func merge( intervals []*Interval ) []*Interval {//leetcode上是可以通过的  改进版
    n := len(intervals)
    if n < 2 {
        return intervals
    }
    sort.Slice(intervals,func(i,j int)bool{
        return intervals[i].Start<intervals[j].Start
    })
    for i := 0; i < len(intervals)-1;  {        
        if intervals[i].End < intervals[i+1].Start {//[a,b][c,d] b<c
            i++  //进行下一个比较
        } else {
            intervals[i].End = max(intervals[i].End, intervals[i+1].End)
            intervals=append(intervals[:i+1],intervals[i+2:]...)        
        }
    }
    return intervals
}
func max(a, b int) int { 
    if a < b {
         return b 
    }
    return a 
}

最小覆盖子串 #

给出两个字符串 s 和 t,要求在 s 中找出最短的包含 t 中所有字符的连续子串。

数据范围:0≤∣S∣,∣T∣≤100000≤∣S∣,∣T∣≤10000,保证s和t字符串中仅包含大小写英文字母

要求:进阶:空间复杂度 O(n)O(n) , 时间复杂度 O(n)O(n)

例如:

S=“XDOYEZODEYXNZ” T=“XYZ” 找出的最短子串为"YXNZ"

注意: 如果 s 中没有包含 t 中所有字符的子串,返回空字符串 “”; 满足条件的子串可能有很多,但是题目保证满足条件的最短的子串唯一。

输入:"XDOYEZODEYXNZ","XYZ"
返回值:"YXNZ"

S="DOABECODEBANC"T="ABC"为例 初始状态:

步骤一:不断增加j使滑动窗口增大,直到窗口包含了T的所有元素,need中所有元素的数量都小于等于0,同时needCnt也是0

步骤二:不断增加i使滑动窗口缩小,直到碰到一个必须包含的元素A,此时记录长度更新结果

步骤三:让i再增加一个位置,开始寻找下一个满足条件的滑动窗口

func minWindow(S string, T string) string {
	needCnt := len(T)
	need := make(map[byte]int)
	for _, v := range T {
		need[byte(v)]++
	}
	i := 0 //滑动窗口左边界
	left,right:=0,len(S)+1
	for j, v := range S { //j,右边界
		if need[byte(v)] > 0 { //如果查出来有,总数减1
			needCnt = needCnt - 1
		}
		need[byte(v)] -= 1 //如果有,字典减1,如果没有,就设置为0
		if needCnt == 0 {  //步骤一,证明滑块内包含T了
			for { //步骤二,增加i,排除多余元素
				x := S[i]
				if need[x] == 0 {
					break
				}
				need[x] += 1
				i += 1
			}
			if j-i < right-left { //记录结果
				left,right= i,j
				
			}
			need[S[i]] += 1 //步骤三,i再增加一个位置,寻找新的满足条件的窗口
			needCnt += 1
			i += 1
		}
	}
	if right>len(S){//意思就是没变化过
		return ""
	}
	return S[left : right+1]
}

反转字符串 #

写出一个程序,接受一个字符串,然后输出该字符串反转后的字符串。(字符串长度不超过1000)

数据范围: 0≤n≤1000

要求:空间复杂度 O(n),时间复杂度 O(n)

输入:"abcd"
返回值:"dcba"
func solve( str string ) string {
    length:=len(str)
    s:=[]byte(str)
    for i,j:=0,length-1;i<j;i++{
        s[i],s[j]=s[j],s[i]
        j=j-1
    }
    return string(s)
}

最长无重复子数组 #

给定一个长度为n的数组arr,返回arr的最长无重复元素子数组的长度,无重复指的是所有数字都不相同。

子数组是连续的,比如[1,3,5,7,9]的子数组有[1,3],[3,5,7]等等,但是[1,3,7]不是子数组

输入:[2,3,4,5]
返回值:4
说明:[2,3,4,5]是最长子数组 

盛水最多的容器 #

接雨水问题 #

贪心算法 #

分糖果问题 #

主持人调度2 #

模拟 #

旋转数组 #

螺旋矩阵 #

顺时针旋转矩阵 #

设计LRU缓存结构 #

设计LFU缓存结构 #

题外 #

猴子分桃 #

**题目:**海滩上有一堆桃子,五只猴子来分。第一只猴子把这堆桃子平均分为五份,多了一个,这只猴子把多的一个扔入海中,拿走了一份。第二只猴子把剩下的桃子又平均分成五份,又多了一个,它同样把多的一个扔入海中,拿走了一份,第三、第四、第五只猴子都是这样做的,问海滩上原来最少有多少个桃子?

package main 
import "fmt"
func main(){
  fmt.Println(number(0,5,0))
}
func number(c,m,t int)int{//c 桃子初始数量,m猴子数量,t每次分过后桃子数量
  if m==0{
    return c
  }else{
    if t%5==1{
      return number(c,m-1,(t-1)-(t-1)/5)//当猴子开始分桃子时,保证总数c不会变
    }else{
      return number(c+1,5,c+1)//桃子不够猴子分,增加一个桃子的数量,桃子的数量和c是同步增加的
    }
  }
}

//简单版
func number() int {
	var tao1, tao2, tao3, tao4, tao5 int
	for i := 0; i > -1; i++ {
		tao1 = i
		if tao1%5 == 1 {
			tao2 = tao1 - 1 - (tao1-1)/5
			if tao2%5 == 1 {
				tao3 = tao2 - 1 - (tao2-1)/5
				if tao3%5 == 1 {
					tao4 = tao3 - 1 - (tao3-1)/5
					if tao4%5 == 1 {
						tao5 = tao4 - 1 - (tao4-1)/5
						if tao5%5 == 1 {
							break
						}
					}
				}
			}
		}
	}
	return tao1
}

因子之和 #

如果一个数等于它的因子之和,则称该数为“完数”(或“完全数”)。例如,6的因子为1、2、3,而6=1+2+3,因此6是“完数”。编程找出1000之内的所有完数。

package main
import "fmt"
func main(){
  for n:=2;n<1000;n++{
    m:=n
    for i:=1;i<n;i++{
      if n%i==0{
        m=m-i
      }
    }
    if m==0{
      fmt.Println(n)
    }
  }
}