--- title: leetcode everyday author: Logic date: 2024-02-27 categories: [""] tags: [] draft: false --- ## day 1 2024-02-27 ### 543 Diameter of Binary Tree Given the root of a binary tree, return the length of the diameter of the tree. The diameter of a binary tree is the length of the longest path between any two nodes in a tree. This path may or may not pass through the root. The length of a path between two nodes is represented by the number of edges between them. ![0227W27EaKxHpjd2](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0227W27EaKxHpjd2.png) ### 题解 读题, 题目要求寻找二叉树中任意两节点之间的最大距离. 这时这个二叉树更像是一个图的性质而不是树, 即寻找图中任意两节点之间的最大距离. 考虑任意一点到另一点的距离等于其到另一点的父节点的距离减一, 则使用一个二维数组保存每个节点的两个属性: 1. 以该节点为根节点且经过该节点的最大直径 2. 以该节点为根节点的子树中叶子节点到该节点的最大距离 属性1可以通过将该节点的两个子节点的属性2加和并加1来计算. 属性2取两个子节点属性2的最大值并加1来计算. 最后遍历数组求得数组中属性1的最大值即可. 有一点动态规划的思想. 题目中并没有提供二叉树的节点总数, 则可以使用动态创建的方法, 在遍历过程中每遇到新节点就在二维数组中增加一项. 这里使用递归来对树进行后序遍历, 对空节点, 设置其属性2的值为-1, 这样保证叶子节点的属性2的值为0. 解题时遇到一个小bug, 使用了一个全局切片(数组)来保存变量时, 第一个测试用例的数据会保留到测试第二个测试用例的过程中, 这大概是因为leetcode的测试是对每个用例直接调用给出的解题入口函数, 因此需要在解题函数中将使用的全局变量初始化一下, 将数组设置为空后问题得到解决. ### 代码 ```go /** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ var length [][]int func diameterOfBinaryTree(root *TreeNode) int { length = nil _ = postorder(root) max := -1 for _, value := range length{ if value[0] > max{ max = value[0] } } return max } func postorder(father *TreeNode) int { if father != nil{ len1 := postorder(father.Left) len2 := postorder(father.Right) len := make([]int,2) // find the max diameter pass through current node from the tree rooted current node len[0] = len1 + len2 + 2 len[1] = max(len1, len2) + 1 length = append(length, len) return len[1] } else { return -1 } } ``` ## day2 2024-02-28 ### 513. Find Bottom Left Tree Value Given the root of a binary tree, return the leftmost value in the last row of the tree. ![0228z98bIXZ3aQeQ](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0228z98bIXZ3aQeQ.png) ### 题解 找到二叉树最底层最左边的节点值, 用层序遍历遍历到最后一层找到最后一层最左边节点的值即可. 实现层序遍历可以使用一个队列, 将当前层节点的所有子节点入队后将当前层节点出队. 在go中可以使用切片实现一个队列. 使用一个标记变量记录当前层所有节点是否有子节点, 若无子节点则当前层为最低层, 返回当前层最左侧节点的值(此时队列中第一个节点的值). ### 代码 ```go /** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ func findBottomLeftValue(root *TreeNode) int { queue := []*TreeNode{root} lowest := false key := -1 var father *TreeNode for !lowest{ queue = queue[key+1:] lowest = true for key, father = range queue{ if(father.Left != nil || father.Right != nil){ lowest = false if(father.Left != nil){ queue = append(queue, father.Left) } if(father.Right != nil){ queue = append(queue, father.Right) } } } } return queue[0].Val } ``` ### 总结 在题解中看到了一个使用深度优先搜索的方法, 记录当前搜索到的层级, 始终保存最大层级的第一个被搜索到的值, 因为使用的是后序遍历, 则每次遇到的当前层大于保存的最大层级时, 该节点就为新的最大层级的第一个节点, 即题目中要求的最左值(leftmost). 算法时间复杂度为O(n)------只遍历一次所有节点. ## day3 2024-02-29 ### 1609. Even Odd Tree A binary tree is named Even-Odd if it meets the following conditions: The root of the binary tree is at level index 0, its children are at level index 1, their children are at level index 2, etc. For every even-indexed level, all nodes at the level have odd integer values in strictly increasing order (from left to right). For every odd-indexed level, all nodes at the level have even integer values in strictly decreasing order (from left to right). Given the root of a binary tree, return true if the binary tree is Even-Odd, otherwise return false. ![0229uX1ZoOdkFEtk](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0229uX1ZoOdkFEtk.png) ### 题解 对二叉树的奇数层, 其节点从左到右是严格递减的(意味着有两个节点的值相同是不允许的), 偶数层是严格递增的. 仍然可以使用昨天题目的层序遍历的方法, 增加一个level变量记录当前层数, 对该层内的节点值进行判断是否符合奇数层和偶数层对应的条件即可. ### 代码 ```go /** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ func isEvenOddTree(root *TreeNode) bool { level := 0 index := -1 var value *TreeNode queue := []*TreeNode{root} saved := 0 for len(queue) != 0{ // remove visited nodes queue = queue[index+1:] index = 0 if(level%2 == 0){ for index, value = range queue{ if(index == 0){ saved = value.Val if(saved %2 != 1){return false} } else{ if(value.Val <= saved || (value.Val%2) != 1){ return false } else{ saved = value.Val } } if(value.Left != nil){queue = append(queue, value.Left)} if(value.Right != nil){queue = append(queue, value.Right)} } level++ } else{ for index, value = range queue{ if(index == 0){ saved = value.Val if(saved %2 != 0){return false} } else{ if(value.Val >= saved || value.Val %2 != 0){ return false } else{ saved = value.Val } } if(value.Left != nil){queue = append(queue, value.Left)} if(value.Right != nil){queue = append(queue, value.Right)} } level++ } } return true } ``` ### 总结 go语言中的for range循环, 如果使用类似`for key, value := range list` 的形式, 那么key, value这两个变量都会在当前作用域下新建, 意味着即使在前面定义了key, key的值在循环结束后也不会被修改. 若想修改之前定义的key值, 需要将value也提前定义好并使用`=`而不是`:=`. go语言中的for range循环时如果使用`:=`会新建两个变量, 然后将slice中的值复制给value变量, 将对应的index值赋值给key变量, 这意味着value变量不会指向数组中对应位置的地址, 而是一个不变的单独地址. ## day4 2024-03-01 ### 2864. Maximum Odd Binary number You are given a binary string s that contains at least one '1'. You have to rearrange the bits in such a way that the resulting binary number is the maximum odd binary number that can be created from this combination. Return a string representing the maximum odd binary number that can be created from the given combination. Note that the resulting string can have leading zeros. Example 1: > Input: s = "010" > Output: "001" > Explanation: Because there is just one '1', it must be in the last position. So the answer is "001". Example 2: > Input: s = "0101" > Output: "1001" > Explanation: One of the '1's must be in the last position. The maximum number that can be made with the remaining digits is "100". So the answer is "1001". ### 题解 题目中说明了给出的字符串中至少有一个1, 因此可以复制一个字符串, 然后遍历原字符串, 遇到第一个1放在最后一位, 0永远插入到倒数第二位, 不是第一个1放在字符串最前面. 由此除保证字符串是奇数的最后一个1以外, 其余的1都在字符串最前面, 其余的0都插入在最前面的一串1和最后的1之间. 保证了字符串是最大的奇数字符串. ### 代码 ```go func maximumOddBinaryNumber(s string) string { s_copy := "" flag := 0 for _, value := range s{ if value == '1'{ if flag != 0{ s_copy = "1" + s_copy } else{ s_copy = s_copy + "1" flag = 1 } } else{ if(len(s_copy) >=2){ s_copy = string(s_copy[:len(s_copy)-1]) + "0" + string(s_copy[len(s_copy)-1]) } else { s_copy = "0" + s_copy } } } return s_copy } ``` ### 总结 在处理字符串的时候像中间某个位置插入字符也要使用双引号, 如插入字符0要用`+"0"`而不是`+'0'`, 此外在截取切片的时候go的切片是左闭右开的. 如[0:3]截取的是0,1,2三个数 ## day5 2024-03-02 ### 977. Squares of a Sorted Array Given an integer array nums sorted in non-decreasing order, return an array of the squares of each number sorted in non-decreasing order. Example 1: Input: nums = [-4,-1,0,3,10] Output: [0,1,9,16,100] Explanation: After squaring, the array becomes [16,1,0,9,100]. After sorting, it becomes [0,1,9,16,100]. Example 2: Input: nums = [-7,-3,2,3,11] Output: [4,9,9,49,121] ### 题解 考虑原数组已经按照非递减排序的情况下, 找到数组中正负交界处元素, 即数组中第一个正数, 以该位置作为起始位置, 使用双指针法, 分别向前和向后遍历数组, 遍历时不断比较两个指针指向的数字的绝对值大小, 将绝对值小的数字平方后追加到结果数组的尾部, 遍历完成即可完成平方值排序. 这样只需要遍历一遍数组即可完成排序. ### 代码 ```go func sortedSquares(nums []int) []int { index := 0 value := nums[0] var result []int for index, value = range nums{ if(value >= 0){ break } } backward := index - 1 forward := index if index != 0{ for _,_ = range nums{ if backward<0{ result = append(result, nums[forward]*nums[forward]) forward++ } else if forward>= len(nums){ result = append(result, nums[backward] * nums[backward]) backward-- } else{ if(abs(nums[forward]) < abs(nums[backward])){ result = append(result, nums[forward]*nums[forward]) forward++ } else{ result = append(result, nums[backward] * nums[backward]) backward-- } } } }else{ for _,_ = range nums{ result = append(result, nums[forward]*nums[forward]) forward++ } } return result } func abs(val int) int { if(val < 0){ return -val }else{ return val } } ``` ### 总结 注意一个go的语法问题, 如果在for range循环中两个变量都使用匿名变量, 则应该使用赋值运算符而不是创建并赋值运算符, 即`for _,_ = range slice` 而不是`for _,_ := range slice`. 这很可能是因为匿名变量默认为已经创建好的变量, 不需要再创建匿名变量本身了. ## day6 2024-03-03 ### 19. Remove Nth Node From End of List Given the head of a linked list, remove the nth node from the end of the list and return its head. ![0303SQETJiUaYzaQ](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0303SQETJiUaYzaQ.png) ### 题解 给出了链表头, 要求移除从后到前的第n个节点. 如果想一遍遍历就完成这个任务. 就使用空间换时间, 使用一个数组保存所有的Next指针的值. 然后让倒数第n+1个元素的Next指针指向n个元素的Next指针即可, 注意处理链表只有一个元素的特殊情况. ### 代码 ```go /** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */ func removeNthFromEnd(head *ListNode, n int) *ListNode { var ptr []*ListNode current := head for current.Next != nil{ ptr = append(ptr, current) current = current.Next } ptr = append(ptr, current) if(len(ptr) == 1){ return nil }else if len(ptr) == n{ return ptr[1] }else{ ptr[len(ptr)-n-1].Next = ptr[len(ptr)-n].Next return head } } ``` ### 总结 在题解中看到大部分使用的是快慢指针的解法, 快慢指针应该是本题想要的解法, 下面贴一个快慢指针的解法示例 ```go /** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */ func removeNthFromEnd(head *ListNode, n int) *ListNode { dummyHead := &ListNode{-1, head} cur, prevOfRemoval := dummyHead, dummyHead for cur.Next != nil{ // n step delay for prevOfRemoval if n <= 0 { prevOfRemoval = prevOfRemoval.Next } cur = cur.Next n -= 1 } // Remove the N-th node from end of list nthNode := prevOfRemoval.Next prevOfRemoval.Next = nthNode.Next return dummyHead.Next } ``` ## day7 2024-03-04 ### 948. Bag of Tokens You start with an initial power of power, an initial score of 0, and a bag of tokens given as an integer array tokens, where each tokens[i] donates the value of tokeni. Your goal is to maximize the total score by strategically playing these tokens. In one move, you can play an unplayed token in one of the two ways (but not both for the same token): **Face-up**: If your current power is at least tokens[i], you may play tokeni, losing tokens[i] power and gaining 1 score. **Face-down**: If your current score is at least 1, you may play tokeni, gaining tokens[i] power and losing 1 score. Return the maximum possible score you can achieve after playing any number of tokens. Example 1: Input: tokens = [100], power = 50 Output: 0 Explanation: Since your score is 0 initially, you cannot play the token face-down. You also cannot play it face-up since your power (50) is less than tokens[0] (100). Example 2: Input: tokens = [200,100], power = 150 Output: 1 Explanation: Play token1 (100) face-up, reducing your power to 50 and increasing your score to 1. There is no need to play token0, since you cannot play it face-up to add to your score. The maximum score achievable is 1. Example 3: Input: tokens = [100,200,300,400], power = 200 Output: 2 Explanation: Play the tokens in this order to get a score of 2: Play token0 (100) face-up, reducing power to 100 and increasing score to 1. Play token3 (400) face-down, increasing power to 500 and reducing score to 0. Play token1 (200) face-up, reducing power to 300 and increasing score to 1. Play token2 (300) face-up, reducing power to 0 and increasing score to 2. The maximum score achievable is 2. ### 题解 本题的目的是最大化score. 一个基本的策略就是通过小的power换取score, 通过score换取大的power, 利用换到的大power赚取中间水平的token的score. 关键在于, 如何找到最大能换取的score. 首先考虑每次进行一次Face-up和Face-down, score没有变化, 只有power增大了, 那么每次都将score置0, 并判断当前能获得的最大score即可. 通过前面的分析可以得出, 算法分为以下几步 1. 将tokens数组排序 2. 判断power是否大于tokens[0], 即最小的token, 若大于, 则计算当前能获得的最大score 3. 将power的值增加目前tokens数组中最大值和最小值(即排好序后的最后一项和第一项)的差值, 同时将tokens数组中第一项和最后一项移除. 重复2, 3步直到power小于tokens[0]或tokens数组长度为0中止. 4. 返回最大的score ### 代码 ```go func bagOfTokensScore(tokens []int, power int) int { sort.Ints(tokens) var powernormal []int remain := power score := 0 powernormal = append(powernormal, score) for len(tokens) != 0 && power > tokens[0]{ remain = power for _, value := range tokens{ if power >= value{ score++ power -= value } else{ break } } powernormal = append(powernormal, score) score = 0 remain += tokens[len(tokens)-1] - tokens[0] if len(tokens) <= 1{ break } tokens = tokens[1:len(tokens)-1] power = remain } sort.Ints(powernormal) return powernormal[len(powernormal)-1] } ``` ### 总结 在实现过程中, 起初使用一个数组来保存每次的score值, 这样空间复杂度略大, 后来查看他人代码, 发现只需要一个max变量来保存当前最大的score值, 并在每次循环计算当前轮次的score值时与当前的最大值比较并根据二者大小更新max变量的值即可, 这样只需要O(1)的空间复杂度. ## day8 2024-03-05 ### 1750. Minimum Length of String After Deleting Similar Ends Given a string s consisting only of characters 'a', 'b', and 'c'. You are asked to apply the following algorithm on the string any number of times: 1. Pick a non-empty prefix from the string s where all the characters in the prefix are equal. 2. Pick a non-empty suffix from the string s where all the characters in this suffix are equal. 3. The prefix and the suffix should not intersect at any index. 4. The characters from the prefix and suffix must be the same. 5. Delete both the prefix and the suffix. Return the minimum length of s after performing the above operation any number of times (possibly zero times). ![0305Qc2qNiDBOKRk](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0305Qc2qNiDBOKRk.png) ### 题解 本题的题目略显复杂, 读起来乍一看让人不明所以, 实际上题目的目标即为从字符串的前后同时删除相同的字符, 删除时只要是相同的就全部删除, 如最前面有一个a最后面有3个a则同时将这四个a删除. 删除的字符串下标不能相交. 思路比较简单, 判断字符串的前后字符是否相同, 然后删除前后的连续相同字符即可, 注意下标不要重叠. 同时注意边界情况, 字符串只有一个字符的情况单独处理一下(直接返回1). ### 代码 ```go func minimumLength(s string) int { forward := 0 backward := 0 for s[0] == s[len(s)-1]{ if len(s) == 1{ return 1 } else { forward = 0 backward = len(s)-1 for forwardforward && s[backward-1] == s[backward] { backward-- } if forward == backward{ return 0 } s = s[forward+1:backward] } } return len(s) } ``` ### 总结 本题值得注意的地方在于for循环中条件的设置, 可能会忽略与运算符两侧条件表达式的前后顺序, 但由于短路机制的存在, 与运算符两侧表达式的前后顺序有时非常重要, 例如在本题中, 如果将forward Input: nums = [1,2,2,3,1,4] > Output: 4 > Explanation: The elements 1 and 2 have a frequency of 2 which is the maximum frequency in the array. > So the number of elements in the array with maximum frequency is 4. Example 2: > Input: nums = [1,2,3,4,5] > Output: 5 > Explanation: All elements of the array have a frequency of 1 which is the maximum. > So the number of elements in the array with maximum frequency is 5. ### 题解 因为题目给出了正整数的范围为1-100, 因此本题可以用简单的数组来解决, 数组下标表示对应的整数, 0不做任何表示. 然后遍历数组将频率最多的元素相加即可. 可以设置一个max标志位来表示当前的最大频率, 相等则增加和, 比max大则将和重置并设max为新的最大值. ### 代码 ```go func maxFrequencyElements(nums []int) int { frequency := make([]int, 101) for _,value := range nums{ frequency[value]++ } max := 0 sum := 0 for _,value := range frequency{ if value > max{ max = value sum = max } else if value == max{ sum += value }else{ continue } } return sum } ``` ### 总结 本题注意考查数据范围, 在数据范围有限的情况下直接使用数组要比哈希表快得多. ## day12 2024-03-09 ### 2540. Minimum Common Value Given two integer arrays nums1 and nums2, sorted in non-decreasing order, return the minimum integer common to both arrays. If there is no common integer amongst nums1 and nums2, return -1. Note that an integer is said to be common to nums1 and nums2 if both arrays have at least one occurrence of that integer. Example 1: > Input: nums1 = [1,2,3], nums2 = [2,4] > Output: 2 > Explanation: The smallest element common to both arrays is 2, so we return 2. Example 2: > Input: nums1 = [1,2,3,6], nums2 = [2,3,4,5] > Output: 2 > Explanation: There are two common elements in the array 2 and 3 out of which 2 is the smallest, so 2 is returned. ### 题解 本题使用两个指针分别指向两个数组, 然后依次移动两个指针比较大小即可, 位于数值更小的位置的数组指针向前移动直到相等或者到数组末尾为止. ### 代码 ```go func getCommon(nums1 []int, nums2 []int) int { index1, index2 := 0 , 0 for index1 Input: nums1 = [1,2,2,1], nums2 = [2,2] > Output: [2] Example 2: > Input: nums1 = [4,9,5], nums2 = [9,4,9,8,4] > Output: [9,4] > Explanation: [4,9] is also accepted. ### 题解 因为数组是无序数组, 寻找二者的相同元素比较困难, 故可先对两数组排序, 然后双指针遍历两个数组找到数组中的相同值. 将值作为key, key对应的value为true放入map中. 这里不需要多余判断map中是否已经存在这个key了, 因为再次给相同的key赋值不会增加新的条目, 而只是覆盖之前的key的值, 我们只需要key来判断map中是否有相同值. ### 代码 ```go func intersection(nums1 []int, nums2 []int) []int { intersection := make(map[int]bool) sort.Ints(nums1) sort.Ints(nums2) index1, index2 := 0,0 for index1 < len(nums1) && index2 < len(nums2){ if nums1[index1] < nums2[index2]{ index1++ }else if nums1[index1] > nums2[index2]{ index2++ }else{ _, ok := intersection[nums1[index1]] if !ok{ intersection[nums1[index1]] = true } index1++ index2++ } } var result []int for key, _ := range intersection{ result = append(result, key) } return result } ``` ### 总结 在查看他人题解过程中, 发现排序其实是没有必要的, 可以直接将一个数组中的值全部作为key, 对应的value为true放入map中. 然后遍历另外一个数组, 同时判断当前遍历的元素在不在map中, 若存在则将其放入结果数组中, 同时将map中key对应的value置为false, 表示该key已经被访问过, 这样可以避免在结果数组中添加重复元素. 一个示例代码如下 ```go func intersection(nums1 []int, nums2 []int) []int { res := make([]int, 0) m := make(map[int]bool, len(nums1)) for _, v := range nums1 { if _, exists := m[v]; exists { continue } m[v] = false } for _, v := range nums2 { used, exists := m[v] if exists && !used { res = append(res, v) m[v] = true } } return res } ``` ## day14 2024-03-11 ### 791. Custom Sort String You are given two strings order and s. All the characters of order are unique and were sorted in some custom order previously. Permute the characters of s so that they match the order that order was sorted. More specifically, if a character x occurs before a character y in order, then x should occur before y in the permuted string. Return any permutation of s that satisfies this property. ![0311fLXVhVrYfXgA](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0311fLXVhVrYfXgA.png) ### 题解 本题初始想先扫描s字符串, 使用一个map记录字符串中各个字符的数量, 再遍历order依次将字符按数量附加到末尾即可. 但考虑到字符只有26个小写英文字母, 使用一个长度为26的数组来保存对应位置的英文字母的数量. 再遍历要比map速度快. ### 代码 ```go func customSortString(order string, s string) string { a := 'a' result := "" numbers := make([]int, 26) for _, character := range s{ numbers[character-a]++ } for _,c := range order{ temp := numbers[c-a] for i:=0;igoal{ if front-1 == back{ front++ } back++ }else{ if (front-1 == back){ result++ front++ back = flag }else if prefixsum[back] == prefixsum[back+1]{ result++ back++ }else if (front goal { sum -= nums[left] left++ } ans += right - left + 1 } return ans } ``` ## day18 2024-03-15 ### 238. Product of Array Except Self Given an integer array nums, return an array answer such that answer[i] is equal to the product of all the elements of nums except nums[i]. The product of any prefix or suffix of nums is guaranteed to fit in a 32-bit integer. You must write an algorithm that runs in O(n) time and without using the division operation. ![0315l01oAnZceg6j](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0315l01oAnZceg6j.png) ### 题解 题目中明确要求本题不能使用分治法, 且算法复杂度在O(n). 本题要求的为数组中每个元素对应的除该元素之外的其他元素的乘积. 根据给出的例子可以发现当存在0的时候是一种特殊情况. 当存在0时除0以外的其他元素对应的结果均为0. 一般情况下可以使用先求出全部元素的乘积, 再遍历数据使用总乘积除以当前元素即可求得对应位置的结果. 因为这样只需要固定遍历两次数据, 故时间复杂度为O(n), 又只需要一个变量来保存总乘积, 一个变量指示是否存在0元素, 故空间复杂度为O(1). 因为题目中明确说明了乘积保证为整数, 故在使用除法的过程中不用考虑结果为小数的问题. ### 代码 ```go func productExceptSelf(nums []int) []int { sum := 1 flag := 0 for _, value := range nums{ if value == 0{ flag++ }else{ sum *= value } } if flag > 1{ return make([]int, len(nums)) } if flag == 1{ result := []int{} for _, value := range nums{ if value == 0{ result = append(result, sum) }else{ result = append(result, 0) } } return result } result := []int{} if flag == 0{ for _, value := range nums{ result = append(result, sum/value) } } return result } ``` ### 总结 查看更快的解法, 发现都使用了前缀积和后缀积, 即从前向后遍历, 计算出当前位置元素的前缀积, 然后反向遍历, 在计算出后缀积的同时就得到了最终的结果. 一个示例代码如下 ```go func productExceptSelf(nums []int) []int { res := make([]int, len(nums)) prefix := 1 for i, n := range nums { res[i] = prefix prefix *= n } postfix := 1 for i := len(nums) - 1; i >= 0; i-- { res[i] *= postfix postfix *= nums[i] } return res } ``` 其实无论是前缀和还是前缀积, 都是一个对以往的计算状态的保留, 保存了更多的信息, 避免了重复的运算, 这种思想是值得细细品味的. ## day19 525. Contiguous Array Given a binary array nums, return the maximum length of a contiguous subarray with an equal number of 0 and 1. ![0316T5HJJzrn5slt](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0316T5HJJzrn5slt.png) ### 题解 考虑本题若想找到到某一个位置处的0和1数量相同的最长子数组长度, 只需要根据到该下标处的0和1数量的差值, 找出符合这个差值的最小下标, 用当前下标减去这个差值下标, 得到的即为0和1数量相同的子数组的长度. 关键在于想要让0和1数量相同, 需要找出能消除当前0和1数量差值的位置. 0和1数量对于寻找相同数量子数组用处不大, 二者数量的差值对于构造一个数量相同的子数组至关重要. 通过加上或减去二者的数量差即可构造出二者数量相同的子数组. 考虑清楚这一点, 思路就很清晰了. 1. 遍历数组, 保存到当前位置0和1的数量 2. 计算二者的差值, 在一个哈希表中寻找以这个差值为key的项是否存在, 不存在则将差值为key, 当前下标作为该key对应的值插入哈希表. 若存在则用当前下标减去哈希表中以差值作为key的对应的下标值, 即得到到当前位置0和1数量相同的最长子数组的长度. 比较这个长度和保存的最长子数组长度, 更新最长子数组长度. 关键在与将差值作为key下标作为value插入哈希表后, 后续有相同的差值作为key时不在更新哈希表, 这样保存的就是符合这个差值的位置的最小值, 也就能构造出最长的0和1数量相同的子数组. ### 代码 ```go func findMaxLength(nums []int) int { map0 := map[int]int{} map1 := map[int]int{} sum0 := 0 sum1 := 0 maxarray := 0 for index,value := range nums{ if value == 1{ sum1++ }else{ sum0++ } if sum1>sum0{ i, ok := map1[sum1-sum0] if !ok{ map1[sum1-sum0] = index }else{ maxarray = max(maxarray, index-i) } }else if sum1= value[0] && end <= value[1]{ insertInterval = append(insertInterval, value[1]) flag++ result = append(result, insertInterval) } }else if start >= value[0] && start <= value[1]{ insertInterval = append(insertInterval, value[0]) flag++ if end >= value[0] && end <= value[1]{ insertInterval = append(insertInterval, value[1]) flag++ result = append(result, insertInterval) } }else{ result = append(result, value) } }else{ if end < value[0]{ insertInterval = append(insertInterval, end) flag++ result = append(result, insertInterval) result = append(result, value) }else if end >= value[0] && end <= value[1]{ insertInterval = append(insertInterval, value[1]) flag++ result = append(result, insertInterval) } } } if flag == 0{ result = append(result, []int{start, end}) }else if flag == 1{ insertInterval = append(insertInterval, end) result = append(result, insertInterval) } return result } ``` ### 总结 这种题思路上并没有特别之处, 但是判断逻辑比较繁琐, 需要耐心思考边界情况, 查看他人题解, 发现用原始数组与需要插入的interval做比较要比使用interval和原始数组的start和end做比较思路简单清晰得多, 这里还是对判断条件的变与不变理解的不够透彻, 需要插入的interval的start和end是不变的, 不断遍历原始数组与不变的start和end做比较要比使用不变的start和end去和不断变化的interval的start和end做比较判断起来容易得多. 找到不变的条件对于思路清楚的解决问题至关重要. 给出示例代码 ```go func insert(intervals [][]int, newInterval []int) [][]int { res := make([][]int, 0) i := 0 for ; i < len(intervals) && intervals[i][1] < newInterval[0]; i++ { res = append(res, intervals[i]) } for ; i < len(intervals) && intervals[i][0] <= newInterval[1]; i++ { newInterval[0] = min(intervals[i][0], newInterval[0]) newInterval[1] = max(intervals[i][1], newInterval[1]) } res = append(res, newInterval) for i < len(intervals) { res = append(res, intervals[i]) i++ } return res } ``` ## day21 2024-03-18 ### 452. Minimum Number of Arrows to Burst Balloons There are some spherical balloons taped onto a flat wall that represents the XY-plane. The balloons are represented as a 2D integer array points where points[i] = [xstart, xend] denotes a balloon whose horizontal diameter stretches between xstart and xend. You do not know the exact y-coordinates of the balloons. Arrows can be shot up directly vertically (in the positive y-direction) from different points along the x-axis. A balloon with xstart and xend is burst by an arrow shot at x if xstart <= x <= xend. There is no limit to the number of arrows that can be shot. A shot arrow keeps traveling up infinitely, bursting any balloons in its path. Given the array points, return the minimum number of arrows that must be shot to burst all balloons. ![03188j7wiR06A45M](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/03188j7wiR06A45M.png) ### 题解 本题描述的很复杂, 其实是寻找相互交错的数组有几组, 如果将数组的范围看作集合, 范围重叠的数组可以看作一个大集合, 那么就是寻找这样的集合的数目. 本题可用贪心算法, 先将这些数组按照x_start的大小从小到大排序, 然后遍历并寻找x_start在当前重合范围内的数组,并且将重合范围设置为当前重合范围和当前遍历的数组的x_end中的较小值以缩小重合范围. 直到当前数组不满足条件, 即可认为之前的数组需要一个arrow. 继续遍历, 重复该操作, 即可找到所有需要的arrow. ### 代码 ```go func findMinArrowShots(points [][]int) int { sort.Slice(points, func (i, j int)bool{ if points[i][0]= points[i][0] {continue} res++ arrow = points[i][1] } return res } ``` ## day22 2024-03-19 ### 621. Task Scheduler You are given an array of CPU tasks, each represented by letters A to Z, and a cooling time, n. Each cycle or interval allows the completion of one task. Tasks can be completed in any order, but there's a constraint: identical tasks must be separated by at least n intervals due to cooling time. Return the minimum number of intervals required to complete all tasks. ![031901VKFQ0HNGWv](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/031901VKFQ0HNGWv.png) ### 题解 拿到本题, 最直接的想法就是将每种任务的数量统计出来, 从大到小排序, 然后按照冷却时间轮流按序执行不同的任务, 不能执行任务的时间片留空. 某种任务全部执行完后, 该任务占据的时间片也留空. 知道全部任务都执行完需要的时间就是最少时间. 这是一种贪心的思想, 简单思考其正确性, 因为即使调换顺序, 在执行到某个时间时也不会比这种贪心算法执行的任务更多. ### 代码 ```go func leastInterval(tasks []byte, n int) int { tasknumber := map[byte]int{} for _, task := range tasks{ _, exist := tasknumber[task] if !exist{ tasknumber[task] = 1 }else{ tasknumber[task]++ } } tasknumber_slice := []int{} for _, value := range tasknumber{ tasknumber_slice = append(tasknumber_slice, value) } sort.Ints(tasknumber_slice) length := 0 result := 0 for { length = len(tasknumber_slice) for i:=1;i<=n+1;i++{ if i<=length{ if(tasknumber_slice[length-i] == 1){ if i==1{ tasknumber_slice = tasknumber_slice[:length-1] }else{ tasknumber_slice = append(tasknumber_slice[:length-i],tasknumber_slice[length-i+1:]...) } }else{ tasknumber_slice[length-i]-- } } result++ if len(tasknumber_slice)==0{ goto Loop } } sort.Ints(tasknumber_slice) } Loop: return result } ``` ### 总结 看了0ms的解法, 十分惊艳, 与其将任务一个一个的安放到插槽中, 不如直接按照频率最大的任务算出必须存在的空插槽的个数, 再用剩余的任务去填这些空插槽, 最后只需要将任务总数和剩余的空插槽个数相加即可得到最终的时长. 到这里我想到, 其实频率最高的任务留出的空插槽数目是固定的, 只要将除频率最高之外的任务总数和空插槽数目相比, 若小于空插槽数目, 则最后时长就是频率最高任务完成需要的时长. 这里需要将和频率最高的任务频率相同的任务数目先减一计算算到任务总数中, 最后再加到最终的时间总数上. 若大于空插槽数目, 最终结果就是任务数目. ```go func leastInterval(tasks []byte, n int) int { freq := make([]int, 26) for _, task := range tasks { freq[task-'A']++ } sort.Ints(freq) maxFreq := freq[25] idleSlots := (maxFreq - 1) * n for i := 24; i >= 0 && freq[i] > 0; i-- { idleSlots -= min(maxFreq-1, freq[i]) } idleSlots = max(0, idleSlots) return len(tasks) + idleSlots } ``` ## day23 2024-03-20 ### 1669. Merge In Between Linked Lists You are given two linked lists: list1 and list2 of sizes n and m respectively. Remove list1's nodes from the ath node to the bth node, and put list2 in their place. The blue edges and nodes in the following figure indicate the result: ![0320kDnwlxpwmKhB](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0320kDnwlxpwmKhB.png) Build the result list and return its head. ![03200deJDZCQpgs3](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/03200deJDZCQpgs3.png) ### 题解 本题为将链表中某段替换为指定的链表, 只需要遍历链表, 保存需要替换部分首尾两个节点的指针即可, 需要注意边界情况的处理, 即替换开头某段或者结尾某段链表时要处理空指针. ### 代码 ```go /** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */ func mergeInBetween(list1 *ListNode, a int, b int, list2 *ListNode) *ListNode { count := 0 var before *ListNode var after *ListNode current := list1 head := list1 for count <= b { if count == a-1 { before = current } count++ current = current.Next } after = current if before == nil { head = list2 } else { before.Next = list2 } current = list2 for current.Next != nil { current = current.Next } current.Next = after return head } ``` ### 总结 本题保存了首尾两个指针的地址, 是典型的用空间换时间的思路, 不过实际应用过程中可能还要根据语言注意被动态分配出去的空间回收的问题. ## day24 2024-03-21 ### 206. Reverse Linked Given the head of a singly linked list, reverse the list, and return the reversed list. ![0321QsvliuLwsiPu](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0321QsvliuLwsiPu.png) ### 题解 本题相当基础, 是一道简单题, 只需要将链表整个反转过来即可. 用一个变量保存当前访问的节点的前一个节点的指针, 将当前节点的next修改为指向前一个节点的指针, 遍历整个链表即可. 注意处理边界情况(头指针为0). ### 代码 ```go /** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */ func reverseList(head *ListNode) *ListNode { var before *ListNode current := head for current!=nil && current.Next!=nil { temp := current.Next current.Next = before before = current current = temp } if head!=nil{ current.Next = before } return current } ``` ### 总结 这两天都是链表相关的题目, 本题是一道基础题, 在查看他人解答的过程中发现本题也可以将链表数据全部复制出来, 再遍历链表逆序赋值即可. 示例代码如下 ```go /** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */ func reverseList(head *ListNode) *ListNode { arr := []int{} node := head for node != nil { arr = append(arr, node.Val) node = node.Next } node = head for i := len(arr); i > 0; i-- { node.Val = arr[i-1] node = node.Next } return head } ``` ## day25 2024-03-22 ### 234. Palindrome Linked Given the head of a singly linked list, return true if it is a palindrome or false otherwise. ![03220NcGLgVTEmnC](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/03220NcGLgVTEmnC.png) ### 题解 本题也是一道基础题, 使用快慢指针的方法遍历到链表的中间, 在遍历的同时将链表的前半部分的值保存到数组中, 再从中间继续向后遍历, 遍历的同时反向遍历数组, 比较遍历的节点和遍历到的数组中元素的值. 若不同则不是回文, 直到全部遍历完成为止. ### 代码 ```go /** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */ func isPalindrome(head *ListNode) bool { back := head before:= head if head.Next != nil{ before = head.Next }else{ return true } values := []int{head.Val} for before!= nil && before.Next != nil{ back = back.Next values = append(values, back.Val) before = before.Next.Next } if before == nil{ for i:=len(values)-2;i>=0;i--{ back = back.Next if back.Val != values[i]{ return false } } return true }else{ for i:= len(values)-1;i>=0;i--{ back =back.Next if back.Val != values[i]{ return false } } return true } } ``` ### 总结 查看用时较短的题解, 使用了快慢指针, 找到中间位置后将后半截链表反转, 然后从原始链表头部和反转的后半截链表头部开始依次遍历并比较即可. 这种方法时间复杂度为O(n), 空间复杂度为O(1), 代码如下 ```go /** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */ func isPalindrome(head *ListNode) bool { slow:=head fast:=head.Next for fast!=nil && fast.Next!=nil{ slow=slow.Next fast=fast.Next.Next } second:=slow.Next slow.Next=nil second=reverse(second) for second!=nil && head!=nil{ if second.Val!=head.Val{ return false } second=second.Next head=head.Next } return true } func reverse(head *ListNode) *ListNode{ var prev *ListNode var futr *ListNode for head!=nil{ futr=head.Next head.Next=prev prev=head head=futr } return prev } ``` ## day26 2024-03-23 ### 143. Reorder You are given the head of a singly linked-list. The list can be represented as: > L0 → L1 → … → Ln - 1 → Ln Reorder the list to be on the following form: > L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … You may not modify the values in the list's nodes. Only nodes themselves may be changed. ![0323xGTn0liJdHDU](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0323xGTn0liJdHDU.png) ### 题解 拿到本题, 首先想到的是将链表中的全部数据保存到数组中, 然后通过同时从前后遍历数组并且给原来的链表赋值的方式, 即可快速解决本题, 如果不使用额外的空间, 可以使用快慢指针, 找到链表的中间节点, 从中间节点开始将链表的后半部分反向, 用两个指针分别从前半部分的开始和后半部分的末尾开始遍历, 并构造新链表即可. 也可以构造一个栈, 将链表的后半部分放入栈中, 然后从顶端一边出栈一边构造新链表即可. 题目中要求不能直接修改节点的值, 因此采用第三种思路. ### 代码 ```go /** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */ func reorderList(head *ListNode) { slow := head fast := head if head.Next != nil{ fast = head.Next } stack := []*ListNode{} for fast != nil && fast.Next != nil{ fast = fast.Next.Next slow = slow.Next } odd := false if fast == nil{ odd = true } // construct pointer stack slow = slow.Next for slow != nil{ stack = append(stack, slow) slow = slow.Next } slow = head temp := head.Next temp2 := head flag := 1 for i:=len(stack)-1;i>=0;i--{ if flag == 1{ stack[i].Next = nil slow.Next = stack[i] slow = slow.Next flag = 0 }else{ temp2 = temp.Next temp.Next = nil slow.Next = temp slow = slow.Next flag = 1 i++ temp = temp2 } } if odd{ temp.Next = nil slow.Next = temp } return } ``` ### 总结 查看最快速度代码, 使用了一个数组先将链表的全部节点指针留一个间隔存放在数组中, 然后再逆序遍历数组将后半节点的指针逆序插入前面留出的空格中, 最后从头遍历数组, 连接整个链表即可. 这样写得出的代码比较简洁. ```go /** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */ func reorderList(head *ListNode) { list := []*ListNode{} for node := head; node != nil; node = node.Next { list = append(list, node) list = append(list, nil) } for i := 1; i < len(list) - i - 1; i += 2 { list[i] = list[len(list) - i - 1] } for i := 0; list[i] != nil; i++ { list[i].Next = list[i + 1] } } ``` ## day27 2024-03-24 ### 287. Find the Duplicate Number Given an array of integers nums containing n + 1 integers where each integer is in the range [1, n] inclusive. There is only one repeated number in nums, return this repeated number. You must solve the problem without modifying the array nums and uses only constant extra space. ![0324Ckibjiatyqu7](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0324Ckibjiatyqu7.png) ### 题解 根据鸽笼原理, 显然必有一个数字有一个重复数字. 最简单的思路就是用每个数和数组其余数字比较, 直到找到相同的数为止, 显然这个方法的时间复杂度比较高, 要O(n^2). 最终并没有想出题目中要求的时间复杂度为O(n), 空间复杂度为O(1)的解法, 看题解得知使用的是Floyd 循环检测算法, 这个算法一般用于检测链表中是否存在环, 即使用快慢指针, 同时遍历链表, 如果存在环, 快指针最终会追上慢指针. 如果链表没有环, 则遍历链表最终会到达空指针自然停止, 这里的快慢指针是给了未知长度的链表一个停止条件, 可以避免若链表有环无限循环遍历下去. 这里将数组堪称链表是非常精妙的思路, 将数组中的值看作下一个节点在数组中的下标, 这样如果有两个节点相同, 则最终快指针和慢指针指向的下标会相同, 再从头遍历一次数组, 找到值为这个下标的数据, 即为重复的数. ### 代码 ```go func findDuplicate(nums []int) int { // Step 1: Find the intersection point of the two pointers slow := nums[0] fast := nums[0] for { slow = nums[slow] fast = nums[nums[fast]] if slow == fast { break } } // Step 2: Find the entrance to the cycle (duplicate number) slow = nums[0] for slow != fast { slow = nums[slow] fast = nums[fast] } return slow } ``` ## day28 2024-03-25 ### 442. Find All Duplicates in an Array Given an integer array nums of length n where all the integers of nums are in the range [1, n] and each integer appears once or twice, return an array of all the integers that appears twice. You must write an algorithm that runs in O(n) time and uses only constant extra space. ![03250qkp0rMB0Ms8](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/03250qkp0rMB0Ms8.png) ### 题解 本题若想在O(n)时间复杂度内求解, 必须在遍历数组的时候充分利用遍历过的数据提供的信息, 在后续充分利用已有信息, 在本题中, 遍历过程中的每个位置处的数本身就是信息. 若使用O(n)的空间复杂度, 可以创建一个和目标数组长度相同的空数组, 将遍历到的元素的数作为下标, 设定对应空数组中下标所在位置的值作为标记. 这样后续遍历时看到标记则知道这个数是重复的. 若想空间复杂度为O(1), 因为本题并没有要求不修改数组, 可以修改数组中以这个数为下标处的数据, 这里可以将其取相反数来表明这个下标已经出现过. 这也是因为体重明确说明数组中的数的范围在1-数组长度之间, 因此可以采用这种方法来标记数据是否已经出现, 如果有数大小超过数组长度, 这种方案就不适用了, 则必须使用O(n)的空间. ### 代码 ```go func findDuplicates(nums []int) []int { result := []int{} for _, values := range nums{ if nums[int(math.Abs(float64(values)))-1] < 0{ result = append(result, int(math.Abs(float64(values)))) } nums[int(math.Abs(float64(values)))-1] = -nums[int(math.Abs(float64(values)))-1] } return result } ``` ## day28 2024-03-26 ### 41. First Missing Positive Given an unsorted integer array nums. Return the smallest positive integer that is not present in nums. You must implement an algorithm that runs in O(n) time and uses O(1) auxiliary space. ![0326ratU6vVn9qLn](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0326ratU6vVn9qLn.png) ### 题解 本题要求O(n)的时间复杂度和O(1)的空间复杂度, 解题思路上与昨天的题目相似, 考虑到数组长度为n, 即使是从1开始的连续正整数, 最大填充到n. 故用数组中的数作为下标修改对应位置的值来标记某个数是否存在是可行的. 但要注意, 当前数组中可能出负数和超过n的数字, 如果想使用将数作为下标将对应位置数设为负数的方式来提供信息, 则要先对负数和超过n的数字进行处理, 为了区分被标记的数和本身为负的数, 将负数和超过n的数修改为0. 再遍历数组按照昨天的思路将数作为下标, 将相应位置数设为负数标记已经访问. 这里可以将数全部设为-1. 最后再遍历一遍数组, 找到第一个数字大于等于0的数的下标, 即为丢失的最小正整数. 这样需要固定遍历三遍数组, 且空间复杂度为O(1). ### 代码 ```go func firstMissingPositive(nums []int) int { for index, value := range nums { if value < 0 || value > len(nums) { nums[index] = 0 } } flag := 0 for _, value := range nums { if abs(value) > 1 || value == -1{ if nums[abs(value)-1] == 1 { flag = 1 nums[abs(value)-1] = -1 } else if nums[abs(value)-1] == 0 { nums[abs(value)-1] = -1 } else if nums[abs(value)-1] > 1 { nums[abs(value)-1] = -nums[abs(value)-1] } }else if value == 1{ flag = 1 if nums[0] == 0{ nums[0] = -1 }else if nums[0] > 1{ nums[0] = -nums[0] } } } for index, value := range nums { if flag == 1 && index == 0 { continue } else if index == 0 && flag == 0 { return 1 } if value >= 0 { return index + 1 } } return len(nums)+1 } func abs(num int) int { if num < 0 { return -num } else { return num } } ``` ### 总结 查看最快速代码, 其将负数直接修改为整数类型最大值, 对于超过数组长度的数直接忽略, 不作处理, 其余的当作下标取对应位置的相反数. 这样处理起来思路比较清晰. ## day29 2024-03-27 ### 713. Subarray Product Less Than K Given an array of integers nums and an integer k, return the number of contiguous subarrays where the product of all the elements in the subarray is strictly less than k. ![0327Kf6RehBJGKTs](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0327Kf6RehBJGKTs.png) ### 题解 本题要求返回的是连续的相邻子数组, 第一个想到的就是滑动窗口, 设置窗口的前后指针, 当窗口内的积= k { p /= nums[j] j++ } res += i -j + 1 } return res } ``` 想到这, 忽然明白, 其实核心在于只需要考虑以某个位置为结尾的向前连续符合要求的数组长度作为该位置处应该增加的计数数目即可. 这样就把整个问题拆分成了独立不关联的小问题. 将每个位置应该增加的计数数目累积, 就是最终的结果. ## day30 2024-03-28 ### 2958. Length of Longest Subarray With at Most K Frequency You are given an integer array nums and an integer k. The frequency of an element x is the number of times it occurs in an array. An array is called good if the frequency of each element in this array is less than or equal to k. Return the length of the longest good subarray of nums. A subarray is a contiguous non-empty sequence of elements within an array. ![0328cwzoUxeYLcAx](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0328cwzoUxeYLcAx.png) ### 题解 拿到本题, 直接思路就是使用滑动窗口, 使用一个哈希表来存储每个数字对应的个数, 在滑动的过程中, 扩大窗口时增加窗口新增数字的计数并不断更新最大长度, 直到当前数字的计数达到上限k开始缩小窗口, 缩小至当前数字计数小于k(缩小过程中的数都在哈希表中对应减掉频率)即可继续扩大窗口. 如此直到遍历完整个数组. 其实从解题思路上与昨天的题内核是十分相似的. ### 代码 ```go func maxSubarrayLength(nums []int, k int) int { count := map[int]int{} end := 0 maxlength := 0 for front:=0;front < len(nums);front++{ _,exist := count[nums[front]] if !exist{ count[nums[front]] = 1 }else{ count[nums[front]]++ } if count[nums[front]] <= k{ maxlength = max(maxlength,front-end+1) }else{ for nums[end] != nums[front]{ count[nums[end]]-- end++ } // 将达到上限的数本身减掉 count[nums[end]]-- end++ } } return maxlength } ``` ### 总结 查看前面10%更快的代码, 发现判断缩小窗口的结束可以用当前窗口前端达到上限的数在哈希表中对应的计数值来判断, 缩小窗口直到达到上限的数的计数值小于k即可结束, 这样整体代码更清晰, 更简洁, 如下 ```go func maxSubarrayLength(nums []int, k int) int { m := make(map[int]int) res := 0 for l, r := 0, 0; r < len(nums); r++ { m[nums[r]]++ for m[nums[r]] > k { m[nums[l]]-- l++ } if r-l+1 > res { res = r-l+1 } } return res } ``` ## day31 2024-03-29 ### 2962. Count Subarrays Where Max Element Appears at Least K Times You are given an integer array nums and a positive integer k. Return the number of subarrays where the maximum element of nums appears at least k times in that subarray. A subarray is a contiguous sequence of elements within an array. ![0329pKR0itEdYZ5b](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0329pKR0itEdYZ5b.png) ### 题解 本题感觉就是昨天那道题的姊妹+升级版, 现在要求找到数组中包含的最大值, 其在这个子数组中的出现次数至少为k次. 这里明确题目中需要解决的两个问题. 1. 找到数组中的最大值 2. 对这个值在子序列中出现的次数进行计数. 无疑, 仍然可以使用滑动窗口来解决, 思路和昨天的题类似, 这里在扩大窗口时, 一直扩大到最大值数目为k, 继续向后遍历时要每次增加从头开始到包含k个最大值的窗口的最左端的元素个数. 核心思路在于让滑动窗口中只保留k个最大值, 这样所有包含前面数据的子数组和包含后面不为最大值的子数组的所有子数组都符合条件. ### 代码 ```go func countSubarrays(nums []int, k int) int64 { max := slices.Max(nums) var result int64 left := 0 nextleft := 0 beforeadded := 0 frequency := 0 for _, value := range nums{ if value == max{ frequency++ if frequency >= k{ for nums[nextleft] != max{ nextleft++ } beforeadded += nextleft - left + 1 result += int64(beforeadded) left = nextleft+1 nextleft = left } }else if frequency >= k{ result += int64(beforeadded) } } return result } ``` ### 总结 查看他人的题解发现, 可以在保持窗口内最大值个数为k的思路下优化解题过程, 重复的加前面的元素是不必要的, 先将所有最大值的下标放入数组, 然后确定包含k个最大值的窗口的两端的下标, 将该窗口左侧前面的元素个数和窗口右侧后面的元素个数相乘即为该窗口对应的符合条件的解的个数, 切换到下一个包含k个最大值的窗口, 继续此操作, 直到窗口中最大值数目不足k为止. ```go func countSubarrays(nums []int, k int) int64 { var m int for _, n := range nums { m = max(m, n) } var idxs []int for i := range nums { if nums[i] == m { idxs = append(idxs, i) } } start := 0 count := int64(0) for i := range idxs { if i+k > len(idxs) { break } last := len(nums) if i+k < len(idxs) { last = idxs[i+k] } count += int64(idxs[i]-start+1) * int64(last-idxs[i+k-1]) } return count } ``` ## day32 2024-03-30 ### 992. Subarrays with K Different Integers Given an integer array nums and an integer k, return the number of good subarrays of nums. A good array is an array where the number of different integers in that array is exactly k. For example, [1,2,3,1,2] has 3 different integers: 1, 2, and 3. A subarray is a contiguous part of an array. ![0330GvIAMJljJeoJ](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0330GvIAMJljJeoJ.png) ### 题解 本题仍然使用滑动窗口, 这几天连续做滑动窗口的题, 总结了滑动窗口的"三要素": 1. 什么时候扩大窗口 2. 双么时候缩小窗口 3. 缩小和扩大窗口时执行哪些操作 对于本题, 题目中要求计数正好包含k个不同数的子数组的个数, 求精确包含k个这种问题往往比较困难, 可以转化为求解包含≤k个和≤k-1个不同数的子数组个数的差. 这种思路求解起来非常方便. 对于求解包含≤k个不同数的子数组的个数, 当数组中包含不同数个数不足k时, 扩大窗口同时将计数增加当前窗口的长度, 若为k+1, 则缩小窗口至正好完全去除了一个数(若某个数只出现了1次, 去掉后窗口内不同数个数就是k, 若不止一次, 则去掉了不同数个数也没有变化, 故要继续向下遍历). 最后求≤k和≤k-1情况的计数值做差即可. ### 代码 ```go func subarraysWithKDistinct(nums []int, k int) int { return countk(nums, k) - countk(nums, k-1) } func countk(nums []int, k int) int { count := map[int]int{} different := 0 left := 0 result := 0 for index, value := range nums { _, exist := count[value] if !exist { different++ count[value] = 1 } else { count[value]++ } if different <= k { result += index - left + 1 } if different == k+1 { for count[nums[left]] > 1 { count[nums[left]]-- left++ } delete(count, nums[left]) left++ different-- result += index - left + 1 } } return result } ``` ### 总结 解题时没有注意题目限制, 后来查看最快解法发现忽略了题目中的数的范围, 题目中的数组中的数的大小不超过数组的长度, 数的范围已知, 因此可以使用数组代替哈希表来计数这样可以大大加快解题速度. ```go func subarraysWithKDistinct(nums []int, k int) (ans int) { f := func(k int) []int { n := len(nums) pos := make([]int, n) cnt := make([]int, n+1) s, j := 0, 0 for i, x := range nums { cnt[x]++ if cnt[x] == 1 { s++ } for ; s > k; j++ { cnt[nums[j]]-- if cnt[nums[j]] == 0 { s-- } } pos[i] = j } return pos } left, right := f(k), f(k-1) for i := range left { ans += right[i] - left[i] } return } ``` ## day33 2024-03-31 ### 2444. Count Subarrays With Fixed Bounds You are given an integer array nums and two integers minK and maxK. A fixed-bound subarray of nums is a subarray that satisfies the following conditions: The minimum value in the subarray is equal to minK. The maximum value in the subarray is equal to maxK. Return the number of fixed-bound subarrays. A subarray is a contiguous part of an array. ![0331tqDltORpk8jM](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/0331tqDltORpk8jM.png) ### 题解 本题先求出数组中包含上下界的所有可行子区域, 可行子区域指所有连续的符合上下界要求的最长区域. 将这些区域的头尾下标保存到一个二维数组中. 对于每个可行区域, 使用该区域包含的全部子数组的个数减去不满足上下界要求的子数组个数, 不满足上下界要求的子数组个数求解使用滑动窗口是比较容易的, 从头开始滑动, 窗口内只能包含上界或者下届或者都不包含. 若直接求解该区域中包含的满足上下界的子数组个数则比较困难. 用子数组总个数和不满足上下界要求的子数组个数做差即可得到想求的满足上下界的子数组个数. 对于每个子区域都执行这样的操作, 并将求得的满足条件的子数组个数加和即可得到最终的结果, 注意处理上下界都是同一个数的边界情况. ### 代码 ```go func countSubarrays(nums []int, minK int, maxK int) int64 { begin := 0 borders := [][]int{} mincurrent := -1 maxcurrent := -1 var result int64 // 找到所有可行区间 for index, value := range nums{ border := []int{} if value == minK { mincurrent = 1 } if value == maxK { maxcurrent = 1 } if value > maxK || value < minK || index == len(nums)-1{ if maxcurrent == -1 || mincurrent == -1{ mincurrent = -1 maxcurrent = -1 begin = index + 1 }else{ if value <= maxK && value >= minK && index == len(nums) - 1 { index += 1 } border = append(border, begin) border = append(border, index) borders = append(borders, border) border = border[:0] mincurrent = -1 maxcurrent = -1 begin = index + 1 } } } // 求每个可行区间中解的数目并加和 求解可行区间已经保证区间内必有上下限存在 for _, region := range borders{ left := region[0] right := region[1] ismin := false ismax := false last := 0 allarray := (right - left + 1) * (right - left) / 2 left = 0 outrange := 0 for index, value := range nums[region[0]:right]{ if value != minK && value != maxK{ outrange += index - left + 1 }else if value == minK{ if value == maxK{ left = index + 1 continue } if ismax{ left = last + 1 ismax = false ismin = true }else if !ismin{ ismin = true } last = index outrange += index - left + 1 }else{ if ismin{ left = last + 1 ismin = false ismax = true }else if !ismax{ ismax = true } last = index outrange += index - left + 1 } } result += int64(allarray - outrange) } return result } ``` ### 总结 本次解题代码的运行速度超过了100%的提交, 因此不再看他人的题解了, 同时庆祝一下自己拿到了3月份的奖章, 证明这个月每天都在坚持. 下个月希望能继续坚持下去. ## day34 2024-04-01 ### 58. Length of Last Word Given a string s consisting of words and spaces, return the length of the last word in the string. A word is a maximal substring consisting of non-space characters only. ![04010SzI491D9VfR](https://testingcf.jsdelivr.net/gh/game-loader/picbase@master/uPic/04010SzI491D9VfR.png) ### 题解 本题是一道简单题, 直接从头开始遍历, 遇到空格结束当前单词计数, 再遇到新字符时重新计数, 直到遍历完整个字符串即可. ### 代码 ```go func lengthOfLastWord(s string) int { length := 0 flag := false for _, value := range s{ if value == ' ' { flag = true } if value != ' ' { if flag{ length = 1 flag = false }else{ length++ } } } return length } ``` ### 总结 前面的题解大多用了go strings库中的函数来按空格分割字符串, 再返回最后一个分割出来的字符串长度. 一般实际应用中, 使用官方库是第一选择, 因为官方库大多对这些操作做了大量优化, 效率会高得多.