Leecode总结

161 阅读11分钟

【栈】

leetcode.cn/problems/va…

image.png

示例:'{()[()]}'

特点:可能层层叠叠,但是类似于消消乐,需要从里面最小的分子消掉,然后向外继续消灭相同的,这种就需要用到先进后出的栈,因为越晚进来的越早配对,配对完就出栈去掉

解法:先进后出的栈,遍历过程中和栈顶匹配就出栈,否则入栈,遍历完成如果站内还有元素,说明没有匹配完整

leetcode.cn/problems/ev…

【dfs】

起点分几种

1.确定从一个点开始 dfs(0) 比如 从左上角开始一共有多少路径 左上角是一个确定的点

2.起点不确定,需要寻找符合要求的起点,那就要for循环寻找,不满足就跳过

for i := 0; i < m; i++ {
   for j := 0; j < n; j++ {
      if board[i][j] == word[0] {
         tmp[i][j] = true
         if dfs(i, j, 1, tmp) {
            return true
         }
         tmp[i][j] = false
      }
   }
}

空间复杂度,主要有两个来源 辅助空间 递归栈 取较大值 时间复杂度 看dfs调用的次数

数组

【有序数组剔除重复项,原地操作】

有个模板,采用快慢指针,记住模板即可,根据不同题目,判断条件稍有不同

1.剔除所有重复 leetcode.cn/problems/re…

2.允许两个重复项 leetcode.cn/problems/re…

这里面关键信息是 有序 ,所以就不用每次遍历了,和前面一个元素比较即可

模板如下,一共6小步

func removeDuplicates(nums []int) int {
//第一步 特殊情况,不满足遍历条件,直接返回
	n := len(nums)
	if n < 2 {
		return n
	}
        //第二步 设置快慢指针的初始值 slow是结果数组里下一个要填充的位置 fast是当前待检查的元素位置
	slow, fast := 2, 2
        //第三步 慢指针每次循环,后移一位
	for ; slow < n; slow++ {
        //第四步 快指针 找满足题目要求的下一个元素 
		for fast < n && nums[fast] == nums[slow-2] {
			fast++
		}
                //第五步 到头,直接跳出循环
		if fast == n {
			break
		}
                //第六步 nums[fast]即为结果数组里的下一个元素,赋值给slow,fast后移
		nums[slow] = nums[fast]
		fast++
	}
	return slow
}

上面示例中判断条件 是找 最多重复两次的元素 如果 nums[fast] == nums[slow-2] 成立 ,说明 nums[fast] = nums[slow-1] = nums[slow-2] 就是三个连续了,不满足,所以继续找,直到 不等于nums[slow-2] 最多有两个重复的,即为所求

如果是26题,删除所有重复,判断条件应该是 nums[fast] == nums[slow-1] 直到这个条件不成立,说明不是重复元素,nums[fast]才是下一个要加入结果数组的元素

快慢指针的初始位置 也不同
删除所有重复元素,结果只放一个元素绝对能满足 不重复的条件,所以 初始值为1
允许两个重复元素,结果跳过两个元素也满足这个条件,所以初始值为2 ,前面的0,1位置不需要检查肯定满足

【有序数组查找某个值】

有序肯定采用二分法查找 有序数组可能经过变换,比如从某一个位置旋转数组,从位置i开始,后半段和前半段调换位置,这种场景二分的时候要考虑一些特殊情况,有模板可用

func search(nums []int, target int) int {
第一步 处理特殊情况直接返回
	n := len(nums)
	if n < 1 {
		return -1
	}
	if n == 1 {
		if nums[0] == target {
			return 0
		}
		return -1
	}
        第二步 设置左右指针位置 
	l, r := 0, n-1
        第三步 for循环条件
	for l <= r {
        第四步 找中间值,满足条件直接返回
		mid := (l + r) / 2
		if nums[mid] == target {
			return mid
		}
        第五步 判断 如果前半部分 是有序的
		if nums[l] <= nums[mid] {
                第六步 target在前半部分中 更新右指针
			if nums[l] <= target && target < nums[mid] {
				r = mid - 1
			} else {
                        第七步 前半部分有序且target不在范围中
				l = mid + 1
			}
		} else {
                第八步 后半部分有序 并且target在其中 更新左指针
			if nums[mid] < target && target <= nums[n-1] {
				l = mid + 1
			} else {
                        //第九步 后半部分有序且 target不在其中 更新右指针
				r = mid - 1
			}
		}
	}
	return -1
}

上面的模板是在数组升序没有重复元素的情况下,如果有重复元素,用第五步的判断证明不了是有序的,举个例子 111111121111111111,中间值和左边界都是1,但左边不是有序的,所以需要在第五步前面判断特殊case,这种特殊case 就是 左右中对应的值都是一样的可没有办法判定哪边是有序的,所以只能两个边界往里缩一位(因为去掉这两个数其实对数组的顺序和最终的结果都不会产生影响,所以去重一直到可以判断哪一个区间是有序的),在第五步前面添加如下代码,具体看leetcode.cn/problems/se…

if nums[l] == nums[mid] && nums[mid] == nums[r] { l++ r-- continue }

升序数组
leetcode.cn/problems/se…

非降序数组
leetcode.cn/problems/se…

数组旋转 leetcode.cn/problems/fi…

【深度优先遍历】

【路径问题】

遍历路径,在遍历的过程中不断更新结果值(比如这棵树的最小值等),dfs方法不需要返回值,在内部更新迭代结果值

leetcode.cn/problems/cl… (路径走一遍) leetcode.cn/problems/se… (遍历树找到比根值大的最小值)

leetcode.cn/problems/bi… (数的中序遍历)

leetcode.cn/problems/va… (遍历二叉树,找到不满足条件的值更新结果)

遍历路径找出所有满足条件的路径,这种情况需要注意路径回溯

leetcode.cn/problems/pa… (有多少种满足条件路径)

leetcode.cn/problems/su… (剪枝去重)

【修改链表结构】

修改链表结构的感觉难度比较大,不太好统一规律

leetcode.cn/problems/fl… (dfs每次要返回连接的node)

leetcode.cn/problems/po… (思路比较清晰但是难以第一时间想到,不适合统计规律)

leetcode.cn/problems/po… (补充next节点,用层序遍历,出入栈,已经不是深度优先遍历的问题)

【翻转链表】

思想就是 设立3个节点,分别是

1.done(已经翻转完成的链表末端)

2.p(当前要翻转的节点)

3.next(即将翻转的节点p的下一个节点),3个节点遍历一直走到末尾

代码模板

func reverseList(head *ListNode) *ListNode {
   //第一步 特殊情况直接返回
   if head == nil {
      return nil
   }
   //第二步 给三个节点赋值,done初始是nil
   var done *ListNode
   p, next := head, head.Next
   //第三步 设立for循环结束条件,这是整个链表翻转的结束条件
   for p != nil {
      //第四步 3个节点赋值后移
      p.Next = done
      done = p
      p = next
      //注意判断边界条件
      if next != nil {
         next = next.Next
      }
   }
   //第五步 done是翻转后的第一个节点,直接返回
   return done
}

以上示例中对于链表部分翻转(left到right)的情况,上面的方法for循环的判断应该是p!= right作为翻转的终点,部分翻转的找到断裂处的4个节点,翻转之后重新连接即可,代码如下 leetcode.cn/problems/re…

func reverseBetween(head *ListNode, left int, right int) *ListNode {
	//tmp从prehead开始,防止left是1的情况
	tmp := &ListNode{-1, head}
        prehead := tmp
	var p1, p2, p3, p4 *ListNode//分别是left的前一位,left,right,right的后一位
	i := 0
	//找断裂处p1,p3
	for tmp != nil {
		if i == left-1 {
			p1 = tmp
		}
		if i == right {
			p3 = tmp
			break
		}
		tmp = tmp.Next
		i++
	}
	p2, p4 = p1.Next, p3.Next //4个断裂处的节点都保存了
	//这个方法翻转l-r的节点
	var reverse = func(l, r *ListNode) (*ListNode, *ListNode) {
                //第一步
		var done *ListNode
		p := l
		next := p.Next
                //第二步
		for done != r {
                        //第三步
			p.Next = done
			done = p
			p = next
			if next != nil {
				next = next.Next
			}
		}
                //第四步
		return r, l //返回反转顺序即可
	}
	l, r := reverse(p2, p3)
	p1.Next, r.Next = l, p4//重新连接
	
	return prehead.Next
}

岛屿区域问题

核心思路就是从边界下手,找到符合条件的元素(比如是特定的值‘O';比前一个元素大的值等),同化这个元素或者标记出来

leetcode.cn/problems/su… (被包围的区域同化,反过来找没有被包围的)

leetcode.cn/problems/pa… (找从边界开始逐渐增高的区域,本质还是从边界开始着手遍历)

还有一些是求有多少独立分隔的区域,这种每次从一个位置入手消除区域元素,不是边界思路了,消除到碰壁就返回,一次循环消除一个区域

leetcode.cn/problems/nu… (求岛屿数量,一边消灭岛屿,一遍更新结果值)

leetcode.cn/problems/ba… (甲板上的战舰就是一个独立的区域,求区域个数)

【动态规划】

适合的场景

1.结果求多少种可能性,极值(比如最大最小值)

2.后面位置的结果跟前面位置有关联

leetcode.cn/problems/de…

leetcode.cn/problems/wo… (死记吧,我想不出来)

leetcode.cn/problems/pa…

一维数组的案例

leetcode.cn/problems/un…

二维数组案例

还可以使用滚动数组优化空间复杂度

leetcode.cn/problems/in…

树 顺序遍历

leetcode.cn/problems/bi…

【回文字符串】

回文字符串 可以考的的场景一般有

1.一个字符串划分子字符串,一共可以有多少种组合方式(路径问题,用回溯遍历)

2.一个字符串分割最少回文子字符串的个数(极值问题,动态规划)

需要判断s【i,j】是否为回文串,动态规划的思路是这样的

image.png

代码实现为

    n := len(s)
    g := make([][]bool, n)
    //先全部设为true
    for i := range g {
        g[i] = make([]bool, n)
        for j := range g[i] {
            g[i][j] = true
        }
    }
    for i := n - 1; i >= 0; i-- {
        for j := i + 1; j < n; j++ {
            g[i][j] = s[i] == s[j] && g[i+1][j-1]
        }
    }

【两个线程交替打印ab】

用go实现非常的优雅,两个channel,用select监听,执行到一个case给另一个case的channel放数据,交替执行,注意sleep后面要显式的写明时间是秒,否则默认是纳秒会提前退出,最后一个打印协程可能不执行

func main() {
   flag1 := make(chan struct{}, 1)
   flag2 := make(chan struct{}, 1)
   flag1 <- struct{}{}
   for i := 0; i < 10; i++ {
      select {
      case <-flag1:
         go func() {
            fmt.Print("A")
            flag2 <- struct{}{}
         }()
      case <-flag2:
         go func() {
            fmt.Print("B")
            flag1 <- struct{}{}
         }()
      }
   }
   time.Sleep(5 * time.Second)
}

【IP和数字相互转换】

image.png

image.png

解释:IP转换数字就是按照.分割分别拿出来4个数,前三个分别位移相应的位数即可; 数字转换成IP,先把原始数据的后8位置0,与原始数据相减就是最后8位二进制的数值,循环操作得到4个数值用.串起来就是IP地址了

func ipToNum(ip string) int {
   strs := strings.Split(ip, ".")
   if len(strs) != 4 {
      return 0
   }
   nums := make([]int, 4)
   for i := 0; i < 4; i++ {
      nums[i], _ = strconv.Atoi(strs[i])
   }
   return nums[0]<<24 + nums[1]<<16 + nums[2]<<8 + nums[3]
}
func numToIp(num int) string {
   nums := make([]int, 4)
   for i := 0; i < 4; i++ {
      src := (num >> 8) << 8 //先左移再右移,后面8位置零
      nums[i] = num - src
      num = num >> 8
   }
   return fmt.Sprintf("%d.%d.%d.%d", nums[3], nums[2], nums[1], nums[0])
}

【three letters】

在其他平台的编程题上做的,一个多小时,还是记录下下来,本来用深度优先遍历,但是时间上太长了,题目让返回一个满足的就行

image.png

大意就是给几个ab,让你安排顺序不能有连续三个的重复字母在一起,思路就是以min作为分隔字母去均匀的隔开max字母,分为两个区间,一个是区间为2个元素的,一个是1个元素的,确定两个区间个数,就能填充得到结果了,比深度遍历要节省时间,返回的格式是 XXYXXYXXYXYXYX的形式

func Solution(A int, B int) string {
   var minMax = func(i, j int) (min, max int) {
      if i < j {
         return i, j
      }
      return j, i
   }
   //获取字母的最大最小值
   min, max := minMax(A, B)
   //因为最小值一共可以划分min+1个缝隙,每个缝隙最多能容纳2个max对应字母,再多就满足不了条件直接返回,节省时间
   if (min+1)*2 < max {
      return ""
   }
   //原始公式是假设min+1个缝隙有x个装2个元素的,装一个元素的就是 总的减去2个的就是 min+1 -x 所有区间加载一起就是max 这样能得到x(two)的值
   two := max - min - 1
   res := ""
   //两者元素一样或者只相差一个的情况
   if two <= 0 {
      for i := 0; i < min; i++ {
         res += "ab"
      }
      c := min + max - len(res)
      //元素较多的补齐其实也就1个
      if A > B {
         for c != 0 {
            res += "a"
            c--
         }
      } else {
         for c != 0 {
            res += "b"
            c--
         }
      }
      return res
   }
   //先装填2个元素的区域
   for i := 0; i < two; i++ {
      if A > B {
         res += "baa"
      } else {
         res += "abb"
      }
   }
   //因为上面为了保证最后是max对应的字母,前面多写了一个min对应的去掉即可,这样就能保证第一个和最后一个区间都被max元素填充
   res = res[1:]
   //还剩下的1区间个数
   d := min + 1 - two
   if A > B {
      for d != 0 {
         res += "ba"
         d--
      }
   } else {
      for d != 0 {
         res += "ab"
         d--
      }
   }
   return res
}