【CodeTop】TOP 100 刷题 1-10

文章目录

  • 1. 无重复字符的最长子串
    • 题目描述
    • 代码与思路
  • 2. 反转链表
    • 题目描述
    • 代码与解题思路
  • 3. LRU 缓存
    • 题目描述
    • 代码与解题思路
  • 4. 数组中的第K个最大元素
    • 题目描述
    • 代码与解题思路
  • 5. K 个一组翻转链表
    • 题目描述
    • 代码与解题思路
  • 6. 三数之和
    • 题目描述
    • 代码与解题思路
  • 7. 最大子数组和
    • 题目描述
    • 代码与解题思路
  • 8. 手撕快排
    • 题目描述
    • 代码与解题思路
  • 9. 两数之和
    • 题目描述
    • 代码与解题思路
  • 10. 最长回文子串
    • 题目描述
    • 代码与解题思路

1. 无重复字符的最长子串

题目链接:3. 无重复字符的最长子串

题目描述

代码与思路

func lengthOfLongestSubstring(s string) int {win := map[byte]int{}slow, fast, ans := 0, 0, 0for fast < len(s) {for win[s[fast]] == 1 { // 字符不能重复,如果重复就得将重复的字符出窗口win[s[slow]]--slow++}ans = max(ans, fast-slow+1)win[s[fast]]++fast++}return ans 
}func max(a, b int) int {if a > b {return a}return b
}

我这里使用的是传统的滑动窗口的解题思路,根据题目条件设置出窗口,每轮遍历将一个新的值入窗口,朴素,但是思路简单好用。足够了。

2. 反转链表

题目链接:206. 反转链表

题目描述

代码与解题思路

/*** Definition for singly-linked list.* type ListNode struct {*     Val int*     Next *ListNode* }*/
func reverseList(head *ListNode) *ListNode {var pre *ListNode = nil // 这里注意一下, 不能用 pre := nil, 语法不支持var cur *ListNode = headfor cur != nil {tmp := cur.Nextcur.Next = prepre = curcur = tmp}return pre
}

这里要注意一下,反转链表开始遍历的时候是 pre,cur 一前一后,而 go 语言不支持 pre := nil 这个操作,所以得用 var pre *ListNode,别搞错了

3. LRU 缓存

题目链接:146. LRU 缓存

题目描述

代码与解题思路

type entry struct {key, value int
}type LRUCache struct {capacity  intlist      *list.ListkeyToNode map[int]*list.Element
}func Constructor(capacity int) LRUCache {return LRUCache {capacity,list.New(),map[int]*list.Element{},}
}func (c *LRUCache) Get(key int) int {node := c.keyToNode[key]if node == nil { // 没找到这本书return -1}c.list.MoveToFront(node)return node.Value.(entry).value
}func (c *LRUCache) Put(key int, value int)  {if node := c.keyToNode[key]; node != nil { // 有这本书node.Value = entry {key, value}c.list.MoveToFront(node)return}c.keyToNode[key] = c.list.PushFront(entry{key, value}) // 新书,放在最上面if len(c.keyToNode) > c.capacity { // 书数量超过 capacitydelete(c.keyToNode, c.list.Remove(c.list.Back()).(entry).key) // 去掉最后一本书}
}/*** Your LRUCache object will be instantiated and called as such:* obj := Constructor(capacity);* param_1 := obj.Get(key);* obj.Put(key,value);*/

用的是双向链表 + 哈希的方法:

  1. get 把缓存取出,放在链表表头
  2. put 放入新缓存,如果已经存在 key,就取出放在表头,并更新 value;如果不存在 key,就放在表头(如果超过 capacity 就把链表尾移除)

双向链表能让我们操作头尾的时候是 O(1) 的复杂度,而哈希能让我们在 get 查找的时候是 O(1) 的复杂度。

代码中需要注意的点是:

  1. 将节点移到表头可以用 go 语言 list 中自带的 MoveToFront 方法,list 相关可以去学习这一篇文章:Go 基础:list 的介绍与使用
  2. 在删除元素的时候,我们使用的两个数据结构:map 和 list 中的元素都要记得删除,所以代码最后一段,我们 delete 和 Remove 同时在使用

4. 数组中的第K个最大元素

题目链接:215. 数组中的第K个最大元素

题目描述

代码与解题思路

var num []intfunc findKthLargest(nums []int, k int) int {num = numsreturn qselect(0, len(nums)-1, len(num)-k)
}func qselect(left, right, k int) int { // 双指针快排模板mid := getmid(left, right) // 三数取中(也可以随机选数)swap(&num[left], &num[mid]) // 选出来的数换 key(基准值)key, prev, cur := left, left, left+1for cur <= right { // 这里的 right 其实就是数组最右边的下标if num[cur] < num[key] {prev++swap(&num[cur], &num[prev])}cur++}swap(&num[key], &num[prev])key = prev // key 的左区间比 key 小, 右区间比 key 大, 再分两路递归if key < k {qselect(key+1, right, k)} else if key > k {qselect(left, key-1, k)}return num[k] // 如果 key 的位置正好是 k, 那就找到要取的数了
}func getmid(left, right int) int { // 三数取中mid := (left+right)/2if num[mid] > num[left] {if num[right] > num[mid] {return mid} else if num[right] < num[left] {return left} else {return right}} else {if num[right] > num[left] {return left} else if num[right] < num[mid] {return mid} else {return right}}return -1
}func swap(a, b *int) {*a, *b = *b, *a
}

这道题有两个思路,一个是用快排来解,通过快排的基准值 key 来锁定倒数第 k 个数,但是 LeetCode 最近新添加了一个全是 1 的样例,如果说快排遇到有序数组可以用随机选数或者三数取中来优化,那么,遇到全是同一个数的场景,快排当场退化成 N 方复杂度,算是快排的缺陷了。

我上面的使用的就是快排,所以现在 LeetCode 最后一个全是 1 的样例过不去(别尬黑,LeetCode 官方自己的题解都过不去)

我使用的是快排的双指针法,代码还是比较好理解的,直接读代码就行。

那这个时候就只能用堆排来做了,虽然复杂度是 O(N*logk) 但是 logk 几乎可以忽略不计,所以也能当做是 O(N) 的复杂度,下面是用堆排的算法:

func findKthLargest(nums []int, k int) int {n := len(nums)buildMaxHeap(nums, n) // 建堆操作, 我们需要一大堆来执行堆排for end := n-1; end > 0; end-- { // 排序成升序数组(堆排)swap(&nums[end], &nums[0]); // 头尾交换AdjustDown(nums, end, 0) // 向下调整(向下调整的左右子树必须是大堆)}return nums[n-k] // 返回倒数第 k 位置的数
}func buildMaxHeap(nums []int, n int) { // 建一个大堆for i := (n-2)/2; i >= 0; i-- { // 不需要遍历叶子节点(如果不明白就画个图)AdjustDown(nums, n, i)}
}func AdjustDown(nums []int, n, parent int) { // 向下调整操作child := parent*2+1 // (左孩子)子节点 = 父节点 * 2 + 1, 右孩子是 * 2 + 2for child < n { // 子节点需要在堆(数组)的范围内if (child+1) < n && nums[child+1] > nums[child] { // 比较左右孩子大小child++ // 右孩子大, 那就往右孩子那边调整}if nums[child] > nums[parent] { // 如果子节点大于父节点, 就交换swap(&nums[child], &nums[parent]);parent = child // child 作为新的父节点child = parent*2+1 // 找下一个孩子} else {break // 这部分已经是大堆了}}
}func swap(a, b *int) {*a, *b = *b, *a
}

之前因为一直没有总结,所以堆排我总是忘记怎么做,今天我就要把堆排拿下。

首先是建堆操作,比如说:[3,1,2,5,6,4] 这个数组建堆:

func buildMaxHeap(nums []int, n int) { // 建一个大堆for i := (n-2)/2; i >= 0; i-- { // 不需要遍历叶子节点(如果不明白就画个图)AdjustDown(nums, n, i)}
}

建堆操作的代码,i 不需要从叶子节点开始,为什么 -2,就是为了让 i 能从 2 这个节点,也就是父节点开始遍历,不需要从叶子节点开始,(n-2)/2 无论是什么情况,都能从最右边的第一个父节点开始。

然后通过向下调整操作,也就这段代码:

func AdjustDown(nums []int, n, parent int) { // 向下调整操作child := parent*2+1 // (左孩子)子节点 = 父节点 * 2 + 1, 右孩子是 * 2 + 2for child < n { // 子节点需要在堆(数组)的范围内if (child+1) < n && nums[child+1] > nums[child] { // 比较左右孩子大小child++ // 右孩子大, 那就往右孩子那边调整}if nums[child] > nums[parent] { // 如果子节点大于父节点, 就交换swap(&nums[child], &nums[parent]);parent = child // child 作为新的父节点child = parent*2+1 // 找下一个孩子} else {break // 这部分已经是大堆了}}
}

就能建出一个大堆:

最后再用堆排序操作,把数组排成一个升序:

func findKthLargest(nums []int, k int) int {n := len(nums)buildMaxHeap(nums, n) // 建堆操作, 我们需要一大堆来执行堆排for end := n-1; end > 0; end-- { // 排序成升序数组(堆排)swap(&nums[end], &nums[0]); // 头尾交换AdjustDown(nums, end, 0) // 向下调整(向下调整的左右子树必须是大堆)}return nums[n-k] // 返回倒数第 k 位置的数
}

因为是一个大堆,所以进行向下调整操作的时候就能够将堆中最大的数调整到堆顶,然后将堆顶(也就是最大的数放到数组尾,让 end–,也就是让堆的 size–,这样向下调整的时候就调整不到放在数组尾的数了),以此类推,就能将数组排序成升序数组。

5. K 个一组翻转链表

题目链接:25. K 个一组翻转链表

题目描述

代码与解题思路

/*** Definition for singly-linked list.* type ListNode struct {*     Val int*     Next *ListNode* }*/
func reverseKGroup(head *ListNode, k int) *ListNode {phead := &ListNode{Next: head}pre, end := phead, pheadfor end.Next != nil {for i := 0; i < k && end != nil; i++ { // 一段反转区间的尾部end = end.Next}if end == nil { // 最后这段区间不完整, 不用再遍历了break}start, next := pre.Next, end.Nextend.Next = nilpre.Next = reverse(start) // 前面让 end.Next = nil, 再用反转就能反转这段区间start.Next = next // next 保存了下一段区间的第一个节点pre = startend = pre // pre 和 end 再次回到类似哨兵位的位置}return phead.Next
}func reverse(head *ListNode) *ListNode { // 反转链表(一模一样的代码)var pre *ListNode = nilvar cur *ListNode = headfor cur != nil {next := cur.Nextcur.Next = prepre = curcur = next}return pre
}

代码的注释非常的详细,具体的思路就是通过 end 找到需要反转的区间尾,用 next 记录下一个区间,然后反转当前锁定的区间,然后接通反转后的区间和未处理的区间,以此类推。

这道题主要就是考察对操作链表代码的掌控能力。

6. 三数之和

题目链接:15. 三数之和

题目描述

代码与解题思路

func threeSum(nums []int) (ans [][]int) {n := len(nums)sort.Ints(nums)for i, v := range nums[:n-2] {if i != 0 && nums[i-1] == v { // 去重操作continue}if v+nums[i+1]+nums[i+2] > 0 { // 两个优化操作break}if v+nums[n-1]+nums[n-2] < 0 {continue}left, right := i+1, n-1for left < right { // 双指针匹配s := v+nums[left]+nums[right]if s < 0 {left++} else if s > 0 {right--} else {ans = append(ans, []int{v, nums[left], nums[right]})for left < right && nums[left] == nums[left+1] { // 两段去重操作left++}for left < right && nums[right] == nums[right-1] {right--}left++right--}}}return ans
}

需要注意的点:

  1. 三段去重的逻辑
  2. 两段优化的逻辑

下次写三数之和的时候,先把主体代码写完,能跑过了再去把两段可以优化的点给做优化。

7. 最大子数组和

题目链接:53. 最大子数组和

题目描述

代码与解题思路

func maxSubArray(nums []int) (ans int) {ans = -10000dp := make([]int, len(nums)+1)for i := 1; i <= len(nums); i++ {dp[i] = max(nums[i-1], dp[i-1]+nums[i-1])ans = max(ans, dp[i])}return ans
}

这道题我一开始还想着是滑动窗口,其实不是啊,别给搞蒙了,这个是动态规划的题目,具体思路是这样的:

dp 数组存的是这样的:i 位置存的数是 i 位置以及前面位置的最大和,这样我们就能通过 dp[i] = max(nums[i-1], dp[i-1]+nums[i-1]) 也就是通过上一位置取得的最大和来计算当前位置的最大和,利用之前位置算出的值,这就是动态规划的思想

8. 手撕快排

题目链接:912. 排序数组

这道题是个补充题,就是来考手撕快排的。

题目描述

代码与解题思路

func sortArray(nums []int) []int {quickSort(0, len(nums)-1, nums)return nums
}func quickSort(left, right int, nums []int) {if left > right {return}v := getmid(left, right, nums)swap(&nums[v], &nums[left])key, pre, cur := left, left, left+1for cur <= right { // 这里注意一下, 用的是 right 做边界if nums[cur] < nums[key] {pre++swap(&nums[pre], &nums[cur])}cur++}swap(&nums[pre], &nums[key])key = prequickSort(left, key-1, nums)quickSort(key+1, right, nums)
}func getmid(left, right int, num []int) int { // 三数取中mid := (left+right)/2if num[mid] > num[left] {if num[right] > num[mid] {return mid} else if num[right] < num[left] {return left} else {return right}} else {if num[right] > num[left] {return left} else if num[right] < num[mid] {return mid} else {return right}}return -1
}func swap(a, b *int) {*a, *b = *b, *a
}

没什么可说的,多练习便是,多手撕几次,自然就记住了(还记得我当年的爱好就是没事手撕个快排呢)

9. 两数之和

题目链接:1. 两数之和

题目描述

代码与解题思路

func twoSum(nums []int, target int) []int {mp := map[int]int{}for i, v := range nums {if j, ok := mp[target-v]; ok { // 哈希表中有值 = target-vreturn []int{i, j}}mp[v] = i}return nil
}

两种解法,暴力 N 方,哈希 O(N),把哈希的解法记住就行。

10. 最长回文子串

题目链接:5. 最长回文子串

题目描述

代码与解题思路

func longestPalindrome(s string) string {if s == "" {return ""}start, end := 0, 0 // 最长的回文串区间for i, _ := range s {l1, r1 := extend(i, i, s)   // "aba"l2, r2 := extend(i, i+1, s) // "bb"if r1-l1 > end-start {start, end = l1, r1}if r2-l2 > end-start {start, end = l2, r2}}return s[start:end+1]
}func extend(l, r int, s string) (int, int) { // 中心扩散for l >= 0 && r < len(s) && s[l] == s[r] {l--r++}return l+1, r-1
}

题解有动态规划,但是看不懂,用中心扩散就行了,把中心扩散的 go 版本代码记住,大概就没有什么问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/168133.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023年成为优秀自动化测试工程师的 7 个步骤!

“测试自动化测试工程师可以将你从充满代码的世界中拯救出来。”企业完全同意这一说法&#xff0c;这就是您在自动化测试行业中看到大量就业机会的原因。我在 Quora 上收到了很多与自动化测试中的职业选择相关的答案请求&#xff0c;以及人们如何在有或没有手动测试经验的情况下…

Visual Studio 2019下编译OpenCV 4.7 与OpenCV 4.7 contrib

一、环境 使用的环境是Win10,Visual Studio 2019,Cmake3.28,cdua 11.7&#xff0c;cudnn 8.5,如果只是在CPU环境下使用&#xff0c;则不用安装CUDA。要使用GPU处理&#xff0c;安装好CUDA之后&#xff0c;要测试安装的CUDA是否能用。不能正常使用的话&#xff0c;添加一下系统…

机器学习---多分类SVM、支持向量机分类

1. 多分类SVM 1.1 基本思想 Grammer-singer多分类支持向量机的出发点是直接用超平面把样本空间划分成M个区域&#xff0c;其 中每个区域对应一个类别的输入。如下例&#xff0c;用从原点出发的M条射线把平面分成M个区域&#xff0c;下图画 出了M3的情形&#xff1a; 1.2 问题…

图论10-哈密尔顿回路和哈密尔顿路径+状态压缩+记忆化搜索

文章目录 1 哈密尔顿回路2 哈密尔顿回路算法实现2.1 常规回溯算法2.2 引入变量记录剩余未访问的节点数量 3 哈密尔顿路径问题4 状态压缩4.1 查看第i位是否为14.2 设置第i位是为1或者04.3 小结4.4 状态压缩在哈密尔顿问题中的应用 5 记忆化搜索5.1 记忆化搜索与递推区别5.2 记忆…

前端面试系列之工程化篇

如果对前端八股文感兴趣&#xff0c;可以留意公重号&#xff1a;码农补给站&#xff0c;总有你要的干货。 前端工程化 Webpack 概念 本质上&#xff0c;webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具。当 webpack 处理应用程序时&#xff0c;它会在内部从一个…

什么工具可以制作照片书并分享到微信?

大家平时在微信朋友圈&#xff0c;有没有看到别人发的翻页效果的照片书&#xff1f;这种照片书&#xff0c;通过链接或者二维码就能够在线观看&#xff0c;仿真翻页效果&#xff0c;就跟真实的看纸质相册一样&#xff0c;阅读体验感真的是超级nice&#xff01; 那你们知道这种…

7-Zip的介绍和【阿里云盘】的使用

7zip从入门到入坑 前言一、7-zip的介绍和安装1、基本介绍1&#xff09;7-Zip 主要特征2&#xff09;支持格式3&#xff09;基础功能4&#xff09;安装环境需求 2、基本操作&#xff08;1&#xff09;简便的界面&#xff08;2&#xff09;发生的问题 二、阿里云盘的使用1、“exe…

十分钟了解自动化测试

自动化测试 自动化测试的定义&#xff1a;使用一种自动化测试工具来验证各种软件测试的需求&#xff0c;它包括测试活动的管理与实施、测试脚本的开发与执行。 自动化测试只是测试工作的一部分&#xff0c;是对手工测试的一种补充; 自动化测试绝不能代替手工测试;多数情况下&…

容器网络-Underlay和Overlay

一、主机网络 前面讲了容器内部网络&#xff0c;但是容器最终是要部署在主机上&#xff0c;跨主机间的网络访问又是怎么样的&#xff0c;跨主机网络主要有两种方案。 二、 Underlay 使用现有底层网络&#xff0c;为每一个容器配置可路由的网络IP。也就是说容器网络和主机网络…

卡码网语言基础课 | 11. 句子缩写

目录 一、 字符串大小的比较 二、 ASCII码值 三、 基本框架代码 四、 解题思路 4.1 首字母问题 4.2 判定小写字母 4.3 小写字母转换为大写字母 五、空格判断 六、 代码模块化 6.1 满足的条件 6.2 代码完善 七、 题目解答 7.1 原始代码 7.2 改进代码 八、 拓展与…

.NET快速对接极光消息推送

什么是消息推送&#xff1f; 很多手机APP会不定时的给用户推送消息&#xff0c;例如一些新闻APP会给用户推送用户可能感兴趣的新闻&#xff0c;或者APP有更新了&#xff0c;会给用户推送是否选择更新的消息等等&#xff0c;这就是所谓的“消息推送”。 常见的一些APP消息推送…

迷雾系统-人物驱散迷雾

使用linerRender,将人物移动数据动态添加进去&#xff0c;同样是特殊层级让FogCamera渲染 EndCapVertices的数量越多&#xff0c;矩形就变为一个椭圆形的形状&#xff0c;更适合圆形视野探索 当拐点的两个点距离太近&#xff0c;LineRender会发生扭曲&#xff0c;解决方案是在…