【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, 0
    for 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 = head
    for cur != nil {
        tmp := cur.Next
        cur.Next = pre
        pre = cur
        cur = 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  int
    list      *list.List
    keyToNode 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 { // 书数量超过 capacity
        delete(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 []int

func findKthLargest(nums []int, k int) int {
    num = nums
    return 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+1
    for 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)/2
    if 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 + 2
    for 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 + 2
    for 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, phead
    for end.Next != nil {
        for i := 0; i < k && end != nil; i++ { // 一段反转区间的尾部
            end = end.Next
        }
        if end == nil { // 最后这段区间不完整, 不用再遍历了
            break
        }
        start, next := pre.Next, end.Next
        end.Next = nil
        pre.Next = reverse(start) // 前面让 end.Next = nil, 再用反转就能反转这段区间
        start.Next = next // next 保存了下一段区间的第一个节点
        pre = start
        end = pre // pre 和 end 再次回到类似哨兵位的位置
    }
    return phead.Next
}

func reverse(head *ListNode) *ListNode { // 反转链表(一模一样的代码)
    var pre *ListNode = nil
    var cur *ListNode = head
    for cur != nil {
        next := cur.Next
        cur.Next = pre
        pre = cur
        cur = 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-1
        for 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 = -10000
    dp := 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+1
    for cur <= right { // 这里注意一下, 用的是 right 做边界
        if nums[cur] < nums[key] {
            pre++
            swap(&nums[pre], &nums[cur])
        }
        cur++
    }
    swap(&nums[pre], &nums[key])
    key = pre
    quickSort(left, key-1, nums)
    quickSort(key+1, right, nums)
}

func getmid(left, right int, num []int) int { // 三数取中
    mid := (left+right)/2
    if 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-v
            return []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 版本代码记住,大概就没有什么问题。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

稳定扩散AI 纹理生成器

推荐基于稳定扩散(stable diffusion) AI 模型开发的自动纹理工具&#xff1a; DreamTexture.js自动纹理化开发包 - NSDT 什么是稳定扩散&#xff1f; 从技术上讲&#xff0c;Stable Diffusion 是一种用于机器学习的潜在扩散模型 &#xff08;LDM&#xff09;。这种类型的专用深…

2023-11笔记

1.switch空指针异常 Exception in thread "main" java.lang.NullPointerException:Cannot invoke "String.hashCode()" because "<local2>" is nullat Study5.Test03.main(Test03.java:6)我们由此可以知道&#xff0c;switch语句部分情况下…

@ConfigurationProperties使用

一直有个疑问,在使用ConfigurationProperties注解作用一个配置类时,如果该配置类继承了一个父类,那么父类的那些配置字段是否可以读取配置信息。 答案是可以的&#xff0c;前提是父类对应字段的set方法是public。 BaseProperties.java Getter Setter public class BasePropert…

51单片机PCF8591数字电压表LCD1602液晶显示设计( proteus仿真+程序+设计报告+讲解视频)

51单片机PCF8591数字电压表LCD1602液晶设计 ( proteus仿真程序设计报告讲解视频&#xff09; 仿真图proteus7.8及以上 程序编译器&#xff1a;keil 4/keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0060 51单片机PCF8591数字电压表LCD1602液晶设计 1.主要功能&a…

目标检测算法改进系列之Backbone替换为VanillaNet

VanillaNet简介 简介&#xff1a;VanillaNet是一种在设计中融入优雅的神经网络架构&#xff0c;通过避免高深度&#xff0c;shortcut和自注意力等复杂操作&#xff0c;VanillaNet简单而强大。每一层都经过精心制作&#xff0c;紧凑而直接&#xff0c;在训练后对非线性激活函数…

【赠书活动】嵌入式虚拟化技术与应用

文章目录 前言 1 背景概述 2 专家推荐 3 本书适合谁&#xff1f; 4 内容简介 5 书籍目录 6 权威作者团队 7 粉丝福利 前言 随着物联网设备急剧增长和万物互联应用迅速发展&#xff0c;虚拟化技术成为嵌入式系统焦点。这反映了信息技术迫切需求更高效、灵活和可靠系统。…

漫谈广告机制设计 | 万剑归宗:聊聊广告机制设计与收入提升的秘密(1)

小时候看武侠电视剧《风云》的时候&#xff0c;其中无名有一招叫“万剑归宗”&#xff0c;乃是剑术最高境界。修炼的口诀是“万气自生&#xff0c;剑冲废穴&#xff1b;归元武学&#xff0c;宗远功长”&#xff0c;也就是说欲练此功&#xff0c;先自废武功&#xff0c;然后回归…

it is missing from your system. Install or enable PHP‘s sodium extension.

Composer with –ignore-platform-reqext-sodium it is missing from your system. Install or enable PHP’s sodium extension. 出现如上的报错是的 开启php.ini中的 sodium的扩展

华为防火墙二层透明模式下双机热备主备备份配置(两端为交换机)

这种模式只能是主备备份模式&#xff0c;不能是负载分担&#xff0c;因为会有环路。 故障切换是&#xff0c;如果主故障&#xff0c;主设备所有接口全都会down状态&#xff0c;然后再up一次&#xff0c;用于改变mac转发表。 FW1 hrp enable hrp interface GigabitEthernet1/0…

数据结构 树和二叉树

敬请期待 1. 术语详解 树&#xff1a; 二叉树&#xff1a; 森林&#xff1a; 完全二叉树&#xff1a; 满二叉树&#xff1a; 二叉排序树&#xff1a; 二叉搜索树&#xff1a; 哈夫曼树&#xff1a;分为左小右大和左先右后两种构造方法。 平衡二叉树&#xff1a; 线索…

网络运维Day08

文章目录 克隆虚拟机修改网卡命名规则Linux配置IP地址 虚拟网络类型NAT模式隔离模式VMware-NAT模式 设置VMnet8网络模式为虚拟机自动配置IP地址 管理IP地址-ifconfig命令常用网络工具ip命令追踪路由ss命令ping命令 练习总结 克隆虚拟机 通过克隆技术&#xff0c;可以快速且方便…

【C语言 | 基础】计算机的位(bit)、字节(Byte)、字(word)、双字(DWord)、四字(QWord)分别代表什么

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

PACD管理循环

目录 一、何谓管理&#xff1f; 二、PDCA构成 三、解读PDCA 四、总结 一、何谓管理&#xff1f; 1、所谓管理&#xff1a;是指利用拥有的资源&#xff0c;设定计划&#xff0c;并为达到此计划之一切活动的全体。 2、管理是由维持(SDCA)及改善(PDCA)这两个轮子构成。维持是…

LeetCode【215】数组中第k大的元素

题目&#xff1a; 思路&#xff1a; https://zhuanlan.zhihu.com/p/59110615 代码&#xff1a; public int findKthLargest(int[] nums, int k) {PriorityQueue<Integer> queue new PriorityQueue<>((o1, o2) -> o1 - o2);for (int i 0; i < nums.lengt…

【lib.dll.a.so】Windows和Linux两个系统下的库文件

1.静态库&&动态库 Windows平台下&#xff1a;静态库后缀为.lib&#xff0c;动态库后缀为.dll Linux平台下&#xff1a;静态库格式为lib**.a&#xff0c;动态库格式为lib**.so 谈论两者区别之前&#xff0c;需要对程序编译和运行有一个大致认识&#xff1a; 代码想要…

拍照打闪光灯,图片出现亮度分层的原因分析

1、问题背景 上周有读者咨询了一个图像异常的问题。拍照打闪光灯&#xff0c;图片出现亮度分层&#xff0c;一半过曝一半偏暗&#xff0c;问题现象如下图所示&#xff0c;本文主要分析一下&#xff0c;出现此问题现象的原因。 2、问题分析 读者那边反馈说是限制了 sensor 的最…

单片机第三季-第五课:GPIO控制LED

目录 1&#xff0c;GPIO数据手册 1.1&#xff0c;端口配置寄存器 ​1.2&#xff0c;端口输入数据寄存器和端口输出数据寄存器 ​1.3&#xff0c;端口位设置/清除寄存器 1.4&#xff0c;端口位清除寄存器 2&#xff0c;原理图分析和MDK工程 3&#xff0c;写代码通过GP…

华为防火墙二层透明模式下双机热备负载分担配置(两端为路由器)

这种模式只做负载分担&#xff0c;不能是主备备份&#xff0c;因为主备备份模式下&#xff0c;备设备会把vlan down掉&#xff0c;如果是主备备份模式&#xff0c;那在主挂后&#xff0c;备的状态在切换过程中先起vlan&#xff0c;再建立ospf邻接&#xff0c;那业务会断线较久&…

Labview利用声卡捕获波形

一般的计算机上自带的声卡&#xff0c;均既有A/D功能&#xff0c;又有D/A功能&#xff0c;就是一款具备基本配置的数据采集卡&#xff0c;并且技术成熟&#xff0c;性能稳定。 后台如下&#xff1a;

前端开发学习指南

前端是一个看似入门门槛不高&#xff0c;但要学好很难的领域。前端的知识体系庞杂又松散&#xff0c;技术演进快&#xff0c;如果摸不清脉络的话很容易陷入盲人摸象的困境甚至跑偏。 其实只要掌握了正确的方法&#xff0c;学习前端和学好前端就只是个时间问题&#xff0c;希望下…