diff --git "a/LeetCode/all/1185.\344\270\200\345\221\250\344\270\255\347\232\204\347\254\254\345\207\240\345\244\251.go" "b/LeetCode/all/1185.\344\270\200\345\221\250\344\270\255\347\232\204\347\254\254\345\207\240\345\244\251.go" new file mode 100644 index 0000000..18d7d41 --- /dev/null +++ "b/LeetCode/all/1185.\344\270\200\345\221\250\344\270\255\347\232\204\347\254\254\345\207\240\345\244\251.go" @@ -0,0 +1,59 @@ +/* + * @lc app=leetcode.cn id=1185 lang=golang + * + * [1185] 一周中的第几天 + * + * https://leetcode-cn.com/problems/day-of-the-week/description/ + * + * algorithms + * Easy (60.12%) + * Likes: 42 + * Dislikes: 0 + * Total Accepted: 12.6K + * Total Submissions: 20.8K + * Testcase Example: '31\n8\n2019' + * + * 给你一个日期,请你设计一个算法来判断它是对应一周中的哪一天。 + * + * 输入为三个整数:day、month 和 year,分别表示日、月、年。 + * + * 您返回的结果必须是这几个值中的一个 {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", + * "Friday", "Saturday"}。 + * + * + * + * 示例 1: + * + * 输入:day = 31, month = 8, year = 2019 + * 输出:"Saturday" + * + * + * 示例 2: + * + * 输入:day = 18, month = 7, year = 1999 + * 输出:"Sunday" + * + * + * 示例 3: + * + * 输入:day = 15, month = 8, year = 1993 + * 输出:"Sunday" + * + * + * + * + * 提示: + * + * + * 给出的日期一定是在 1971 到 2100 年之间的有效日期。 + * + * + */ + +// @lc code=start +func dayOfTheWeek(day int, month int, year int) string { + t, _ := time.Parse("2006-01-02", fmt.Sprintf("%4d-%02d-%02d", year, month, day)) + return t.Weekday().String() +} +// @lc code=end + diff --git "a/LeetCode/all/1576.\346\233\277\346\215\242\346\211\200\346\234\211\347\232\204\351\227\256\345\217\267.go" "b/LeetCode/all/1576.\346\233\277\346\215\242\346\211\200\346\234\211\347\232\204\351\227\256\345\217\267.go" new file mode 100644 index 0000000..a2f516a --- /dev/null +++ "b/LeetCode/all/1576.\346\233\277\346\215\242\346\211\200\346\234\211\347\232\204\351\227\256\345\217\267.go" @@ -0,0 +1,84 @@ +/* + * @lc app=leetcode.cn id=1576 lang=golang + * + * [1576] 替换所有的问号 + * + * https://leetcode-cn.com/problems/replace-all-s-to-avoid-consecutive-repeating-characters/description/ + * + * algorithms + * Easy (47.67%) + * Likes: 95 + * Dislikes: 0 + * Total Accepted: 42.8K + * Total Submissions: 83.4K + * Testcase Example: '"?zs"' + * + * 给你一个仅包含小写英文字母和 '?' 字符的字符串 s,请你将所有的 '?' 转换为若干小写字母,使最终的字符串不包含任何 连续重复 的字符。 + * + * 注意:你 不能 修改非 '?' 字符。 + * + * 题目测试用例保证 除 '?' 字符 之外,不存在连续重复的字符。 + * + * 在完成所有转换(可能无需转换)后返回最终的字符串。如果有多个解决方案,请返回其中任何一个。可以证明,在给定的约束条件下,答案总是存在的。 + * + * + * + * 示例 1: + * + * 输入:s = "?zs" + * 输出:"azs" + * 解释:该示例共有 25 种解决方案,从 "azs" 到 "yzs" 都是符合题目要求的。只有 "z" 是无效的修改,因为字符串 "zzs" + * 中有连续重复的两个 'z' 。 + * + * 示例 2: + * + * 输入:s = "ubv?w" + * 输出:"ubvaw" + * 解释:该示例共有 24 种解决方案,只有替换成 "v" 和 "w" 不符合题目要求。因为 "ubvvw" 和 "ubvww" 都包含连续重复的字符。 + * + * + * 示例 3: + * + * 输入:s = "j?qg??b" + * 输出:"jaqgacb" + * + * + * 示例 4: + * + * 输入:s = "??yw?ipkj?" + * 输出:"acywaipkja" + * + * + * + * + * 提示: + * + * + * + * 1 <= s.length <= 100 + * + * + * s 仅包含小写英文字母和 '?' 字符 + * + * + * + */ + +// @lc code=start +func modifyString(s string) string { + res := []byte(s) + for k,v := range s{ + if v != '?'{ + continue + } + for b:=byte('a');b<='c';b++{ + if !(k-1>=0 && res[k-1] == b || k+1b{ + return a + } + return b +} +// @lc code=end + diff --git "a/LeetCode/all/2022.\345\260\206\344\270\200\347\273\264\346\225\260\347\273\204\350\275\254\345\217\230\346\210\220\344\272\214\347\273\264\346\225\260\347\273\204.go" "b/LeetCode/all/2022.\345\260\206\344\270\200\347\273\264\346\225\260\347\273\204\350\275\254\345\217\230\346\210\220\344\272\214\347\273\264\346\225\260\347\273\204.go" new file mode 100644 index 0000000..878de3c --- /dev/null +++ "b/LeetCode/all/2022.\345\260\206\344\270\200\347\273\264\346\225\260\347\273\204\350\275\254\345\217\230\346\210\220\344\272\214\347\273\264\346\225\260\347\273\204.go" @@ -0,0 +1,92 @@ +/* + * @lc app=leetcode.cn id=2022 lang=golang + * + * [2022] 将一维数组转变成二维数组 + * + * https://leetcode-cn.com/problems/convert-1d-array-into-2d-array/description/ + * + * algorithms + * Easy (65.08%) + * Likes: 8 + * Dislikes: 0 + * Total Accepted: 5.1K + * Total Submissions: 7.8K + * Testcase Example: '[1,2,3,4]\n2\n2' + * + * 给你一个下标从 0 开始的一维整数数组 original 和两个整数 m 和  n 。你需要使用 original 中 所有 元素创建一个 m 行 n + * 列的二维数组。 + * + * original 中下标从 0 到 n - 1 (都 包含 )的元素构成二维数组的第一行,下标从 n 到 2 * n - 1 (都 包含 + * )的元素构成二维数组的第二行,依此类推。 + * + * 请你根据上述过程返回一个 m x n 的二维数组。如果无法构成这样的二维数组,请你返回一个空的二维数组。 + * + * + * + * 示例 1: + * + * 输入:original = [1,2,3,4], m = 2, n = 2 + * 输出:[[1,2],[3,4]] + * 解释: + * 构造出的二维数组应该包含 2 行 2 列。 + * original 中第一个 n=2 的部分为 [1,2] ,构成二维数组的第一行。 + * original 中第二个 n=2 的部分为 [3,4] ,构成二维数组的第二行。 + * + * + * 示例 2: + * + * 输入:original = [1,2,3], m = 1, n = 3 + * 输出:[[1,2,3]] + * 解释: + * 构造出的二维数组应该包含 1 行 3 列。 + * 将 original 中所有三个元素放入第一行中,构成要求的二维数组。 + * + * + * 示例 3: + * + * 输入:original = [1,2], m = 1, n = 1 + * 输出:[] + * 解释: + * original 中有 2 个元素。 + * 无法将 2 个元素放入到一个 1x1 的二维数组中,所以返回一个空的二维数组。 + * + * + * 示例 4: + * + * 输入:original = [3], m = 1, n = 2 + * 输出:[] + * 解释: + * original 中只有 1 个元素。 + * 无法将 1 个元素放满一个 1x2 的二维数组,所以返回一个空的二维数组。 + * + * + * + * + * 提示: + * + * + * 1 <= original.length <= 5 * 10^4 + * 1 <= original[i] <= 10^5 + * 1 <= m, n <= 4 * 10^4 + * + * + */ + +// @lc code=start +func construct2DArray(original []int, m int, n int) [][]int { + aLen := len(original) + res := [][]int{} + if aLen != m*n{ + return res + } + for i:=0;i>1) + min := mid + for _,v := range nums{ + if v <= min{ + min = v + }else if v <= mid{ + mid = v + }else if v > mid{ + return true + } + } + return false +} +// @lc code=end + diff --git "a/LeetCode/all/390.\346\266\210\351\231\244\346\270\270\346\210\217.go" "b/LeetCode/all/390.\346\266\210\351\231\244\346\270\270\346\210\217.go" new file mode 100644 index 0000000..8242526 --- /dev/null +++ "b/LeetCode/all/390.\346\266\210\351\231\244\346\270\270\346\210\217.go" @@ -0,0 +1,70 @@ +/* + * @lc app=leetcode.cn id=390 lang=golang + * + * [390] 消除游戏 + * + * https://leetcode-cn.com/problems/elimination-game/description/ + * + * algorithms + * Medium (46.87%) + * Likes: 242 + * Dislikes: 0 + * Total Accepted: 25.8K + * Total Submissions: 42.8K + * Testcase Example: '9' + * + * 列表 arr 由在范围 [1, n] 中的所有整数组成,并按严格递增排序。请你对 arr 应用下述算法: + * + * + * + * + * 从左到右,删除第一个数字,然后每隔一个数字删除一个,直到到达列表末尾。 + * 重复上面的步骤,但这次是从右到左。也就是,删除最右侧的数字,然后剩下的数字每隔一个删除一个。 + * 不断重复这两步,从左到右和从右到左交替进行,直到只剩下一个数字。 + * + * + * 给你整数 n ,返回 arr 最后剩下的数字。 + * + * + * + * 示例 1: + * + * + * 输入:n = 9 + * 输出:6 + * 解释: + * arr = [1, 2, 3, 4, 5, 6, 7, 8, 9] + * arr = [2, 4, 6, 8] + * arr = [2, 6] + * arr = [6] + * + * + * 示例 2: + * + * + * 输入:n = 1 + * 输出:1 + * + * + * + * + * 提示: + * + * + * 1 <= n <= 10^9 + * + * + * + * + */ + +// @lc code=start +func lastRemaining(n int) int { + l,r,interval := 1,n,1 + for lcount{ + minCount = count + } + } + return minCount +} \ No newline at end of file diff --git "a/LeetCode/all/71.\347\256\200\345\214\226\350\267\257\345\276\204.go" "b/LeetCode/all/71.\347\256\200\345\214\226\350\267\257\345\276\204.go" new file mode 100644 index 0000000..7d42868 --- /dev/null +++ "b/LeetCode/all/71.\347\256\200\345\214\226\350\267\257\345\276\204.go" @@ -0,0 +1,95 @@ +/* + * @lc app=leetcode.cn id=71 lang=golang + * + * [71] 简化路径 + * + * https://leetcode-cn.com/problems/simplify-path/description/ + * + * algorithms + * Medium (42.22%) + * Likes: 425 + * Dislikes: 0 + * Total Accepted: 125.5K + * Total Submissions: 284.8K + * Testcase Example: '"/home/"' + * + * 给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 '/' 开头),请你将其转化为更加简洁的规范路径。 + * + * 在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (..) + * 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠(即,'//')都被视为单个斜杠 '/' 。 + * 对于此问题,任何其他格式的点(例如,'...')均被视为文件/目录名称。 + * + * 请注意,返回的 规范路径 必须遵循下述格式: + * + * + * 始终以斜杠 '/' 开头。 + * 两个目录名之间必须只有一个斜杠 '/' 。 + * 最后一个目录名(如果存在)不能 以 '/' 结尾。 + * 此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含 '.' 或 '..')。 + * + * + * 返回简化后得到的 规范路径 。 + * + * + * + * 示例 1: + * + * + * 输入:path = "/home/" + * 输出:"/home" + * 解释:注意,最后一个目录名后面没有斜杠。 + * + * 示例 2: + * + * + * 输入:path = "/../" + * 输出:"/" + * 解释:从根目录向上一级是不可行的,因为根目录是你可以到达的最高级。 + * + * + * 示例 3: + * + * + * 输入:path = "/home//foo/" + * 输出:"/home/foo" + * 解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。 + * + * + * 示例 4: + * + * + * 输入:path = "/a/./b/../../c/" + * 输出:"/c" + * + * + * + * + * 提示: + * + * + * 1 + * path 由英文字母,数字,'.','/' 或 '_' 组成。 + * path 是一个有效的 Unix 风格绝对路径。 + * + * + */ + +// @lc code=start +func simplifyPath(path string) string { + var stack []string + for _,v := range strings.Split(path,"/"){ + if v == "" || v == "."{ + continue + } + if v == ".."{ + if len(stack) > 0{ + stack = stack[:len(stack)-1] + } + }else{ + stack = append(stack,v) + } + } + return "/" + strings.Join(stack,"/") +} +// @lc code=end + diff --git "a/LeetCode/all/747.\350\207\263\345\260\221\346\230\257\345\205\266\344\273\226\346\225\260\345\255\227\344\270\244\345\200\215\347\232\204\346\234\200\345\244\247\346\225\260.go" "b/LeetCode/all/747.\350\207\263\345\260\221\346\230\257\345\205\266\344\273\226\346\225\260\345\255\227\344\270\244\345\200\215\347\232\204\346\234\200\345\244\247\346\225\260.go" new file mode 100644 index 0000000..1c8ca80 --- /dev/null +++ "b/LeetCode/all/747.\350\207\263\345\260\221\346\230\257\345\205\266\344\273\226\346\225\260\345\255\227\344\270\244\345\200\215\347\232\204\346\234\200\345\244\247\346\225\260.go" @@ -0,0 +1,76 @@ +/* + * @lc app=leetcode.cn id=747 lang=golang + * + * [747] 至少是其他数字两倍的最大数 + * + * https://leetcode-cn.com/problems/largest-number-at-least-twice-of-others/description/ + * + * algorithms + * Easy (42.04%) + * Likes: 97 + * Dislikes: 0 + * Total Accepted: 47.9K + * Total Submissions: 113.6K + * Testcase Example: '[3,6,1,0]' + * + * 给你一个整数数组 nums ,其中总是存在 唯一的 一个最大整数 。 + * + * 请你找出数组中的最大元素并检查它是否 至少是数组中每个其他数字的两倍 。如果是,则返回 最大元素的下标 ,否则返回 -1 。 + * + * + * + * 示例 1: + * + * + * 输入:nums = [3,6,1,0] + * 输出:1 + * 解释:6 是最大的整数,对于数组中的其他整数,6 大于数组中其他元素的两倍。6 的下标是 1 ,所以返回 1 。 + * + * + * 示例 2: + * + * + * 输入:nums = [1,2,3,4] + * 输出:-1 + * 解释:4 没有超过 3 的两倍大,所以返回 -1 。 + * + * 示例 3: + * + * + * 输入:nums = [1] + * 输出:0 + * 解释:因为不存在其他数字,所以认为现有数字 1 至少是其他数字的两倍。 + * + * + * + * + * 提示: + * + * + * 1 + * 0 + * nums 中的最大元素是唯一的 + * + * + */ + +// @lc code=start +func dominantIndex(nums []int) int { + mid,max := ^int(0),^int(0) + index := -1 + for k,v := range nums{ + if v>max{ + mid = max + max = v + index = k + }else if v>mid{ + mid = v + } + } + if max >= mid*2{ + return index + } + return -1 +} +// @lc code=end + diff --git "a/LeetCode/all/89.\346\240\274\351\233\267\347\274\226\347\240\201.go" "b/LeetCode/all/89.\346\240\274\351\233\267\347\274\226\347\240\201.go" new file mode 100644 index 0000000..43a2ee9 --- /dev/null +++ "b/LeetCode/all/89.\346\240\274\351\233\267\347\274\226\347\240\201.go" @@ -0,0 +1,77 @@ +/* + * @lc app=leetcode.cn id=89 lang=golang + * + * [89] 格雷编码 + * + * https://leetcode-cn.com/problems/gray-code/description/ + * + * algorithms + * Medium (71.28%) + * Likes: 376 + * Dislikes: 0 + * Total Accepted: 65.2K + * Total Submissions: 91.4K + * Testcase Example: '2' + * + * n 位格雷码序列 是一个由 2^n 个整数组成的序列,其中: + * + * 每个整数都在范围 [0, 2^n - 1] 内(含 0 和 2^n - 1) + * 第一个整数是 0 + * 一个整数在序列中出现 不超过一次 + * 每对 相邻 整数的二进制表示 恰好一位不同 ,且 + * 第一个 和 最后一个 整数的二进制表示 恰好一位不同 + * + * + * 给你一个整数 n ,返回任一有效的 n 位格雷码序列 。 + * + * + * + * 示例 1: + * + * + * 输入:n = 2 + * 输出:[0,1,3,2] + * 解释: + * [0,1,3,2] 的二进制表示是 [00,01,11,10] 。 + * - 00 和 01 有一位不同 + * - 01 和 11 有一位不同 + * - 11 和 10 有一位不同 + * - 10 和 00 有一位不同 + * [0,2,3,1] 也是一个有效的格雷码序列,其二进制表示是 [00,10,11,01] 。 + * - 00 和 10 有一位不同 + * - 10 和 11 有一位不同 + * - 11 和 01 有一位不同 + * - 01 和 00 有一位不同 + * + * + * 示例 2: + * + * + * 输入:n = 1 + * 输出:[0,1] + * + * + * + * + * 提示: + * + * + * 1 <= n <= 16 + * + * + */ + +// @lc code=start +func grayCode(n int) []int { + res := []int{0} + head := 1 + for i:=0;i=0;j--{ + res = append(res, res[j]|head) + } + head = head << 1 + } + return res +} +// @lc code=end + diff --git a/LeetCode/suan-fa-mian-shi-zhu-yi.md b/LeetCode/suan-fa-mian-shi-zhu-yi.md index 58a4887..5e74572 100644 --- a/LeetCode/suan-fa-mian-shi-zhu-yi.md +++ b/LeetCode/suan-fa-mian-shi-zhu-yi.md @@ -5,7 +5,7 @@ * 进入函数优先考虑边界 * 如果出现循环,进入循环时考虑`break`条件和`continue`条件 * 使用下标计算长度时,优先考虑区间的开闭 -* 我们应该在做算法的过程总不断的思考和总结共性 +* 我们应该在做算法的过程中不断的思考和总结共性 ## 面试注意 @@ -40,7 +40,7 @@ math.MinInt32 牛客网比较坑,一切输入输出都要自己实现 -还要劳记链表创建代码,[完整代码](https://github.com/coding3min/interview-leetcode/tree/5cb2e5224e55a017aa569f71ee31714371f1a8cd/LeetCode/all/0.创建链表.go) +还要牢记链表创建代码,[完整代码](https://github.com/coding3min/interview-leetcode/tree/5cb2e5224e55a017aa569f71ee31714371f1a8cd/LeetCode/all/0.创建链表.go) ```go package main diff --git "a/LeetCode/\345\211\221\346\214\207offer/day10_\345\212\250\346\200\201\350\247\204\345\210\222(\344\270\255\347\255\211)2/\346\212\212\346\225\260\345\255\227\347\277\273\350\257\221\346\210\220\345\255\227\347\254\246\344\270\262.go" "b/LeetCode/\345\211\221\346\214\207offer/day10_\345\212\250\346\200\201\350\247\204\345\210\222(\344\270\255\347\255\211)2/\346\212\212\346\225\260\345\255\227\347\277\273\350\257\221\346\210\220\345\255\227\347\254\246\344\270\262.go" new file mode 100644 index 0000000..fa36960 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day10_\345\212\250\346\200\201\350\247\204\345\210\222(\344\270\255\347\255\211)2/\346\212\212\346\225\260\345\255\227\347\277\273\350\257\221\346\210\220\345\255\227\347\254\246\344\270\262.go" @@ -0,0 +1,52 @@ +// 题目链接:https://leetcode.cn/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/?envType=study-plan&id=lcof +// day10/31 +// 第 10 天主题为:动态规划(中等) +// 包含两道题目: +// 剑指offer46.把数字翻译成字符串 +// 剑指offer48.最长不含重复字符的子字符串 +package main + +import "strconv" + +//比较明显的动态规划题目,设 dp[i]代表s[:i+1]的翻译方法数目,dp[i] 明显依赖于 dp[i-1] 与 dp[i-2],类似于斐波那契数是吧, +//本题有点不一样的地方在于 dp[i]=dp[i-1]+dp[i-2] 是有条件的 +//- 当 s[i-1] 与 s[i] 组成的数字小于 25 时,s[i] 可以与 s[i-1] 组合翻译,也可以分开翻译,dp[i]=dp[i-1]+dp[i-2], +//- 否则 s[i] 与 s[i-1] 无法组合翻译,只能单独翻译,dp[i] = dp[i-1] + +//动态规划三步骤: +//- 确定dp数组大小及下标含义:dp[i] 代表 s[:i+1] 的翻译方法数目,len(dp)=len(string(num)) +//- dp 数组初始化:dp[0]对应s[0],单个字符只有一种翻译方法,dp[0]=1,当 s[:2] 小于26 且 s[i]!=0 时,dp[1]=2,否则 dp[1]=1 +//- 状态转移方程:从下标 2 开始遍历,x = strconv.Atoi(s[i-1:i+1]),并且判断 s[i-1] 是否为 0 +//- 若 x < 26 且 s[i-1]!= 0,dp[i]=dp[i-1]+dp[i-2],s[i] 可以与 s[i-1] 组合翻译,也可以单独翻译 +//- 否则,dp[i] = dp[i-1],s[i] 只能单独翻译,s[:i+1] 的翻译方法数目依赖于 s[:i] +// 最后,返回 dp[n-1] 即可。 + +//有一点需要注意,在状态转移方程那里一定要判断 s[i-1] 是否为 0,因为 “01”只能翻译为 “ab”,不能翻译成 “a", +//我第一次做这道题的时候就是忘记判断 s[i-1]是否为0,导致没有ac,刚才做的时候,又在这个地方跌到坑里了,有了再一再二,不会有再三再四了。 +func translateNum(num int) int { + s := strconv.Itoa(num) + n := len(string(s)) + if n == 0{ + return 0 + } + dp := make([]int,n) + dp[0] = 1 + x,_ := strconv.Atoi(s[:2]) + zero,_ := strconv.Atoi(string(s[0])) + if x < 26 && zero != 0{ + dp[1] = 2 + } else { + dp[1] = 1 + } + for i:=2;i start{ + start = record[x] + } else { + res = max(res,i-start) + } + record[x] = i + } + return res +} + +func max(x,y int) int { + if x > y{ + return x + } + return y +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day11_\345\217\214\346\214\207\351\222\210(\347\256\200\345\215\225)/\345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\350\212\202\347\202\271.go" "b/LeetCode/\345\211\221\346\214\207offer/day11_\345\217\214\346\214\207\351\222\210(\347\256\200\345\215\225)/\345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\350\212\202\347\202\271.go" new file mode 100644 index 0000000..93e0b30 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day11_\345\217\214\346\214\207\351\222\210(\347\256\200\345\215\225)/\345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\350\212\202\347\202\271.go" @@ -0,0 +1,59 @@ +// 题目链接:https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof/?envType=study-plan&id=lcof +// day11/31 +// 第 11 天主题为:双指针(简单) +// 包含两道题目: +// 剑指offer18.删除链表的节点 +// 剑指offer22.链表中倒数第k个节点 + +package main + +//思路很简单的题目,就是要注意代码的鲁棒性 +//链表相关的题目非常考察代码的鲁棒性,谨防访问空节点.Next 这种情况的发生。 + +//要删除某个节点,我们需要记录要删除节点的前一个节点,将前一个节点的 Next域 指向要删除节点的下一个节点, +//所以我们要保存要删除节点的前一个节点,才能做到删除节点。 +//cur=head,一次遍历链表,遍历条件为 cur.Next != nil,如果 cur.Next 是我们要删除的节点, +//cur.Next = cur.Next.Next,然后结束遍历,否则 cur=cur.Next,向后移动节点。最后返回 head。 + +//有一种情况需要额外判断,就是 head 节点就是要删除的节点,因为我们无法保存该节点的前一个节点, +//针对这种情况,直接返回 head.Next 即可 +//在遍历之前,我们就要对这种情况进行判断。 + +//Definition for singly-linked list. +type ListNode struct { + Val int + Next *ListNode +} + +func deleteNode(head *ListNode, val int) *ListNode { + if head.Val == val{ + return head.Next + } + cur := head + for cur.Next != nil{ + if cur.Next.Val == val{ + cur.Next = cur.Next.Next + // 跳出是为了应对要删除的节点为链表最后一个,会造成访问空指针的情况出现 + break + } + cur = cur.Next + } + return head +} + +//稍微来扩展一下,对应《剑指offer》 +// +//如果题目给的是一个链表的头指针 和 一个要被删除的节点,而非节点的值。那我们应该如何处理呢? +//按照如上时间复杂度O(n)的思路当然是可以的,那是不是一定需要找到被删除节点的前一个节点呢? +//答案是否定的,我们可以很方便地找到要删除的节点的下一个节点。如果我们把下一个节点的内容复制到被删除节点上来覆盖该节点原有的内容,再把下一个节点删除,那是不是就相当于把需要删除的节点删除了? + +//(这个思路很新奇,在用户的需求中,删除节点的意思就是删除节点值,而非内存空间,要抓住这样的需求和抽象层析的信息不对称来寻求突破) + +//上述思路还有一个问题:如果被删除的节点位于链表的尾部,Next域为空,就无法使用了,在这种情况下,我们只能遍历链表,得到该节点的前一个节点,将该节点删除。 +//值得注意的是:上述代码仍然不是一段完美的代码,因为它基于这样一种假设:要删除的节点确定在链表中。我们需要O(n)的时间才能判断链表是否包含某一节点。 +//在面试的过程中,我们可以和面试官讨论这个假设,这样面试官就会觉得我们考虑问题非常全面。 +// +//考察点: +//- 应聘者对链表的编程能力 +//- 创新思维能力,这需要应聘者打破常规思维模式。当我们想要删除某一节点时,不一定要删除节点本身,可以先把下一个节点的内容复制出来覆盖要被删除节点的内容,再将下一个节点删除,这种思路不容易想到 +//- 代码的鲁棒性,也即应聘者思维的全面性,全面考虑到该节点是链表尾结点或头结点的情况。 \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day11_\345\217\214\346\214\207\351\222\210(\347\256\200\345\215\225)/\351\223\276\350\241\250\344\270\255\345\200\222\346\225\260\347\254\254K\344\270\252\350\212\202\347\202\271.go" "b/LeetCode/\345\211\221\346\214\207offer/day11_\345\217\214\346\214\207\351\222\210(\347\256\200\345\215\225)/\351\223\276\350\241\250\344\270\255\345\200\222\346\225\260\347\254\254K\344\270\252\350\212\202\347\202\271.go" new file mode 100644 index 0000000..bdb2d64 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day11_\345\217\214\346\214\207\351\222\210(\347\256\200\345\215\225)/\351\223\276\350\241\250\344\270\255\345\200\222\346\225\260\347\254\254K\344\270\252\350\212\202\347\202\271.go" @@ -0,0 +1,38 @@ +//题目链接:https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/ + +package main + +//这道题比较通俗的解法是两次遍历 +//先一次遍历链表,得到链表的长度 n,倒数第 k 个节点,即正数第 n-k+1 个节点,第二次遍历输出该节点即可。 +func getKthFromEnd(head *ListNode, k int) *ListNode { + n := 0 + cur := head + // 第一次遍历得到链表长度n + for cur != nil{ + n ++ + cur = cur.Next + } + cur = head + x := n-k+1 + //第二次遍历找到整数第n-k+1个节点并返回 + for i:=0;i0,那 x 和 y 走 m+n-x 步后到达该节点,若不存在公共节点,x 和 y 始终不相等,知道 x 和 y 共同走向合并链表的尽头, +//也就是空节点,此时,返回 A or B 都是空节点。 +// +//针对细节,在做一些描述(也是自己曾经的疑惑): +//1. 对一个链表(长度为n),从头结点走到尾结点需要 n-1 步,走 n 步会到达尾结点的Next域(即空节点),在本题的双指针解法中,真是要走到该空节点位置,然后再走到另一条链表的道路,和另一个指针一起判断是否存在公共节点。 +//2. 公共节点,该节点不仅Val域相同,Next域也是相同的,所以以该节点作为头结点的链表长度也是相同的。 +//3. 若两链表长度相同呢,感觉都走不到对方的链表上面?确实是这样,因为这种情况下我们也不需要走到对方的链表上,具体分两种情况 +// 1. 存在公共节点,两指针 x 和 y 在第一条路上就能找到公共节点 +// 2. 不存在公共节点,x 和 y 走到各自链表的尾结点的 Next 域时,已经相同,返回空节点即可 + +// 此解法时间复杂度O(m+n),空间复杂度O(1),满足题目要求(题目描述是O(n),这里我认为已经满足) +func getIntersectionNode_2(headA, headB *ListNode) *ListNode { + x,y := headA,headB + for x != y{ + if x != nil{ + x = x.Next + } else { + x = headB + } + if y != nil{ + y = y.Next + } else { + y = headA + } + } + return x +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day12_\345\217\214\346\214\207\351\222\210(\347\256\200\345\215\225)2/\345\220\210\345\271\266\344\270\244\344\270\252\346\216\222\345\272\217\347\232\204\351\223\276\350\241\250.go" "b/LeetCode/\345\211\221\346\214\207offer/day12_\345\217\214\346\214\207\351\222\210(\347\256\200\345\215\225)2/\345\220\210\345\271\266\344\270\244\344\270\252\346\216\222\345\272\217\347\232\204\351\223\276\350\241\250.go" new file mode 100644 index 0000000..475d552 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day12_\345\217\214\346\214\207\351\222\210(\347\256\200\345\215\225)2/\345\220\210\345\271\266\344\270\244\344\270\252\346\216\222\345\272\217\347\232\204\351\223\276\350\241\250.go" @@ -0,0 +1,60 @@ +//题目链接:https://leetcode.cn/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/?envType=study-plan&id=lcof + +package main + +//Definition for singly-linked list. +type ListNode struct { + Val int + Next *ListNode +} + +//很经典的链表题目了,计算机考研算法必做题 +//我的思路是,新建一个最终返回链表的头结点 head,Val域为0(随便设),Next域为空,之后的链表合并操作在其 Next域进行,最终返回 head.Next 即可。 +//再声明一个变量 cur = head,用于合并链表,具体分为两步: +//1. 循环合并:当 l1 与 l2 链表头结点均非空时,判断两者头结点的Val域,谁更小,cur.Next 指向该节点, +// 然后该链表头节点指向其下一个节点,cur也指向其Next域,如此循环,直至 l1 或 l2 为空; +//2. 合并剩余尾部:此时若 l1 链表非空,则 head 指向的链表所有节点值均小于等于 l1 链表剩余节点值,cur.Next 指向 l1 即可; +// 若是 l2 链表非空,同理。 + +//最终返回 head.Next 即可。 +func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode { + head := &ListNode{0,nil} + cur := head + for l1 != nil && l2 != nil{ + if l1.Val <= l2.Val{ + cur.Next = l1 + cur = cur.Next + l1 = l1.Next + } else { + cur.Next = l2 + cur = cur.Next + l2 = l2.Next + } + } + if l1 != nil{ + cur.Next = l1 + } + if l2 != nil{ + cur.Next = l2 + } + return head.Next +} + +//这道题还可以递归解决: +//当 l1 或 l2 链表为空时,无需合并,直接返回另一个非空链表即可。 +//否则,判断 l1 和 l2 链表的头结点谁更小,改变其 Next 域,递归地指向 l1 和 l2.Next 合并链表的结果,最后返回该链表头结点。递归结束条件为某一链表为空。 +func mergeTwoLists_2(l1 *ListNode, l2 *ListNode) *ListNode { + if l1 == nil{ + return l2 + } + if l2 == nil{ + return l1 + } + if l1.Val <= l2.Val{ + l1.Next = mergeTwoLists(l1.Next,l2) + return l1 + } else { + l2.Next = mergeTwoLists(l1,l2.Next) + return l2 + } +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day13_\345\217\214\346\214\207\351\222\210(\347\256\200\345\215\225)3/\345\222\214\344\270\272s\347\232\204\344\270\244\344\270\252\346\225\260\345\255\227.go" "b/LeetCode/\345\211\221\346\214\207offer/day13_\345\217\214\346\214\207\351\222\210(\347\256\200\345\215\225)3/\345\222\214\344\270\272s\347\232\204\344\270\244\344\270\252\346\225\260\345\255\227.go" new file mode 100644 index 0000000..2a796c9 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day13_\345\217\214\346\214\207\351\222\210(\347\256\200\345\215\225)3/\345\222\214\344\270\272s\347\232\204\344\270\244\344\270\252\346\225\260\345\255\227.go" @@ -0,0 +1,66 @@ +// 题目链接:https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/?envType=study-plan&id=lcof +// day13/31 +// 第 13 天主题为:双指针(简单) +// 包含三道题目: +// 剑指offer21.调整数组顺序使奇数位于偶数前面 +// 剑指offer57.和为s的两个数字 +// 剑指offer58-I.翻转单词顺序 + +package main + +//梦回LeetCode第一题:两数之和,有点像是吧,这道题和两数之和的区别在于:数组有序。 +//方法 1 :先来暴力解法,双层遍历 +//第一层遍历从头开始,第二层遍历从第一层遍历元素的下一个元素开始,若两数之和等于 target,返回即可。 +func twoSum(nums []int, target int) []int { + n := len(nums) + for i:=0;i target{ + right -- + } else if nums[left]+nums[right] < target{ + left ++ + } else { + return []int{nums[left],nums[right]} + } + } + return []int{0,0} +} +//时间复杂度O(n),空间复杂度O(1),充分利用题目所给条件,为最优解法! \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day13_\345\217\214\346\214\207\351\222\210(\347\256\200\345\215\225)3/\347\277\273\350\275\254\345\215\225\350\257\215\351\241\272\345\272\217.go" "b/LeetCode/\345\211\221\346\214\207offer/day13_\345\217\214\346\214\207\351\222\210(\347\256\200\345\215\225)3/\347\277\273\350\275\254\345\215\225\350\257\215\351\241\272\345\272\217.go" new file mode 100644 index 0000000..ee0057f --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day13_\345\217\214\346\214\207\351\222\210(\347\256\200\345\215\225)3/\347\277\273\350\275\254\345\215\225\350\257\215\351\241\272\345\272\217.go" @@ -0,0 +1,78 @@ +//题目链接:https://leetcode.cn/problems/fan-zhuan-dan-ci-shun-xu-lcof/?envType=study-plan&id=lcof + +package main + +import ( + "strings" +) + +//我的想法是使用栈,每遇到一个完整的单词,添加到栈中,最后出栈。 +// +//具体过程:一次遍历,用 string.Builder 构建字符串,若当前字符不是空格,则写入将当前字符写入 sb, +//若是,则将 sb 的内容写入栈 并 清空内容,准备下一个单词的填充。 +func reverseWords(s string) string { + var sb strings.Builder + n := len(s) + if n == 0{ + return "" + } + stack := []string{} + for i:=0;i0 && s[i-1]!=32{ + stack = append(stack,sb.String()) + sb.Reset() + } else if s[i]!=32{ + sb.WriteByte(s[i]) + } + } + if s[n-1] != 32{ + stack = append(stack,sb.String()) + } + sb.Reset() + for i:=len(stack)-1;i>=0;i--{ + sb.WriteString(stack[i]) + sb.WriteByte(' ') + } + res := sb.String() + // 去掉末尾空格 + // 同时注意特殊情况:s全为空格,res长度为0,对其进行去除末尾空格操作会索引越界 + if len(res)==0{ + return "" + } + res = res[:len(res)-1] + return res +} + + +//还可以用双指针: +// +//声明变量 res 为空字符串,left 和 right 两指针初始化为 0,左右指针分别用于寻找每个单词的左右边界。 +//一次遍历字符串,左指针向右移动寻找第一个非空格字符,为要寻找字符串的左边界,然后开始寻找右边界,先将 left 的值赋给 right,然后 right 向右移动, +//直到到达字符串末尾或者寻找到空格字符,此时 left 和 right 分别指向一个单词的左右边界,将其加入 res,然后将 right 赋给 left,开始寻找下一个单词。 +// +//具体实现时,注意每加入一个单词的同时,还要添加一个空格,最后返回 res 前,处理掉最后添加的空格。 +// +//注意特殊情况的处理,当输入字符串全为空格时,res 长度为空,此时如果对其进行去除空格的操作,会索引越界。 +// 方法2: +func reverseWords_2(s string) string { + n := len(s) + left,right := 0,0 + res := "" + for left < n{ + if s[left] == 32{ + left ++ + } else { + right = left + for right=m || j >= n || bitsum(i)+bitsum(j)>k{ + return 0 + } + if used[i][j]{ + return 0 + } + used[i][j] = true + return 1 + dfs(i,j+1) + dfs(i+1,j) + } + return dfs(0,0) +} + +func bitsum(x int) int { + res := 0 + for x > 0{ + res += x % 10 + x /= 10 + } + return res +} + + +//方法2:BFS +//本题中,虽然我们也使用 used 数组记录每个位置是否被访问过,但是我们不需要回溯操作,所以是可以使用 BFS 的。 +//BFS通常需要借助队列的先进先出特性来实现 + +func movingCount_2(m int, n int, k int) int { + used := make([][]bool,m) + for i:=0;i 4{ + return false + } + return true +} + + +//另外,我们也可以不用排序,而是使用一次遍历的方式求得最大最小值(0除外) +//思路和排序是一致的,代码如下 +//这种解法注意 minNum 和 maxNum 的初始化,minNum 和 maxNum 分别初始化为最大和最小的数字 +func isStraight_2(nums []int) bool { + minNum,maxNum := 14,0 + record := map[int]struct{}{} + for _,num := range nums{ + if num == 0{ + continue + } + if _,ok:=record[num];ok{ + return false + } + record[num] = struct{}{} + minNum = min(minNum,num) + maxNum = max(maxNum,num) + } + if maxNum - minNum > 4{ + return false + } + return true +} + +func max(x,y int) int{ + if x > y{ + return x + } + return y +} + +func min(x,y int) int{ + if x < y{ + return x + } + return y +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day16_\346\216\222\345\272\217(\347\256\200\345\215\225)/\346\212\212\346\225\260\347\273\204\346\216\222\346\210\220\346\234\200\345\260\217\347\232\204\346\225\260.go" "b/LeetCode/\345\211\221\346\214\207offer/day16_\346\216\222\345\272\217(\347\256\200\345\215\225)/\346\212\212\346\225\260\347\273\204\346\216\222\346\210\220\346\234\200\345\260\217\347\232\204\346\225\260.go" new file mode 100644 index 0000000..a949a27 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day16_\346\216\222\345\272\217(\347\256\200\345\215\225)/\346\212\212\346\225\260\347\273\204\346\216\222\346\210\220\346\234\200\345\260\217\347\232\204\346\225\260.go" @@ -0,0 +1,55 @@ +// 题目链接:https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/ +package main + +import ( + "sort" + "strconv" +) + +//这道题我刚开始以为数组的元素都是个位数,想着用哈希表统计每个数字出现的频率,然后从 0 开始遍历,将制 +//定数目的数字添加至返回的在字符串中, 看了下示例,才知道数组的元素可以不是个位数。 +// +//但其实原理也是类似的,小数在前,大数在后,本质上是一个排序问题,设 nums 中两整数分别为 x 和 y, +//先将其转化为字符串,然后拼接位 x+y 和 y+x 作比较 +//- 若拼接字符串 x+y > y+x,则 x > y +//- 否则(包括相等的情况),x < y +//根据此规则,套用任何排序算法对 nums 执行即可。 +func minNumber(nums []int) string { + n := len(nums) + strNums := make([]string,len(nums)) + for i:=0;i12 的情况下,使用的是快速排序,经过递归,长度降至 12以下时,使用插入排序。 +//下面咱来不用标准库,自己复习下冒泡,来解这道题 +func minNumber_2(nums []int) string { + n := len(nums) + strNums := make([]string,len(nums)) + for i:=0;i strNums[j+1]+strNums[j]{ + strNums[j],strNums[j+1] = strNums[j+1],strNums[j] + } + } + } + res := "" + for i:=0;i k{ + return quickSort(nums,left,partitionIndex-1,k) + } else { + // 当相等时,达到我们的要求,返回“排好序”的数组 + return nums + } + } + return nums +} + +//移动左右指针,按照基准(这里使用nums[left])划分区域。最后返回基准所在的下标 +func partition(nums []int,left,right int) int { + // 基准 选择 left 指向的元素 + pivot := nums[left] + for left < right{ + for left=pivot{ + right -- + } + nums[left] = nums[right] + for left y{ + return x + } + return y +} + + + +func maxDepth_2(root *TreeNode) int { + if root == nil { + return 0 + } + res := 0 + q := []*TreeNode{root} + for len(q) != 0{ + res ++ + n := len(q) + for i:=0;i= 0 +} + +func depth(node *TreeNode) int { + if node == nil{ + return 0 + } + leftDepth := depth(node.Left) + rightDepth := depth(node.Right) + if leftDepth == -1 || rightDepth == -1 { + return -1 + } + if abs(leftDepth-rightDepth)>1{ + return -1 + } + return 1+max(leftDepth,rightDepth) +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day19_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\344\270\255\347\255\211)2/BST\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.go" "b/LeetCode/\345\211\221\346\214\207offer/day19_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\344\270\255\347\255\211)2/BST\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.go" new file mode 100644 index 0000000..3c9dd00 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day19_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\344\270\255\347\255\211)2/BST\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.go" @@ -0,0 +1,69 @@ +//题目链接:https://leetcode.cn/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/ +// day19/31 +// 第 19 天主题为:搜索与回溯算法(中等) +// 包含三道题目: +// 剑指offer64.求1+2+...+n +// 剑指offer68-I.二叉搜索树的最近公共祖先 +// 剑指offer68-II.二叉树的最近公共祖先 +package main + + +//Definition for a binary tree node. +type TreeNode struct { + Val int + Left *TreeNode + Right *TreeNode +} + +//BST 的中序遍历是有序数组,root 节点的左子树中所有节点 val 都比 root.val 小,右子树中所有节点的 val 比root.val 大。 +//根据这个特点,我们对 root、p、q 的节点值进行分情况讨论: +// +//1. p.val > root.val 且 q.val < root.val,说明 p 和 q 分别存在于 root 节点的右左子树中,root 节点为最近公共祖先(p.val < q.val 同理); +// +//2. p.Val < root.Val 且 q.Val > root.Val,与第一种情况同理,p、q 分别在root 的左右子树中,root 节点为最近公共祖先; +// +//3. root.Val == p.Val 或者 root.Val == q.Val,说明另一个节点在 root 的左子树或者右子树中, root 已经是两节点的最近公共祖先, +// 因为我们是从整棵树的根节点开始往下遍历的 +// +//4. p.Val < root.Val 且 q.Val < root.Val,p 和 q 均在 root 的左子树中,root 的左孩子节点会是 p 和 q 的公共祖先, +// 对 root.Left 进行递归操作寻找 p 和 q 的最近公共祖先 +// +//5. p.Val > root.Val 且 q.Val > root.Val,与情况 4 同理,对 root.Right 进行递归操作寻找 p 和 q 的最近公共祖先。 +// +//因为 1 和 2同理,4 和 5 同理,精简一些,只有三种情况,令 p.Val 小于 q.Val +// +//1. p.Val <= root.Val 或者 root.val < q.Val,此时 root 为 最近公共祖先 +//2. p.Val < root.Val < q.Val,p 和 q 分别在 root 的左右子树中,root 为 最近公共祖先 +//3. p.Val < root.Val 且 q.Val < root.Val,对 root.Left 递归操作;若均大于,对 root.Right 递归操作 +// +//虽然我分析过程中写的是递归,但实际还是有递归和迭代两种写法的 + +//递归 + +func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { + if root.Valp.Val && root.Val>q.Val{ + return lowestCommonAncestor(root.Left,p,q) + } + return root +} + +//迭代 +func lowestCommonAncestor_2(root, p, q *TreeNode) *TreeNode { + for root != nil { + if p.Valroot.Val && q.Val>root.Val{ + root = root.Right + continue + } + return root + } + // 此种情况不会发生,因为题目说明p和q均存在于给定BST中 + // 只是为了保证代码的逻辑正确 + return root +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day19_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\344\270\255\347\255\211)2/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.go" "b/LeetCode/\345\211\221\346\214\207offer/day19_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\344\270\255\347\255\211)2/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.go" new file mode 100644 index 0000000..b9a2e13 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day19_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\344\270\255\347\255\211)2/\344\272\214\345\217\211\346\240\221\347\232\204\346\234\200\350\277\221\345\205\254\345\205\261\347\245\226\345\205\210.go" @@ -0,0 +1,67 @@ +//题目链接:https://leetcode.cn/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/?envType=study-plan&id=lcof +package main + +//取消了第一版题目BST的条件,退化为普通的二叉树,我们就只能相对暴力地去解决这个问题。 +// +//解法参考自题解:https://leetcode.cn/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/solution/mian-shi-ti-68-ii-er-cha-shu-de-zui-jin-gong-gon-7/ +// +//考虑通过递归对二叉树进行先序遍历,遇到 p 或 q 时返回,从底至顶回溯,当节点 p、q 分别在 root 的两侧 或 p、q 两者之一就是 root 节点时, +//root 为 p 和 q 的LCA,返回 root。 +// +//然后具体分析如何实现: +// +//1. 终止条件: +// 1. 当越过叶节点时,直接返回 nil; +// 2. root 等于 p 或 q 时,返回 root +//2. 递推工作: +// 1. 递归 root.Left,记返回值为 left; +// 2. 递归 root.Right,记返回值为 right; +//3. 返回值:根据 left 和 right,分为以下四种情况: +// 1. left 和 right 同时为空,说明 root 的左右子树均不包含 p 和 q,以 root 为根节点的子树不可能包含 p 和 q,返回 nil +// 2. left 为 空,right 不为空,说明 p 和 q 都包含在 root.Right 中,返回 right(分两种情况, +// 第一种:p 和 q 其中一个节点在 root 的右子树中,此时 right 为所包含的节点; +// 第二种:p 和 q 均在 root 的右子树中,此时 right 指向 p 和 q 的 LCA) +// 3. right 为 空,left 不为空,与情况2同理 +// 4. left 和 right 均不为空,说明 p、q 分别在 root 的左右子树中,root 为 LCA,返回 root 即可。 +func lowestCommonAncestor_3(root, p, q *TreeNode) *TreeNode { + // 递归结束条件 + if root == nil{ + return nil + } + if root.Val==p.Val || root.Val==q.Val{ + return root + } + // 开始递归 + left := lowestCommonAncestor(root.Left,p,q) + right := lowestCommonAncestor(root.Right,p,q) + // 情况1 + if left==nil && right==nil{ + return nil + } + // 情况2 + if left == nil{ + return right + } + // 情况3 + if right == nil{ + return left + } + // 情况4 + return root +} + +//这里想看过的一段话: +// 学习一个算法,最要的是弄清楚这个算法要解决什么样的问题,它的已知量(input)是什么? +// 待求的未知量(output)是什么 +// 如果这三个问题没找到答案就去学习算法,就会浪费大量时间在逻辑猜测与记忆上 + +//针对本题,对 lowestCommonAncestor 函数,本人分析如下 +//函数 lowestCommonAncestor 输入为 root、p 和 q +// +//输出分几种情况: +//1. 若 root 为空节点,直接返回 root 即可 +//2. 以 root 为根节点的树包含 p 和 q,返回 p 和 q 的 LCA; +//3. 以 root 为根节点的树只包含 p 或 q 其中一个,则返回 包含的节点; +//4. 以 root 为根节点的树不包含 p 且 不包含 q,返回 nil + +//解决问题:在以 root 为根节点的子树中,寻找 p 和 q 的 LCA diff --git "a/LeetCode/\345\211\221\346\214\207offer/day19_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\344\270\255\347\255\211)2/\346\261\2021+2+...+n.go" "b/LeetCode/\345\211\221\346\214\207offer/day19_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\344\270\255\347\255\211)2/\346\261\2021+2+...+n.go" new file mode 100644 index 0000000..9357abd --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day19_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\344\270\255\347\255\211)2/\346\261\2021+2+...+n.go" @@ -0,0 +1,26 @@ +//题目链接:https://leetcode.cn/problems/qiu-12n-lcof/?envType=study-plan&id=lcof +package main + +//先不考虑限制条件,有三种解法, +//1.高斯求和公式(使用乘除法) +//2.迭代(使用for或while) +//3.递归(递归终止条件需要使用if)。 +// +//考虑限制条件的话,难点在于如何实现这个 1~n 的循环。 +//我们可以利用运算符的短路效应实现该循环 +// +//先看下逻辑与 与 逻辑或 的短路效应 +//- if a && b; 若 a 为 false,对 b 的判断不会执行(即短路),直接返回 false +//- if a || b; 若 a 为 true,对 b的判断不会执行,直接返回 true +// +//我们可以利用这种短路机制,通过递归实现 1~n 的相加 +func sumNums(n int) int { + res := 0 + var f func(*int,int) bool + f = func(res *int,n int) bool{ + *res += n + return n > 0 && f(res,n-1) + } + f(&res,n) + return res +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day1_\346\240\210\344\270\216\351\230\237\345\210\227(\347\256\200\345\215\225)/\345\214\205\345\220\253min\345\207\275\346\225\260\347\232\204\346\240\210.go" "b/LeetCode/\345\211\221\346\214\207offer/day1_\346\240\210\344\270\216\351\230\237\345\210\227(\347\256\200\345\215\225)/\345\214\205\345\220\253min\345\207\275\346\225\260\347\232\204\346\240\210.go" new file mode 100644 index 0000000..fb7dd34 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day1_\346\240\210\344\270\216\351\230\237\345\210\227(\347\256\200\345\215\225)/\345\214\205\345\220\253min\345\207\275\346\225\260\347\232\204\346\240\210.go" @@ -0,0 +1,60 @@ +// 题目链接:https://leetcode.cn/problems/bao-han-minhan-shu-de-zhan-lcof/ +// 思路:建立辅助栈,与存储数据的栈大小相同,在向栈中存数据时,辅助栈同时存入一个数字 +// 该辅助栈入栈元素的序列是一个非严格递增序列 +// 如果该数据小于辅助栈栈顶元素,则辅助栈存入该数据,否则辅助栈还存入一个辅助栈的栈顶元素。 +//(如果是存入第一个元素,辅助栈直接入栈即可,无比较操作) + +package main + +type MinStack struct { + nums []int //储存栈 + min []int //辅助储存栈,存储最小值 +} + +/** initialize your data structure here. */ +// 为解决命名冲突,这里函数名后+“2”,在LeetCode需删除 +func Constructor2() MinStack { + return MinStack{ + []int{}, + []int{}, + } +} + +// 入栈时,存储栈直接入栈 +// 对辅助栈,若栈长度为0,直接入栈 +// 否则,与栈顶元素进行比较,若大于栈顶元素,入栈,否则,辅助栈再次存入栈顶元素 +func (this *MinStack) Push(x int) { + this.nums=append(this.nums,x) + if len(this.min)==0{ + this.min=append(this.min,x) + }else if this.min[len(this.min)-1] 0{ + x := this.stack1[len(this.stack1)-1] + this.stack2 = append(this.stack2,x) + this.stack1 = this.stack1[:len(this.stack1)-1] + } + } + // stack2出栈的元素即为队首元素 + if len(this.stack2) > 0{ + res := this.stack2[len(this.stack2)-1] + this.stack2 = this.stack2[:len(this.stack2)-1] + return res + } + // 若stack2长度仍为0,说明队列为空,返回-1 + return -1 +} + + +/** + * Your CQueue object will be instantiated and called as such: + * obj := Constructor(); + * obj.AppendTail(value); + * param_2 := obj.DeleteHead(); + */ \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day1_\346\240\210\344\270\216\351\230\237\345\210\227(\347\256\200\345\215\225)/\347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227_\351\223\276\350\241\250\345\256\236\347\216\260.go" "b/LeetCode/\345\211\221\346\214\207offer/day1_\346\240\210\344\270\216\351\230\237\345\210\227(\347\256\200\345\215\225)/\347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227_\351\223\276\350\241\250\345\256\236\347\216\260.go" new file mode 100644 index 0000000..7ecbf5a --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day1_\346\240\210\344\270\216\351\230\237\345\210\227(\347\256\200\345\215\225)/\347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227_\351\223\276\350\241\250\345\256\236\347\216\260.go" @@ -0,0 +1,35 @@ +// 题目解析在 用两个栈实现队列.go +// 本文件为 用链表实现的栈 来 实现队列 +// 为解决命名冲突,本文件结构体与函数名后+1 +package main + +import "container/list" + +type CQueue1 struct { + stack1, stack2 *list.List +} + +func Constructor1() CQueue1 { + return CQueue1{ + stack1: list.New(), + stack2: list.New(), + } +} + +func (this *CQueue1) AppendTail1(value int) { + this.stack1.PushBack(value) +} + +func (this *CQueue1) DeleteHead1() int { + if this.stack2.Len() == 0 { + for this.stack1.Len() > 0 { + this.stack2.PushBack(this.stack1.Remove(this.stack1.Back())) + } + } + if this.stack2.Len() != 0 { + e := this.stack2.Back() + this.stack2.Remove(e) + return e.Value.(int) + } + return -1 +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day1_\346\240\210\344\270\216\351\230\237\345\210\227\357\274\210\347\256\200\345\215\225\357\274\211/\345\214\205\345\220\253min\345\207\275\346\225\260\347\232\204\346\240\210.go" "b/LeetCode/\345\211\221\346\214\207offer/day1_\346\240\210\344\270\216\351\230\237\345\210\227\357\274\210\347\256\200\345\215\225\357\274\211/\345\214\205\345\220\253min\345\207\275\346\225\260\347\232\204\346\240\210.go" new file mode 100644 index 0000000..fb7dd34 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day1_\346\240\210\344\270\216\351\230\237\345\210\227\357\274\210\347\256\200\345\215\225\357\274\211/\345\214\205\345\220\253min\345\207\275\346\225\260\347\232\204\346\240\210.go" @@ -0,0 +1,60 @@ +// 题目链接:https://leetcode.cn/problems/bao-han-minhan-shu-de-zhan-lcof/ +// 思路:建立辅助栈,与存储数据的栈大小相同,在向栈中存数据时,辅助栈同时存入一个数字 +// 该辅助栈入栈元素的序列是一个非严格递增序列 +// 如果该数据小于辅助栈栈顶元素,则辅助栈存入该数据,否则辅助栈还存入一个辅助栈的栈顶元素。 +//(如果是存入第一个元素,辅助栈直接入栈即可,无比较操作) + +package main + +type MinStack struct { + nums []int //储存栈 + min []int //辅助储存栈,存储最小值 +} + +/** initialize your data structure here. */ +// 为解决命名冲突,这里函数名后+“2”,在LeetCode需删除 +func Constructor2() MinStack { + return MinStack{ + []int{}, + []int{}, + } +} + +// 入栈时,存储栈直接入栈 +// 对辅助栈,若栈长度为0,直接入栈 +// 否则,与栈顶元素进行比较,若大于栈顶元素,入栈,否则,辅助栈再次存入栈顶元素 +func (this *MinStack) Push(x int) { + this.nums=append(this.nums,x) + if len(this.min)==0{ + this.min=append(this.min,x) + }else if this.min[len(this.min)-1] 0{ + x := this.stack1[len(this.stack1)-1] + this.stack2 = append(this.stack2,x) + this.stack1 = this.stack1[:len(this.stack1)-1] + } + } + // stack2出栈的元素即为队首元素 + if len(this.stack2) > 0{ + res := this.stack2[len(this.stack2)-1] + this.stack2 = this.stack2[:len(this.stack2)-1] + return res + } + // 若stack2长度仍为0,说明队列为空,返回-1 + return -1 +} + + +/** + * Your CQueue object will be instantiated and called as such: + * obj := Constructor(); + * obj.AppendTail(value); + * param_2 := obj.DeleteHead(); + */ \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day1_\346\240\210\344\270\216\351\230\237\345\210\227\357\274\210\347\256\200\345\215\225\357\274\211/\347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227_\351\223\276\350\241\250\345\256\236\347\216\260.go" "b/LeetCode/\345\211\221\346\214\207offer/day1_\346\240\210\344\270\216\351\230\237\345\210\227\357\274\210\347\256\200\345\215\225\357\274\211/\347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227_\351\223\276\350\241\250\345\256\236\347\216\260.go" new file mode 100644 index 0000000..7ecbf5a --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day1_\346\240\210\344\270\216\351\230\237\345\210\227\357\274\210\347\256\200\345\215\225\357\274\211/\347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227_\351\223\276\350\241\250\345\256\236\347\216\260.go" @@ -0,0 +1,35 @@ +// 题目解析在 用两个栈实现队列.go +// 本文件为 用链表实现的栈 来 实现队列 +// 为解决命名冲突,本文件结构体与函数名后+1 +package main + +import "container/list" + +type CQueue1 struct { + stack1, stack2 *list.List +} + +func Constructor1() CQueue1 { + return CQueue1{ + stack1: list.New(), + stack2: list.New(), + } +} + +func (this *CQueue1) AppendTail1(value int) { + this.stack1.PushBack(value) +} + +func (this *CQueue1) DeleteHead1() int { + if this.stack2.Len() == 0 { + for this.stack1.Len() > 0 { + this.stack2.PushBack(this.stack1.Remove(this.stack1.Back())) + } + } + if this.stack2.Len() != 0 { + e := this.stack2.Back() + this.stack2.Remove(e) + return e.Value.(int) + } + return -1 +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day20_\345\210\206\346\262\273\347\256\227\346\263\225(\344\270\255\347\255\211)/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227.go" "b/LeetCode/\345\211\221\346\214\207offer/day20_\345\210\206\346\262\273\347\256\227\346\263\225(\344\270\255\347\255\211)/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227.go" new file mode 100644 index 0000000..300206c --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day20_\345\210\206\346\262\273\347\256\227\346\263\225(\344\270\255\347\255\211)/\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227.go" @@ -0,0 +1,57 @@ +//题目链接:https://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/ +// day20/31 +// 第 20 天主题为:分治算法(中等) +// 包含三道题目: +// 剑指offer07.重建二叉树 +// 剑指offer16.数值的整数次方 +// 剑指offer33.二叉搜索树的后序遍历序列 + +package main + +//一句话描述:后序遍历的根节点在最后一位,前面可以分为两部分,一部分为左子树的后序遍历,另一部分为右子树的后序遍历, +//右子树所有节点值均大于根节点,找到右子树节点的起始节点,判断该部分是否所有节点值都大于根节点的值, +//若否,说明该序列无法构成BST的后序遍历序列,之后再递归判断左子树部分和右子树部分的序列是否为BST的后序遍历序列。 +// +//下面是详细描述: +//二叉树后序遍历的结果是:[ [左子树的后序遍历结果], [右子树的后序遍历结果],根节点] +//我们可以把后序遍历数组分为三部分:左子树的后序遍历、右子树的后序遍历和该二叉树的根节点。二叉树的根节点一定是后序遍历数组的最后一个元素。 +//BST左子树所有元素节点值均小于根节点,右子树所有元素值均大于根节点。所以,我们可以从后序遍历数组的第一个元素遍历到倒数第二个元素, +//进行判断,是否大于等于根节点,若大于等于,说明该元素之前所有元素为左子树的后序遍历结果,从该元素至倒数第二个元素为右子树的后序遍历结果。 +//从该元素起,遍历到倒数第二个元素,若存在元素值小于根节点的值,说明该数组不是BST的后序遍历序列。 然后递归地判断左子树序列与右子树序列是否为某BST的后序遍历序列。 +// +//有一点需要注意的是,right的初始化应该是序列长度-1,我刚开始初始化为0,结果就出错了。 +//如果将 k 初始化为 0,且右子树区间大小为 0,最后 k 值为 0,递归验证右子树的区间会是 postorder[0:n-1], +//而实际上该区间大小为 0,所以我们初始化 k 时,需要其值 大于等于 n-1。 +//这是容易出错的一个点,需要牢记 + +func verifyPostorder(postorder []int) bool { + // 若数组大小小于等于2,则必定是某BST的后序遍历序列 + if len(postorder) <= 2{ + return true + } + n := len(postorder) + // 后序遍历的根节点在数组最后一位 + root := postorder[n-1] + // k 为右子树的后序遍历序列起始下标 + // 初始化为 n-1 + k := n-1 + // 寻找 k 值 + for i:=0;i < n-1;i++{ + if postorder[i] > root{ + k = i + break + } + } + // 验证根节点的右子树所有节点值均大于根节点值 + // 若存在小于根节点的节点,返回 false + for i:=k+1;i>= 1 + } + return res +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day20_\345\210\206\346\262\273\347\256\227\346\263\225(\344\270\255\347\255\211)/\351\207\215\345\273\272\344\272\214\345\217\211\346\240\221.go" "b/LeetCode/\345\211\221\346\214\207offer/day20_\345\210\206\346\262\273\347\256\227\346\263\225(\344\270\255\347\255\211)/\351\207\215\345\273\272\344\272\214\345\217\211\346\240\221.go" new file mode 100644 index 0000000..d067d52 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day20_\345\210\206\346\262\273\347\256\227\346\263\225(\344\270\255\347\255\211)/\351\207\215\345\273\272\344\272\214\345\217\211\346\240\221.go" @@ -0,0 +1,45 @@ +//题目链接:https://leetcode.cn/problems/zhong-jian-er-cha-shu-lcof/?envType=study-plan&id=lcof +package main + +//解题思路:参考自题解(https://leetcode.cn/problems/zhong-jian-er-cha-shu-lcof/solution/mian-shi-ti-07-zhong-jian-er-cha-shu-by-leetcode-s/ +// +//对于任意一颗树而言,前序遍历的形式总是:[根节点,[左子树的前序遍历结果], [右子树的前序遍历结果]] +//即根节点总是前序遍历中的第一个节点。 +//而中序遍历的形式总是:[[左子树的中序遍历结果],根节点,[右子树的中序遍历结果]] +// +//根据二叉树的前序遍历,我们可以构造出根节点,之后要考虑的是如何构造根节点的左孩子节点和右孩子节点 +//只要我们在中序遍历中定位到根节点,我们就可以知道该二叉树左子树和右子树的节点数量。 +//知道数量后,我们就可以在前序遍历和中序遍历中通过切片得到左子树和右子树的前序遍历和中序遍历。 +// +//之后递归构造根节点的左孩子节点和右孩子节点即可 +// +//在中序遍历中对根节点进行定位时,我们这里使用遍历的方式 + +//Definition for a binary tree node. +type TreeNode struct { + Val int + Left *TreeNode + Right *TreeNode +} + +func buildTree(preorder []int, inorder []int) *TreeNode { + // 递归退出条件,遍历序列长度为0,节点为空 + if len(preorder) == 0{ + return nil + } + rootVal := preorder[0] + // 构造根节点 + root := &TreeNode{rootVal,nil,nil} + // k初始化为0,寻找中序遍历中根节点的下标 + k := 0 + for ;k>= 1 + } + return res +} + +//方法2:位运算优化,消除二进制末尾的 1 +//非常巧妙的做法,当 n 非零时,n=n&(n-1)可消除n的二进制中最后一个出现的 1. +//因此,执行 n=n&(n-1)使得n变成 0 的操作次数,就是 n 的二进制中 1 的个数。 +func hammingWeight_2(num uint32) int { + res := 0 + for num != 0{ + res ++ + num &= num-1 + } + return res +} + +//第一种解法的时间复杂度为 O(logn),第二种解法为 O(m),m 为二进制串中 1 的个数; +//两种解法的空间复杂度均为 O(1)。 +// +//个人感觉啊,位运算优化只是一种技巧,在效率方面对比逐位检查提升并不大。 +//因为逐位检查已经是对数级别了,对数级别的时间复杂度已经是相当高效。 \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day22_\344\275\215\350\277\220\347\256\227(\344\270\255\347\255\211)/\346\225\260\347\273\204\344\270\255\346\225\260\345\255\227\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.go" "b/LeetCode/\345\211\221\346\214\207offer/day22_\344\275\215\350\277\220\347\256\227(\344\270\255\347\255\211)/\346\225\260\347\273\204\344\270\255\346\225\260\345\255\227\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.go" new file mode 100644 index 0000000..734a65f --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day22_\344\275\215\350\277\220\347\256\227(\344\270\255\347\255\211)/\346\225\260\347\273\204\344\270\255\346\225\260\345\255\227\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.go" @@ -0,0 +1,47 @@ +//题目链接:https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/ +// day22/31 +// 第 22 天主题为:位运算(中等) +// 包含两道题目: +// 剑指offer56.数组中数字出现的次数 +// 剑指offer56-II.数组中数字出现的次数 II +package main + +//本题最通俗的解法是哈希表记录每个数字出现的次数,不符合题目要求空间复杂度,位运算解法可将空间复杂度从 O(n) 降低至 O(1) +//解题思路:分组异或 +//先考虑一种简单的情况,一个整形数组 nums 中除一个数字出现一次之外,其余数字都出现了两次,找出这个数字。 +//很容易想到用 异或 来解决。两个相同的数字进行异或操作结果为 0。对 nums 进行一次遍历,xor 变量初始化为 0 +//与 nums 中每个数字进行异或操作,相同的数字会两两抵消,最后 xor 就与仅出现一次的数字值相同。 +// +//回到本题,有两个出现一次的数字,设为 a 和 b,如果我们使用相同的方法对 nums 进行异或,最终得到的结果 xor 会是 a 与 b 异或的结果。 +//xor 必定不等于 0,因为若 xor 等于 0,说明 a==b,与条件相悖。 + +//我们可以根据 xor 的二进制中任意不等于 0 的位进行分组,将 a 和 b 分到不同的组中,然后对两组数字分别进行异或,取出 a 和 b。 +//假设 xor 的第二位为 1,那我们就取 x = 10,对 nums 进行遍历,记当前数字为 num,根据 num&x==0和 num&x!=0 分组, +//a 和 b 在各自组中异或得到。 + +// 简单一句话描述: +//先对所有数字进行异或操作,得到两个只出现一次的数字进行异或的结果,在该结果中找到任意为 1 的位, +//根据该位对所有数字进行分组,在每个组内进行异或操作,即可得到两个数字。 +func singleNumbers(nums []int) []int { + xor := 0 + for _,num := range nums{ + xor ^= num + } + x := 1 + for { + if xor & x == 0{ + x <<= 1 + } else { + break + } + } + a,b := 0,0 + for _,num := range nums{ + if num & x == 0{ + a ^= num + } else { + b ^= num + } + } + return []int{a,b} +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day22_\344\275\215\350\277\220\347\256\227(\344\270\255\347\255\211)/\346\225\260\347\273\204\344\270\255\346\225\260\345\255\227\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260II.go" "b/LeetCode/\345\211\221\346\214\207offer/day22_\344\275\215\350\277\220\347\256\227(\344\270\255\347\255\211)/\346\225\260\347\273\204\344\270\255\346\225\260\345\255\227\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260II.go" new file mode 100644 index 0000000..ecccc49 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day22_\344\275\215\350\277\220\347\256\227(\344\270\255\347\255\211)/\346\225\260\347\273\204\344\270\255\346\225\260\345\255\227\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260II.go" @@ -0,0 +1,26 @@ +//题目链接:https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/?envType=study-plan&id=lcof +package main + +//本题最通俗的解法是哈希表记录每个数字出现的次数,我们还是用位运算进行优化,将空间复杂度从 O(n) 降低至 O(1) +// +//解题思路:依次确定二进制位 +//记数组中出现一次的数字为 res,题目给定数组中整数范围为 1~2^31-1,我们可以从最低位开始,依次确定 res 当前位 是 0 还是 1 。 +//在每一位中,我们遍历数组所有元素,统计当前位为 1 的个数 num,若 num 对 3 取余 为 0,说明 res 当前位为 0,否则为 1 。 + +func singleNumber(nums []int) int { + n := len(nums) + res := 0 + for i:=0;i<32;i++{ + num := 0 + bit := 1<=0;i--{ + suffix[i] = suffix[i+1] * a[i+1] + } + for i:=0;i=0;i--{ + temp *= a[i+1] + res[i] *= temp + } + return res +} diff --git "a/LeetCode/\345\211\221\346\214\207offer/day24_\346\225\260\345\255\246(\344\270\255\347\255\211)/\345\211\252\347\273\263\345\255\220.go" "b/LeetCode/\345\211\221\346\214\207offer/day24_\346\225\260\345\255\246(\344\270\255\347\255\211)/\345\211\252\347\273\263\345\255\220.go" new file mode 100644 index 0000000..d847e1d --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day24_\346\225\260\345\255\246(\344\270\255\347\255\211)/\345\211\252\347\273\263\345\255\220.go" @@ -0,0 +1,58 @@ +//题目链接:https://leetcode.cn/problems/jian-sheng-zi-lcof/?envType=study-plan&id=lcof +// day24/31 +// 第 24 天主题为:数学(中等) +// 包含三道题目: +// 剑指offer14-I.剪绳子 +// 剑指offer57-II.和为s的连续正数序列 +// 剑指offer62.圆圈中最后剩下的数字 + +package main + +import "math" + +//先上传统的动态规划解法 +// +//1. 确定dp数组大小及下标含义:dp[i] 代表长度为 i 的绳子切割后的最大乘积,长度为 n+1 +//2. dp 数组初始化:dp[1]=1,dp[2]=2,dp[3]=3,需要注意的是,该初始值只针对 n>=4 的情况,n<4的情况,我们会单独处理 +//3. 状态转移方程:dp[i] = max(dp[i] * dp[i-j]),j 从 1 遍历到 i-1,这里我们可以使用一个小技巧,j 从 1 遍历至 n/2 即可,因为 1*6 = 6 * 1 +// +//再说下 n<4 的情况,题目给定 n>=2,当 n<4 时,返回 n-1 即可。 + +func cuttingRope(n int) int { + if n < 4{ + return n-1 + } + dp := make([]int,n+1) + dp[1],dp[2],dp[3] = 1,2,3 + for i:=4;i<=n;i++{ + for j:=1;j<=i/2;j++{ + dp[i] = max(dp[i],dp[j]*dp[i-j]) + } + } + return dp[n] +} + +func max(x,y int) int{ + if x > y{ + return x + } + return y +} + +//这道题还有一种数学的解法,时间和空间复杂度都超过dp +// +//公式我在纸上推导,拍照放在了同一文件夹中,剪绳子.jpg + +func cuttingRope_2(n int) int { + if n <= 3{ + return n-1 + } + a,b := n/3,n%3 + if b == 0{ + return int(math.Pow(3,float64(a))) + } else if b == 1{ + return int(math.Pow(3,float64(a-1)) * 4) + } else { + return int(math.Pow(3,float64(a)) * 2) + } +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day24_\346\225\260\345\255\246(\344\270\255\347\255\211)/\345\211\252\347\273\263\345\255\220.jpg" "b/LeetCode/\345\211\221\346\214\207offer/day24_\346\225\260\345\255\246(\344\270\255\347\255\211)/\345\211\252\347\273\263\345\255\220.jpg" new file mode 100644 index 0000000..cc42284 Binary files /dev/null and "b/LeetCode/\345\211\221\346\214\207offer/day24_\346\225\260\345\255\246(\344\270\255\347\255\211)/\345\211\252\347\273\263\345\255\220.jpg" differ diff --git "a/LeetCode/\345\211\221\346\214\207offer/day24_\346\225\260\345\255\246(\344\270\255\347\255\211)/\345\222\214\344\270\272s\347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227.go" "b/LeetCode/\345\211\221\346\214\207offer/day24_\346\225\260\345\255\246(\344\270\255\347\255\211)/\345\222\214\344\270\272s\347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227.go" new file mode 100644 index 0000000..4b9b221 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day24_\346\225\260\345\255\246(\344\270\255\347\255\211)/\345\222\214\344\270\272s\347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227.go" @@ -0,0 +1,42 @@ +//题目链接:https://leetcode.cn/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/?envType=study-plan&id=lcof + +package main + +//解题思路:滑动窗口 +// +//设连续正整数序列的左边界与右边界分别为 left 和 right,构建滑动窗口向右滑动。 +//循环中,每轮判断滑动窗口内元素和与 target 的大小关系 +//- 若相等则记录结果,然后移动左指针 +//- 若 大于 target 则移动左指针 +//- 若小于 target 则移动右指针 +// +//每次移动指针的同时,更新滑动窗口内元素和 sum +//因为序列至少由两个数字组成,所以左指针边界为 [1,target/2] +func findContinuousSequence(target int) [][]int { + // left,right 为滑动窗口左右指针 + // sum 动态记录窗口元素和 + // 窗口至少含有两个数 + left, right, sum := 1, 2,3 + res := make([][]int, 0) + // 序列至少右两个元素组成,所以左边界只需遍历到 target/2 + for left <= target>>1 { + if sum < target { + // move right cursor and increase sum + right++ + sum += right + } else if sum > target { + // move left cursor and reduce sum + sum -= left + left++ + } else { + nums := make([]int, 0) + for i := left; i <= right; i++ { + nums = append(nums, i) + } + res = append(res,nums) + sum -= left + left++ + } + } + return res +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day24_\346\225\260\345\255\246(\344\270\255\347\255\211)/\345\234\206\345\234\210\344\270\255\346\234\200\345\220\216\345\211\251\344\270\213\347\232\204\346\225\260\345\255\227.go" "b/LeetCode/\345\211\221\346\214\207offer/day24_\346\225\260\345\255\246(\344\270\255\347\255\211)/\345\234\206\345\234\210\344\270\255\346\234\200\345\220\216\345\211\251\344\270\213\347\232\204\346\225\260\345\255\227.go" new file mode 100644 index 0000000..826a2d1 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day24_\346\225\260\345\255\246(\344\270\255\347\255\211)/\345\234\206\345\234\210\344\270\255\346\234\200\345\220\216\345\211\251\344\270\213\347\232\204\346\225\260\345\255\227.go" @@ -0,0 +1,54 @@ +//题目链接:https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/?envType=study-plan&id=lcof + +package main + +//约瑟夫环问题,模拟整个删除过程最直观,这里我用数组进行模拟,首先构建长度为 n 的模拟数组 nums, +//元素值分别为 0 ~ n-1,start 为每次循环中数组第一个元素下标,初始化为 0,之后开始模拟 +// +//先获取当前数组的长度 clen,删除第 m 个数字,m 可能大于等于 n,我们将 m-1 对 n 取余, 直接得到要删除的元素下标, +//考虑到初始元素下标不一定为 0,最终待删除的元素下表 loc_del = (m-1+start) % clen, +//将删除 该元素后的数组重新赋值给 nums,start 更新为 loc_del,开启下一次循环,直至数组长度为 1, +//可得到圆圈剩下的最后一个数组,返回该元素即可。 + +func lastRemaining(n int, m int) int { + // 每次循环中数组的第一个元素下标 + start := 0 + // 构建 nums 数组 + nums := []int{} + for i:= 0;i 1{ + clen := len(nums) + loc_del := (m-1+start) % clen + nums = append(nums[:loc_del],nums[loc_del+1:]...) + start = loc_del + } + return nums[0] +} + +//提交后会超时,个人感觉时间是浪费在数组的拼接上。 +// +//下述解法参考自官方题解 +//现在我们建模 n 个数字删除 第 m 个元素的问题为 f(n,m),f(n,m) 的值为最后剩余元素值(同元素下标) +//那 f(1,m) 的答案是一定的,数组只存在一个元素,f(1,m) = 0,我们可以试想下,可不可以通过 f(1,n) 推出 f(n,m) 的值。 +//进一步来说,如果能通过 f(n-1,m) 推出 f(n,m),那就一定能从 f(1,n) 推出 f(n,m) +//因为 f(n,m) 要删除第 m%n 个元素后,长度就变成了 n-1,那自然而然就与 f(n-1,m) 扯上了关联,它们的区别在哪里呢? +//对应元素下标不同! 只要我们把它们的下标相对应起来,那我们就可以求解改题目。 +//下标不同在哪里呢?起始位置,f(n-1,m) 起始元素下标为 0,而 f(n,m) 删除一个元素后的起始元素下标为 m%n, +//我们把它们对应起来,就可以通过 f(n-1,m) 求得 f(n,m) +//同理,由于 f(1,m) 值是固定的,我们可以从 f(1,m) 递归到 f(n,m) + +func lastRemaining_2(n int, m int) int { + // n=1时,剩余元素下标为0 + res := 0 + // 从前向后推,求n=2,3,...,n时,剩余元素下标 + // i 为数组长度 + for i:= 2;i <= n;i++{ + // 将i-1数组元素下标与长度为i的数组下标对应起来 + // 求得长度为 i 的数组剩余元素 + res = (res + m) % i + } + return res +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day25_\346\250\241\346\213\237(\344\270\255\347\255\211)/\346\240\210\347\232\204\345\216\213\345\205\245\343\200\201\345\274\271\345\207\272\345\272\217\345\210\227.go" "b/LeetCode/\345\211\221\346\214\207offer/day25_\346\250\241\346\213\237(\344\270\255\347\255\211)/\346\240\210\347\232\204\345\216\213\345\205\245\343\200\201\345\274\271\345\207\272\345\272\217\345\210\227.go" new file mode 100644 index 0000000..c38056f --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day25_\346\250\241\346\213\237(\344\270\255\347\255\211)/\346\240\210\347\232\204\345\216\213\345\205\245\343\200\201\345\274\271\345\207\272\345\272\217\345\210\227.go" @@ -0,0 +1,25 @@ +//题目链接:https://leetcode.cn/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/?envType=study-plan&id=lcof +// day25/31 +// 第 25 天主题为:模拟(中等) +// 包含两道题目: +// 剑指offer29.顺时针打印矩阵 +// 剑指offer31.栈的压入、弹出序列 +package main + +//简单题,模拟该过程,声明一个栈 stack 和 变量 i(i 符合条件的出栈元素数量),遍历压入序列 pushed,依次入栈,每次入栈结束后 +//判断栈顶元素是否和出栈序列 popped[i] 相等 +//若相等,出栈,i++,再次判断栈顶元素和 popped[i]是否相等...直至栈长度为0或者栈顶元素和popped[i]不相等。 +// +//遍历完成后,若 i = len(popped),说明所有元素出栈成功,返回 true。判断条件改为 len(stack)=0 也可以,道理相同 +func validateStackSequences(pushed []int, popped []int) bool { + stack := []int{} + i := 0 + for _,num := range(pushed){ + stack = append(stack,num) + for len(stack) > 0 && stack[len(stack)-1] == popped[i]{ + stack = stack[:len(stack)-1] + i++ + } + } + return len(stack) == 0 +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day25_\346\250\241\346\213\237(\344\270\255\347\255\211)/\351\241\272\346\227\266\351\222\210\346\211\223\345\215\260\347\237\251\351\230\265.go" "b/LeetCode/\345\211\221\346\214\207offer/day25_\346\250\241\346\213\237(\344\270\255\347\255\211)/\351\241\272\346\227\266\351\222\210\346\211\223\345\215\260\347\237\251\351\230\265.go" new file mode 100644 index 0000000..6a639d8 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day25_\346\250\241\346\213\237(\344\270\255\347\255\211)/\351\241\272\346\227\266\351\222\210\346\211\223\345\215\260\347\237\251\351\230\265.go" @@ -0,0 +1,84 @@ +//题目链接:https://leetcode.cn/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/?envType=study-plan&id=lcof +package main + +//简单模拟,这种题目,个人感觉比较看经验,做过类似题目的话,很快可以模拟出来,没做过或者没思路的话,就有点无从下手或者不知道如何用代码实现自己的想法。 +//需要考虑三个问题:移动方向、边界 和 结束条件。 +// +//1. 移动方向:很明显为 右、下、左、上 这样的循环,我们可以用一个二维数组代表四个方向,每移动到边界,更改方向; +//2. 边界:边界问题是本题的重点,因为边界是随着遍历在变化的,打印矩阵的过程中,边界逐渐变小。规则是:如果当前行(列)遍历结束后,将次行(列)的边界向内移动一格; +//3. 结束条件:当矩阵所有位置都被打印过,即遍历数组的长度等于矩阵元素个数时,结束遍历。 +// +//代码中,x和y代表当前遍历到的元素所在位置索引,dirs代表移动的四个方向(上、右、下、左是一个循环), +//dir[cur_d]代表下一次移动的方向,结束条件是 res 数组元素个数等于矩阵元素个数。 +func spiralOrder(matrix [][]int) []int { + if len(matrix) == 0 || len(matrix[0]) == 0{ + return []int{} + } + m,n := len(matrix),len(matrix[0]) + directions := [][]int{{0,1},{1,0},{0,-1},{-1,0}} + cur_dir := 0 + top,right,bottom,left := 0,n-1,m-1,0 + res := make([]int,0) + x,y := 0,0 + for len(res) < m*n{ + res = append(res,matrix[x][y]) + if cur_dir == 0 && y == right{ + cur_dir ++ + top ++ + } else if cur_dir == 1 && x == bottom{ + cur_dir ++ + right -- + } else if cur_dir == 2 && y == left{ + cur_dir ++ + bottom -- + } else if cur_dir == 3 && x == top{ + cur_dir ++ + left ++ + } + cur_dir %= 4 + x += directions[cur_dir][0] + y += directions[cur_dir][1] + } + return res +} + +//也可以按层模拟,将矩阵分为若干层,先打印最外层元素,再打印次外层的元素,直到输出完最内层的元素。 +// +//对于每层,从左上方开始以顺时针的顺序遍历当前层所有元素。还是要着重考虑边界的问题,假设当前层左上角元素索引为(top,left),右下角元素索引为(bottom,right). +// +//- 从左到右遍历上侧元素,依次为 (top,left) 到 (top,right)。 +//- 从上到下遍历右侧元素,依次为 (top+1,right) 到 (bottom,right)。 +//- 如果 left=left;i--{ + res = append(res,matrix[bottom][i]) + } + for i := bottom-1;i>top;i--{ + res = append(res,matrix[i][left]) + } + } + top ++ + right -- + bottom -- + left ++ + } + return res +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day26_\345\255\227\347\254\246\344\270\262(\344\270\255\347\255\211)/\346\212\212\345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\210\220\346\225\264\346\225\260.go" "b/LeetCode/\345\211\221\346\214\207offer/day26_\345\255\227\347\254\246\344\270\262(\344\270\255\347\255\211)/\346\212\212\345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\210\220\346\225\264\346\225\260.go" new file mode 100644 index 0000000..9d37282 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day26_\345\255\227\347\254\246\344\270\262(\344\270\255\347\255\211)/\346\212\212\345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\210\220\346\225\264\346\225\260.go" @@ -0,0 +1,71 @@ +//题目链接:https://leetcode.cn/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/?envType=study-plan&id=lcof +// day26/31 +// 第 26 天主题为:字符串(中等) +// 包含两道题目: +// 剑指offer20.表示数值的字符串 +// 剑指offer67.把字符串转换成整数 + +package main + +// 模拟即可 +// 官方题解的解法是:确定有限状态自动机,这块我不太懂,有兴趣的同学自行了解哈 +func isNumber(s string) bool { + n := len(s) + // 若字符串长度为0,无法表示数值 + if n == 0{ + return false + } + // 从下标0开始遍历 + index := 0 + // 读取字符开头处的若干空格 + for index < n && s[index] == ' '{ + index ++ + } + // 读取整数部分,,numeric代表读取到index是否为数值 + // index 持续向前推进 + numeric := ScanInteger(s,&index) + // 若下一个字符为'.',说明为小数,读取小数部分 + if index < n && s[index] == '.'{ + index ++ + // 这里用逻辑或的原因: + // 1. '.'前若无数字,则'.'后至少需要一位数字 + // 对应左表达式为 true,右表达式为 false + // 2.'.'前有数字,则后方可以有也可以没有数字 + // 对应左true右true 和 左false右true + numeric = ScanUnsignedInteger(s,&index) || numeric + } + // 若出现e,后面需要跟一个整数,原理同上 + if index < n && (s[index] == 'e' || s[index] == 'E'){ + index ++ + numeric = numeric && ScanInteger(s,&index) + } + // 读取字符串结尾处的若干空格 + for index < n && s[index] == ' '{ + index ++ + } + // 若 numeric 为 true,且读取到字符串末尾,说明为数值 + return numeric && index == len(s) +} + +// 读取字符串s从下标index开始的有符号整数 +func ScanInteger(s string,index *int) bool{ + // 先读取正负号,若无符号,默认正数 + if *index < len(s) && (s[*index] == '+' || s[*index] == '-'){ + *index ++ + } + // 读取符号后,读取无符号整数 + return ScanUnsignedInteger(s,index) +} + +// 读取字符串s从下标index开始的无符号整数 +// 若未读取到无符号整数,返回false,否则,返回true +func ScanUnsignedInteger(s string,index *int) bool { + start_of_integer := *index + // 根据ASCII码判断是否为数字字符 + // 若读取到非数字字符,直接跳出即可 + for *index < len(s) && s[*index] >= '0' && s[*index] <= '9'{ + *index ++ + } + // 若*index大于读取前的值,说明有整数被读取到 + return *index > start_of_integer +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day26_\345\255\227\347\254\246\344\270\262(\344\270\255\347\255\211)/\350\241\250\347\244\272\346\225\260\345\200\274\347\232\204\345\255\227\347\254\246\344\270\262.go" "b/LeetCode/\345\211\221\346\214\207offer/day26_\345\255\227\347\254\246\344\270\262(\344\270\255\347\255\211)/\350\241\250\347\244\272\346\225\260\345\200\274\347\232\204\345\255\227\347\254\246\344\270\262.go" new file mode 100644 index 0000000..c0eef3c --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day26_\345\255\227\347\254\246\344\270\262(\344\270\255\347\255\211)/\350\241\250\347\244\272\346\225\260\345\200\274\347\232\204\345\255\227\347\254\246\344\270\262.go" @@ -0,0 +1,85 @@ +//题目链接:https://leetcode.cn/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/?envType=study-plan&id=lcof +package main + +//这里我想先解释一下为什么负数范围比正数范围大 1 . +//因为对有符号数,在计算机中表达时,最高位约定为符号位,符号位为 0 时代表正数,符号位为 1 时代表负数。 +//对 int32,真正表达数值的部分就是剩下的 31 位,正数范围很容易理解为 1~2^31-1(2147483647) +//有一个特殊的值,就是 0 值,对符号位 1 和 0 的时候,都是 0,用两个形式表达同一个数无疑是浪费的, +//所以,我们就规定,符号位为 1 时的全 0,表达 -2147483648,这也是负数表示的范围比正数多1的原因。 +//综上,对于任意位的,无论是8位,16位,32位甚至64位的整数类型表示范围的计算公式为: +//如总位数为n位,那么有符号数的范围为:-2^(n-1) ~ 2^(n-1)-1,无符号数的表示范围为:0~2^n-1 + + +//此题解参考自链接:https://leetcode.cn/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/submissions/ + +//首部空格: 删除之即可; +//符号位: 三种情况,即 ''+'' , ''-'' , ''无符号" ;新建一个变量保存符号位,返回前判断正负即可。 +//非数字字符: 遇到首个非数字的字符时,应立即返回。 +//数字字符: +//字符转数字: “此数字的 ASCII 码” 与 “ 00 的 ASCII 码” 相减即可; +//数字拼接: 若从左向右遍历数字,设当前位字符为 c ,当前位数字为 x ,数字结果为 res,则数字拼接公式为:res = 10 * res + x +//x = ascii(c) - ascii('0') +// +//接下来是比较关键的数字越界处理: +//题目要求返回的数值范围应在 [-2^{31}, 2^{31} - 1],因此需要考虑数字越界问题。 +//而由于题目指出 环境只能存储 32 位大小的有符号整数 ,因此判断数字越界时,要始终保持 res 在 int 类型的取值范围内。 +// +//在每轮数字拼接前,判断 res 在此轮拼接后是否超过 2147483647,若超过则加上符号位直接返回。 +//设数字拼接边界 boundary = 2147483647 // 10 = 214748364,则以下两种情况越界: +// +//1. res > boundry, 拼接后越界 +//2. res = boundry && x > 7,拼接后越界 +// +//这种处理方式非常巧妙,> ‘7’ 这个操作看似只考虑到了最大值,其实也考虑了最小值,只要大于,直接返回即可。 +const ( + MAX_INT = 1 << 31 - 1 + MIN_INT = -1 * (1 << 31) +) + +func strToInt(str string) int { + // index 为 str 遍历到的位置 + index := 0 + // 为防止读取空格出现越界panic,需要提前处理str为空字符或全空格的情况 + if str == ""{ + return 0 + } + for i:=0;i '9'{ + break + } + // 预计出现越界情况,根据符号位返回最大或最小值 + if res > boundary || res == boundary && s > '7'{ + if sign == 1{ + return MAX_INT + } else { + return MIN_INT + } + } + res = res * 10 + int(s - '0') + + } + return sign * res +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day27_\346\240\210\344\270\216\351\230\237\345\210\227(\345\233\260\351\232\276)/\346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\346\234\200\345\244\247\345\200\274.go" "b/LeetCode/\345\211\221\346\214\207offer/day27_\346\240\210\344\270\216\351\230\237\345\210\227(\345\233\260\351\232\276)/\346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\346\234\200\345\244\247\345\200\274.go" new file mode 100644 index 0000000..594636a --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day27_\346\240\210\344\270\216\351\230\237\345\210\227(\345\233\260\351\232\276)/\346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\346\234\200\345\244\247\345\200\274.go" @@ -0,0 +1,72 @@ +// 题目链接:https://leetcode.cn/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/?envType=study-plan&id=lcof +// day27/31 +// 第 27 天主题为:栈与队列(困难) +// 包含两道题目: +// 剑指offer59-I.滑动窗口的最大值 +// 剑指offer59-II.队列的最大值 +package main + +//可以先去暴力解决,对每个滑动窗口,遍历所有元素得到最大值。 +//对长度为 n 的数组 nums,窗口数量为 n-k+1,暴力解法时间复杂度为 O((n-k+1)*k) = O(nk) +func maxSlidingWindow(nums []int, k int) []int { + n := len(nums) + if n == 0 || k == 0{ + return []int{} + } + res := []int{} + for i := 0;i < n-k+1;i++{ + res = append(res,max(nums[i:i+k])) + } + return res +} + + +func max(sli []int) (res int){ + res = sli[0] + for _,i := range sli{ + if i > res{ + res = i + } + } + return +} + +//法二:单调队列 +//然后想一想,暴力没有用到题目中哪些条件,可以针对这些条件去做改进 +//相邻的两个窗口,有重叠元素 k-1 个,这 k-1 个元素中的最大值对两个窗口来说是相同的,这就是我们改进的方向 +// +//设想一下,一个窗口中两个元素下标分别为 i 和 j,其中 i 在 j 的左侧(i= nums[j],nums[i] 出队后,nums[j] 是有可能成为队列最大值的,nums[j] 需要保留 +// +//因此,我们可以使用一个队列来存储还没有被删除的元素。在队列中,这些元素的值是单调递减的 +//当窗口向右滑动时,我们需要把一个新的元素放入队列中,为了保持队列的性质,我们需要不断将新元素与队列队尾元素进行比较, +//若新元素较大,则队尾元素出队,不断进行此操作,直至队列为空 或 新的元素小于等于队尾的元素 +// +//由于队列的元素是严格单调递减的,且队列中元素属于该窗口,所以队首元素就是该窗口的最大值 +//此时,我们需要考虑最后一个问题,若当前窗口的最大值为窗口最左侧元素,那进入下一个窗口前队列中该元素应该出队,因为该元素并不属于下一个窗口 +// +//该队列元素单调递减,满足这种单调性的队列一般称作 单调队列。 +func maxSlidingWindow_2(nums []int, k int) []int { + n := len(nums) + monoQ := []int{} + res := make([]int,0,n-k+1) + for i:=0;i 0 && nums[i] > monoQ[len(monoQ)-1]{ + monoQ = monoQ[:len(monoQ)-1] + } + monoQ = append(monoQ,nums[i]) + // 若单调队列最大值非窗口元素 + // 将该元素出队 + if i >= k && monoQ[0] == nums[i-k]{ + monoQ = monoQ[1:] + } + // i=k-1时,窗口元素数量达到 k + // 开始向 res 数组添加窗口最大值 + if i >= k-1{ + res = append(res,monoQ[0]) + } + } + return res +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day27_\346\240\210\344\270\216\351\230\237\345\210\227(\345\233\260\351\232\276)/\351\230\237\345\210\227\347\232\204\346\234\200\345\244\247\345\200\274.go" "b/LeetCode/\345\211\221\346\214\207offer/day27_\346\240\210\344\270\216\351\230\237\345\210\227(\345\233\260\351\232\276)/\351\230\237\345\210\227\347\232\204\346\234\200\345\244\247\345\200\274.go" new file mode 100644 index 0000000..9a934a3 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day27_\346\240\210\344\270\216\351\230\237\345\210\227(\345\233\260\351\232\276)/\351\230\237\345\210\227\347\232\204\346\234\200\345\244\247\345\200\274.go" @@ -0,0 +1,61 @@ +//题目链接:https://leetcode.cn/problems/dui-lie-de-zui-da-zhi-lcof/?envType=study-plan&id=lcof +package main + +//有了上一题的基础后,这一题就简简单单了 +//和第一题的解析类似,这里我们也可以使用单调队列。把队列看作是第一题的窗口,只不过滑动规则有所差异, +//pop 出队时,窗口的左指针右移,并对我们实现的单调队列进行相应操作,求队列最大值时,只需返回单调队列队首元素即可。 +type MaxQueue struct { + // 存储队列 + q []int + // 单调队列 + monoQ []int +} + +// 构造函数 +func Constructor() MaxQueue { + return MaxQueue{[]int{},[]int{}} +} + +// 取最大值,若队列长度为 0,返回-1,否则返回单调队列队首元素 +func (this *MaxQueue) Max_value() int { + if len(this.q) == 0{ + return -1 + } + return this.monoQ[0] +} + +// 入队,存储队列直接入队 +// 单调队列需要判断队尾元素与新元素大小关系 +// 若新元素大于队尾元素,出队 +// 直至单调队列长度为 0或者 新元素值 小于等于 队尾元素 +func (this *MaxQueue) Push_back(value int) { + this.q = append(this.q,value) + for len(this.monoQ)>0 && value>this.monoQ[len(this.monoQ)-1]{ + this.monoQ = this.monoQ[:len(this.monoQ)-1] + } + this.monoQ = append(this.monoQ,value) +} + +// 出队,先判断存储队列长度,若为 0,返回-1 +// 获取存储队列队首元素后,存储队列队首元素出队 +// 若该元素等于单调队列队首元素,则单调队列队首元素出队 +func (this *MaxQueue) Pop_front() int { + if len(this.q) == 0{ + return -1 + } + res := this.q[0] + this.q = this.q[1:] + if res == this.monoQ[0]{ + this.monoQ = this.monoQ[1:] + } + return res +} + + +/** + * Your MaxQueue object will be instantiated and called as such: + * obj := Constructor(); + * param_1 := obj.Max_value(); + * obj.Push_back(value); + * param_3 := obj.Pop_front(); + */ \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day28_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\345\233\260\351\232\276)/\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.go" "b/LeetCode/\345\211\221\346\214\207offer/day28_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\345\233\260\351\232\276)/\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.go" new file mode 100644 index 0000000..f24e5e3 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day28_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\345\233\260\351\232\276)/\345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.go" @@ -0,0 +1,84 @@ +// 题目链接:https://leetcode.cn/problems/zi-fu-chuan-de-pai-lie-lcof/?envType=study-plan&id=lcof +// day28/31 +// 第 28 天主题为:搜索与回溯算法(困难) +// 包含两道题目: +// 剑指offer37.序列化二叉树 +// 剑指offer38.字符串的排列 +package main + +import ( + "fmt" + "sort" +) + +//全排列问题,如果题目所给字符不包含重复字符,用下面简单回溯即可: +func permutation(s string) []string { + res := []string{} + n := len(s) + used := make([]bool,n) + var backtrace func(path string) + backtrace = func(path string){ + if len(path) == n{ + res = append(res,path) + return + } + for i:=0;i0 && runeS[i-1]==runeS[i] && !used[i-1]{ + continue + } + used[i] = true + path = append(path,runeS[i]) + backtrace(path) + path = path[:len(path)-1] + used[i] = false + } + } + backtrace([]byte{}) + fmt.Println(runeS) + return res +} + +//本题解题思路与 LeetCode 48:全排列 II 完全一致,有兴趣可以再去做做练习一下 \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day28_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\345\233\260\351\232\276)/\345\272\217\345\210\227\345\214\226\344\272\214\345\217\211\346\240\221.go" "b/LeetCode/\345\211\221\346\214\207offer/day28_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\345\233\260\351\232\276)/\345\272\217\345\210\227\345\214\226\344\272\214\345\217\211\346\240\221.go" new file mode 100644 index 0000000..d60c500 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day28_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\345\233\260\351\232\276)/\345\272\217\345\210\227\345\214\226\344\272\214\345\217\211\346\240\221.go" @@ -0,0 +1,84 @@ +//题目链接:https://leetcode.cn/problems/xu-lie-hua-er-cha-shu-lcof/?envType=study-plan&id=lcof +package main + +import ( + "strconv" + "strings" +) + +//本题接参考自官方题解:https://leetcode.cn/problems/serialize-and-deserialize-binary-tree/solution/er-cha-shu-de-xu-lie-hua-yu-fan-xu-lie-hua-by-le-2/ +// +//二叉树的序列化本质上是对其值进行编码,更重要的是对其结构进行编码。可以遍历树来完成上述任务。 +//众所周知,我们一般有两个策略:广度优先搜索和深度优先搜索。 +//广度优先搜索可以按照层次的顺序从上到下遍历所有的节点 +// +//深度优先搜索可以从一个根开始,一直延伸到某个叶,然后回到根,到达另一个分支。 +//根据根节点、左节点和右节点之间的相对顺序,可以进一步将深度优先搜索策略区分为:先序遍历、中序遍历 以及 后序遍历。 +// +//对题目所给例子进行举例,最终序列化字符串是 1,2,3,None,None,4,None,None,5,None,None,。 +//其中,None,None 用来标记缺少左右子节点,这是我们在序列化期间保存树结构的方式。 +// +//即我们可以先序遍历这颗二叉树,遇到空子树的时候序列化成 None,否则继续递归序列化。那么我们如何反序列化呢? +//首先我们需要根据 , 把原先的序列分割开来得到先序遍历的元素列表,然后从左向右遍历这个序列: +//- 如果当前的元素为 None,则当前为空树 +//- 否则先解析这棵树的左子树,再解析它的右子树 + +//Definition for a binary tree node. +type TreeNode struct { + Val int + Left *TreeNode + Right *TreeNode +} + + +type Codec struct { + +} + +func Constructor() Codec { + return Codec{} +} + +// Serializes a tree to a single string. +func (this *Codec) serialize(root *TreeNode) string { + var sb strings.Builder + var preorder func(root *TreeNode) + preorder = func(root *TreeNode) { + if root == nil{ + sb.WriteString("nil,") + return + } + sb.WriteString(strconv.Itoa(root.Val)) + sb.WriteString(",") + preorder(root.Left) + preorder(root.Right) + } + preorder(root) + return sb.String() +} + +// Deserializes your encoded data to tree. +func (this *Codec) deserialize(data string) *TreeNode { + s := strings.Split(data,",") + var build func() *TreeNode + build = func() *TreeNode { + if s[0] == "nil"{ + s = s[1:] + return nil + } + val,_ := strconv.Atoi(s[0]) + s = s[1:] + node := &TreeNode{val,build(),build()} + return node + } + return build() +} + + +/** + * Your Codec object will be instantiated and called as such: + * ser := Constructor(); + * deser := Constructor(); + * data := ser.serialize(root); + * ans := deser.deserialize(data); + */ \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day2_\351\223\276\350\241\250(\347\256\200\345\215\225)/\344\273\216\345\260\276\345\210\260\345\244\264\346\211\223\345\215\260\351\223\276\350\241\250.go" "b/LeetCode/\345\211\221\346\214\207offer/day2_\351\223\276\350\241\250(\347\256\200\345\215\225)/\344\273\216\345\260\276\345\210\260\345\244\264\346\211\223\345\215\260\351\223\276\350\241\250.go" new file mode 100644 index 0000000..39e0423 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day2_\351\223\276\350\241\250(\347\256\200\345\215\225)/\344\273\216\345\260\276\345\210\260\345\244\264\346\211\223\345\215\260\351\223\276\350\241\250.go" @@ -0,0 +1,54 @@ +// 题目链接:https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/?plan=lcof&plan_progress=klczit3 +// day2/31 +// 第二天主题为:链表(简单) +// 包含三道题目: +// 剑指offer06.从尾到头打印链表 +// 剑指offer30.反转链表 +// 剑指offer35.复杂链表的复制 + +// 解题思路:从尾到头打印链表,最后返回整数切片,也就是说,链表头节点的值为切片最后的元素 +// 而链表尾结点的值为切片第一个元素 +// 与链表的遍历结合,很容易想到先进后出的解题策略 +// 说到”先进后出“,那必然会用到栈,先用栈来解题 +// 从头到尾遍历链表,将节点依次入栈 +// 遍历结束后,在从栈顶逐个输出节点的值至整数切片即可 + +package main + + +//Definition for singly-linked list. +type ListNode struct { + Val int + Next *ListNode +} + + +func reversePrint(head *ListNode) []int { + stack := []*ListNode{} + res := []int{} + if head == nil{ + return []int{} + } + for head != nil{ + stack = append(stack,head) + head = head.Next + } + for len(stack) > 0{ + res = append(res,stack[len(stack)-1].Val) + stack = stack[:len(stack)-1] + } + return res +} + +// 法2:递归在本质上是一个栈结构,针对本题,我们也可以使用递归来实现 +// 容易想到,要实现反过来输出链表,每当我们访问到一个节点时,要将 +// 当前节点的值放在返回切片的末尾,先递归输出其之后的节点, +// 当访问到当前的节点为空节点时,返回空切片即可 +// 利用系统栈,实现了从尾到头打印链表 +// 为避免命名冲突,此函数名后添加了后缀 ”_recursion“ +func reversePrint_recursion(head *ListNode) []int { + if head == nil{ + return []int{} + } + return append(reversePrint(head.Next),head.Val) +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day2_\351\223\276\350\241\250(\347\256\200\345\215\225)/\345\217\215\350\275\254\351\223\276\350\241\250.go" "b/LeetCode/\345\211\221\346\214\207offer/day2_\351\223\276\350\241\250(\347\256\200\345\215\225)/\345\217\215\350\275\254\351\223\276\350\241\250.go" new file mode 100644 index 0000000..cd5e124 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day2_\351\223\276\350\241\250(\347\256\200\345\215\225)/\345\217\215\350\275\254\351\223\276\350\241\250.go" @@ -0,0 +1,59 @@ +// 题目链接:https://leetcode.cn/problems/fan-zhuan-lian-biao-lcof/ + +// 解题思路:本题最通俗的解法为迭代,从头到尾遍历链表,不断更改当前节点的Next域 +// 我们需要事先引入一个空节点,第一次迭代时,头结点指向pre,之后不断更新 +// 更改Next域前,要记录当前节点的Next域指向的节点,防止链表出现断裂 +// 做链表相关题目时,一定要谨防链表断裂的情况出现 +// 此题的另一个要注意的点是代码的鲁棒性 +// 表头指针为 null 或者整个链表只有一个节点时,我们要保证程序依旧能正常运行。 + +package main + +/** + * Definition for singly-linked list. + * type ListNode struct { + * Val int + * Next *ListNode + * } + */ + +// 关于Go语言中关于空节点的初始化需要格外注意,本题中对应pre的初始化 +// 我最初的写法是 pre:=*ListNode{},报错: indirect of leetcode.ListNode{} +// ListNode{}的间接无效,不确定该如何翻译 +// 若 pre 初始化为 nil,报错:use of untyped nil (solution.go) +// pre:=new(ListNode) 也是不可以的,最后输出的切片结果会多一个0元素 +// 正确写法为 var pre *ListNode,此时pre为空节点,无零值,值为nil +// 好吧,这块是我对GO理解存在问题,为什么 *ListNode{}不可以,我需要再思考一下 +func reverseList(head *ListNode) *ListNode { + var pre *ListNode + cur := head + for cur != nil{ + pnext := cur.Next + cur.Next = pre + pre,cur = cur,pnext + } + // 遍历结束后,cur指向nil节点,pre指向原先链表的尾结点 + return pre +} + +// 法2:递归 +// 理解:如果链表长度为2,结构为:a->b->nil 想要反转链表,可以用 +// a.Next.Next=a +// a.Next=nil +// return b +// 这三行代码实现,明白这个,那递归就好理解了 +// 假设链表长度大于2,当前正在处理b节点,b往后的节点已经完成反转 +// 我们希望b指向a,则 a.Next.Next=a +// 若当目前处理节点为空,或其Next域为空时,返回该节点,即新链表的头结点 +// 为避免命名冲突,本函数名后添加了后缀 ”_recursion“ +func reverseList_recrusion(head *ListNode) *ListNode { + if head == nil || head.Next == nil{ + return head + } + newhead := reverseList(head.Next) + head.Next.Next = head + // 虽然这行代码实质上只为原链表的头结点服务,但是仍然不可缺少 + // 若无下面这行代码,链表有可能会形成环 + head.Next = nil + return newhead +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day2_\351\223\276\350\241\250(\347\256\200\345\215\225)/\345\244\215\346\235\202\351\223\276\350\241\250\347\232\204\345\244\215\345\210\266.go" "b/LeetCode/\345\211\221\346\214\207offer/day2_\351\223\276\350\241\250(\347\256\200\345\215\225)/\345\244\215\346\235\202\351\223\276\350\241\250\347\232\204\345\244\215\345\210\266.go" new file mode 100644 index 0000000..1a9c33e --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day2_\351\223\276\350\241\250(\347\256\200\345\215\225)/\345\244\215\346\235\202\351\223\276\350\241\250\347\232\204\345\244\215\345\210\266.go" @@ -0,0 +1,100 @@ +//题目链接:https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof/?envType=study-plan&id=lcof + +// 感觉这道题题目描述地不够清晰,可以查看主站138题的题目描述 +// 刚看到这个题目我是有些困惑的,遍历原链表,然后逐个生成新节点不就可以了吗,思考过后,才发现不是这样的 +// 如果没有 random域,确实可以在遍历的同时创建节点,设当前遍历到的节点为 cur,对 Next 指向的节点 pnext +// 先创建Val相同,Next为空的节点next,然后将当前节点 cur 指向next,然后 cur=next,遍历完成后即可完成复制 +// 但本题中 Node 节点包含了 Random 域,Random 指向的节点位置是随机的,可能该节点还未创建,无法进行指向 +// 本题的难点就在于构建新链表节点的 Random 域。 +// 所以,我们需要进行两次遍历,第一次创建节点,给 Val 域复制,第二次遍历给 Next 域 和 Random 域 进行指向。 +// 还有一个问题,如何保存第一次遍历创建的节点呢?哈希表应该是第一个浮现在脑海中的数据结构,键为原链表节点,值为新创建的原节点对应的节点。 + +// 上面讲的有些啰嗦了,下面看具体实现,参考自题解: +// https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof/solution/jian-zhi-offer-35-fu-za-lian-biao-de-fu-zhi-ha-xi-/ +// 利用哈希表的查询特点,第一次遍历构建原链表节点和新链表节对应节点的键值对映射关系, +// 第二次遍历构建新链表各节点的 Next 与 Random 指向即可。 +//1. 若头结点head为空,直接返回 head +//2. 构建哈希表 record +//3. 复制链表,进行第一次遍历,构建新节点,Next和Random域均为空 +//4. 第二次遍历,所有节点已创建完成,根据record进行Next和Random域的指向 +//5. 返回新链表的头结点record[head] +package main + + +//Definition for a Node. +type Node struct { + Val int + Next *Node + Random *Node +} + + +func copyRandomList(head *Node) *Node { + if head == nil{ + return head + } + record := map[*Node]*Node{} + cur := head + for cur != nil{ + node := Node{cur.Val,nil,nil} + record[cur] = &node + cur = cur.Next + } + cur = head + for cur != nil{ + if cur.Next != nil{ + record[cur].Next = record[cur.Next] + } + if cur.Random != nil{ + record[cur].Random = record[cur.Random] + } + cur = cur.Next + } + return record[head] +} + + +// 上述解法为哈希表,是一种通俗的解法,下面看一种非常巧妙的解法 +// 法2:拼接+拆分 +// 这里建议大家多画图去理解这种解法 +// 考虑构建 原节点 1 -> 新节点 1 -> 原节点 2 -> 新节点 2 -> …… 的拼接链表 +// 如此便可在访问原节点的 random 指向节点的同时找到新对应新节点的 random 指向节点。 +//算法流程: +//1. 复制各节点,构建拼接链表:设原链表为 a->b->...,构建的拼接链表为:a->a'->b->b'->... +//2. 构建新链表各节点的random指向:当访问原节点cur的随机指向节点cur.random时,对应新节点cur.next的随机指向节点为cur.random.next 。 +//3. 拆分原/新链表:设置pre/cur分别指向原/新链表头节点,遍历执行pre.next=pre.next.next和cur.next=cur.next.next将两链表拆分开。 +//4. 返回新链表的头节点 res 即可。 + +// 为避免命名冲突,此函数名后添加了后缀 ”_2“ +func copyRandomList_2(head *Node) *Node { + if head == nil{ + return head + } + cur := head + for cur != nil{ + temp := &Node{cur.Val,nil,nil} + temp.Next = cur.Next + cur.Next = temp + cur = temp.Next + // 同 cur = cur.Next.Next + } + cur = head + for cur != nil{ + if cur.Random != nil{ + cur.Next.Random = cur.Random.Next + } + cur = cur.Next.Next + } + cur,res := head.Next,head.Next + pre := head + for cur.Next != nil{ + pre.Next = pre.Next.Next + cur.Next = cur.Next.Next + pre = pre.Next + cur = cur.Next + } + // 处理原链表尾结点 + pre.Next = nil + // 返回新链表头结点 + return res +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day3_\345\255\227\347\254\246\344\270\262(\347\256\200\345\215\225)/\345\267\246\346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.go" "b/LeetCode/\345\211\221\346\214\207offer/day3_\345\255\227\347\254\246\344\270\262(\347\256\200\345\215\225)/\345\267\246\346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.go" new file mode 100644 index 0000000..5a21d1f --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day3_\345\255\227\347\254\246\344\270\262(\347\256\200\345\215\225)/\345\267\246\346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.go" @@ -0,0 +1,89 @@ +// 题目链接:https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/?plan=lcof&plan_progress=klczit3 +// day3/31 +// 第 3 天主题为:字符串(简单) +// 包含两道题目: +// 剑指offer05.替换空格 +// 剑指offer58-II.左旋转字符串 + + +// 此题题解前三个方法参考自:https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/solution/mian-shi-ti-58-ii-zuo-xuan-zhuan-zi-fu-chuan-qie-p/ +// 解题思路:设字符串s的长度为n,首先,很容易想到,s左旋n位还是原本的字符串,我们只需要左旋k%n次即可。 +// 此题解法较多,请耐心观看 +package main + +// 方法1:也是最通俗的解法,字符串切片进行拼接 +func reverseLeftWords(s string, n int) string { + n = n % len(s) + return s[n:] + s[:n] +} + +// 如果面试不允许对字符串进行切片,那我们可以对列表进行遍历,然后进行拼接,记为方法2: +// 先遍历从下标n到末尾的字符串元素,加入返回的rune切片res,然后遍历下标0-n,添加至res。最后返回将res转换为string返回即可 +// 为避免命名冲突,此函数名添加后缀 “_2" +func reverseLeftWords_2(s string, n int) string { + res := make([]rune,len(s)) + n = n % len(s) + k := 0 + for i:=n;i=0;i--{ + if s[i]==' '{ + res[j-2] = '%' + res[j-1] = '2' + res[j] = '0' + j -= 3 + } else { + res[j] = rune(s[i]) + j -- + } + } + return string(res) +} + +// 若遍历方向改为从前往后,同理 +// 为避免命名冲突,此函数名添加后缀 “_2" +func replaceSpace_2(s string) string { + nums := 0 + for _,x := range s{ + if x == ' '{ + nums ++ + } + } + m := len(s) + 2 * nums + res := make([]rune,m) + j := 0 + for i:=0;i>1 + //优先考虑两种极端情况 + if mid==0 && nums[mid]!=mid{ + return mid + } else if mid==n-1 && nums[mid]==mid{ + return mid+1 + } else if nums[mid]!=mid && nums[mid-1]==mid-1{ + return mid + } else if nums[mid] != mid{ + right = mid - 1 + } else { + left = mid + 1 + } + } + return -1 +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day4_\346\237\245\346\211\276\347\256\227\346\263\225(\347\256\200\345\215\225)/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\346\225\260\345\255\227I.go" "b/LeetCode/\345\211\221\346\214\207offer/day4_\346\237\245\346\211\276\347\256\227\346\263\225(\347\256\200\345\215\225)/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\346\225\260\345\255\227I.go" new file mode 100644 index 0000000..7840cf2 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day4_\346\237\245\346\211\276\347\256\227\346\263\225(\347\256\200\345\215\225)/\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\346\225\260\345\255\227I.go" @@ -0,0 +1,78 @@ +//题目链接:https://leetcode.cn/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/ + +package main + +// 最通俗的解法,遍历数组,统计target出现的次数 +// 但这样的话完全没用到题目中数组是排序的 这个条件 +// 显然,本题想要考察二分查找算法! +// 看到有序数组就应该第一时间想到二分查找 +func search(nums []int, target int) int { + n := len(nums) + ans := 0 + for i := 0;i < n;i++{ + if nums[i] == target{ + ans += 1 + } + } + return ans +} + +// 方法2:两次二分查找 +// 统计一个数字在排序数组中出现的次数,那我们只需要知道其在数组中第一次和最后一次出现的下标 +// 设为 left 和 right,出现次数即为:right-left+1 +// 函数 first_equal_search 和 last_equal_search 分别用于查找数字第一次和最后一次出现的位置下标。 +// 实现上述两个函数时,网上有很多花里花哨的写法,如果去死记硬背,没过几天就会全部忘光 +// 下面我的写法非常好理解 +// 为避免命名冲突,次函数名添加后缀 “_2” +func search_2(nums []int, target int) int { + left := first_equal_search(nums,target) + right := last_equal_search(nums,target) + // 若出现次数为 0,需额外处理 + if left == -1{ + return 0 + } + return right-left+1 +} + +func first_equal_search(nums []int,target int) int{ + n := len(nums) + left,right := 0,n-1 + for left <= right{ + mid := left + (right-left)>>1 + if mid==0 && nums[mid]==target{ + return mid + } else if mid>0 && nums[mid]==target && nums[mid-1]!=target{ + return mid + } else if nums[mid] < target{ + left = mid + 1 + } else if nums[mid] > target{ + right = mid - 1 + } else { + // 此种情况说明 mid 指向元素为众多与target相等元素的其中一个 + // 而且不在起始点,我们要找第一个,所以移动右指针 + right = mid -1 + } + } + return -1 +} + +func last_equal_search(nums []int,target int) int{ + n := len(nums) + left,right := 0,n-1 + for left <= right{ + mid := left + (right-left)>>1 + if mid==n-1 && nums[mid]==target{ + return mid + } else if mid target{ + right = mid - 1 + } else { + // 与找第一个相等的元素同理,不在最后一个,所以移动左指针 + left = mid + 1 + } + } + return -1 +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day4_\346\237\245\346\211\276\347\256\227\346\263\225(\347\256\200\345\215\225)/\346\225\260\347\273\204\344\270\255\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.go" "b/LeetCode/\345\211\221\346\214\207offer/day4_\346\237\245\346\211\276\347\256\227\346\263\225(\347\256\200\345\215\225)/\346\225\260\347\273\204\344\270\255\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.go" new file mode 100644 index 0000000..3cc74b7 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day4_\346\237\245\346\211\276\347\256\227\346\263\225(\347\256\200\345\215\225)/\346\225\260\347\273\204\344\270\255\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.go" @@ -0,0 +1,99 @@ +// 题目链接:https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/ + +package main + +// 解题思路:本题最通俗的解法为哈希表,用哈希表记录nums数组中元素是否出现过, +// 若出现过,返回该元素即可。 +func findRepeatNumber(nums []int) int { + record := map[int]int{} + for _,num := range nums{ + if record[num] > 0{ + return num + } + record[num] ++ + } + return -1 +} + +// 方法2:设计哈希表 +// 哈希表解法的时间与空间复杂度均为O(n),该方法没有用到题目给的数字范围条件,所以应该思考下如何做改进 +// 试想下如何降低复杂度,对长度为n的数组,其中所有数字都在0~n-1范围内,而0~n-1又是长度为n的数组的所有下标索引。我们刚才方法是将其值放入哈希表 +// 而现在我们给定数组的长度为n,0~n-1刚好可以对应数组索引,让我们有一种将数组设计为哈希表的思路: +// 对数组进行遍历,设当前遍历到的的数字值为 x,则将索引为 x 对应的数字打标记,若之后遍历到某元素后,打标记过程中发现该元素已打标记,说明该数字已出现过,返回即可。 +// 现在要做的就是设计标记,标记为取负值,流程如下: +// 一次遍历数组中元素,设当前元素值为num,它可能已经被打了标记,我们取用其原数x=abs(num) +// 如果 nums[x] < 0,说明 x 已经出现过,返回 x 即可,否则,打标记,nums[x] = -nums[x] +// 但还存在一个问题,就是0取负还是0,我没想到太好的解决方案,就用了最朴素的方法,单独处理0。 +// 处理流程如下:初始化0的下标为 zero_index,遍历数组,得到0的下标zero_index,然后第二次遍历数组,若数组元素值为 zero_index的元素个数大于1,说明该元素重复,返回0的下标即可。 +// 这样的解题思路来自LeetCode41题:缺失的第一个正数 +// 算法核心在于将输入数组设计为哈希表,另外,我在此题的题解部分还没有看人有人用类似的方案解此题 +// 在进行此操作前,请务必与面试官进行交流,询问是否可以修改输入数组,确定可以的话,再用此方案。 +// 为避免命名冲突,次函数名添加后缀 “_2" +func findRepeatNumber_2(nums []int) int { + // 先处理0的情况 + // m为nums中0的下标出现的次数,zero_index为0的下标初始值 + m := 0 + zero_index := -1 + // 第一次遍历得到0的下标 + for i:=0;i 1{ + return zero_index + } + // 将输入数组设计为哈希表 + for _,num := range nums{ + x := abs(num) + // nums[x]<0,说明x元素此前出现过 + if nums[x] < 0{ + return x + } + // 若未出现过,打标记 + nums[x] = -nums[x] + } + return -1 +} + +func abs(num int) int { + if num < 0{ + return -num + } + return num +} + + +// 方法3:原地交换 次题解方法来自:https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/solution/mian-shi-ti-03-shu-zu-zhong-zhong-fu-de-shu-zi-yua/ +// 长度为 n 的数组 nums 里的所有数字都在 0 ~ n-1 的范围内 。 此说明含义:数组元素的 索引 和 值 是 一对多 的关系。 +// 因此,可遍历数组并通过交换操作,使元素的 索引 与 值 一一对应(即 nums[i] = i)。因而,就能通过索引映射对应的值,起到与字典等价的作用。 +// 遍历中,第一次遇到数字 x 时,将其交换至索引 x 处;而当第二次遇到数字 x 时,一定有 nums[x] = x,此时即可得到一组重复数字。。 +// 算法流程: +// 遍历数组 nums,设索引初始值为 i = 0 : +// 若 nums[i] = i: 说明此数字已在对应索引位置,无需交换,因此跳过; +// 若 nums[nums[i]] = nums[i]: 代表索引nums[i]处和索引i处的元素值都 nums[i],即找到一组重复值,返回此值 nums[i]; +// 否则,交换索引为i和nums[i]的元素值,将此数字交换至对应索引位置。 +// 若遍历完毕尚未返回,则返回 -1 。 +// 为避免命名冲突,次函数名添加后缀 “_3" +func findRepeatNumber_3(nums []int) int { + i := 0 + for i < len(nums){ + // 交换 nums[i]至索引i出后才进行下一索引处的交换 + if nums[i] == i{ + i ++ + continue + } + if nums[nums[i]] == nums[i]{ + return nums[i] + } + nums[nums[i]],nums[i] = nums[i],nums[nums[i]] + } + return -1 +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day5_\346\237\245\346\211\276\347\256\227\346\263\225(\344\270\255\347\255\211)/\344\272\214\347\273\264\346\225\260\347\273\204\344\270\255\347\232\204\346\237\245\346\211\276.go" "b/LeetCode/\345\211\221\346\214\207offer/day5_\346\237\245\346\211\276\347\256\227\346\263\225(\344\270\255\347\255\211)/\344\272\214\347\273\264\346\225\260\347\273\204\344\270\255\347\232\204\346\237\245\346\211\276.go" new file mode 100644 index 0000000..038a789 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day5_\346\237\245\346\211\276\347\256\227\346\263\225(\344\270\255\347\255\211)/\344\272\214\347\273\264\346\225\260\347\273\204\344\270\255\347\232\204\346\237\245\346\211\276.go" @@ -0,0 +1,59 @@ +// 题目链接:https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/?plan=lcof&plan_progress=klczit3 +// day5/31 +// 第 5 天主题为:查找算法(中等) +// 包含三道题目: +// 剑指offer04.二维数组中的查找 +// 剑指offer11.旋转数组的最小数字 +// 剑指offer50.第一个只出现一次的字符 + +// 通俗解法就是遍历二维数组的每个元素,查看是否存在值等于target的元素,若存在,返回 true,否则,false。 +// 但这完全没用到题目所给的递增条件,直接pass,就不附代码。 +// 当我们需要解决一个复杂的问题时,一个很有效的办法就是从一个具体的问题入手,通过分析简单具体的例子,试图寻找普遍的规律。 +// 利用下题目所给条件,每一行从左到右递增,每一列从上到下递增。这道题,找一下规律。 +// 如果我们先选取数组右上角的数字,则该数字左侧元素小于其值,下方元素大于其值,类似二叉搜索树。 +// 如果 target > 右上角数字的数值,则剔除第一行,第二行最后一个元素作为最右上角的数字, +// 同样的做法,每次移动,消除一行或者一列元素,直到找到目标值 target,或者 查找范围为空(即 target 不存在于二维数组中)。 +package main + +func findNumberIn2DArray(matrix [][]int, target int) bool { + if len(matrix)==0 || len(matrix[0]) == 0{ + return false + } + m,n := len(matrix),len(matrix[0]) + i,j := 0,n-1 + for matrix[i][j] != target{ + if matrix[i][j] > target{ + j -- + } else { + i ++ + } + if i>=m || j <0{ + return false + } + } + return true +} + +// 另外,本题从左下角分析也可以,道理是一样的,下述代码为从左下角元素开始的“二分” +// 为避免函数命名冲突,次函数名添加后缀 “_2” +func findNumberIn2DArray_2(matrix [][]int, target int) bool { + if len(matrix)==0 || len(matrix[0]) == 0{ + return false + } + m,n := len(matrix),len(matrix[0]) + i,j := m-1,0 + for matrix[i][j] != target{ + if matrix[i][j] > target{ + i -- + } else { + j ++ + } + if i<0 || j >= n{ + return false + } + } + return true +} + +// 本题考点:考察应聘者对二维数组的理解及编程能力,还考察应聘者分析问题的能力,当发现问题比较复杂时,是否能通过具体的例子找出其中的规律,是能够解决这个问题的关键所在。 +// 本题只要从一个具体的二维数组的右上角开始分析,就能找到规律所在,从而找到解决问题的突破口。 \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day5_\346\237\245\346\211\276\347\256\227\346\263\225(\344\270\255\347\255\211)/\346\227\213\350\275\254\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\346\225\260\345\255\227.go" "b/LeetCode/\345\211\221\346\214\207offer/day5_\346\237\245\346\211\276\347\256\227\346\263\225(\344\270\255\347\255\211)/\346\227\213\350\275\254\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\346\225\260\345\255\227.go" new file mode 100644 index 0000000..223471c --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day5_\346\237\245\346\211\276\347\256\227\346\263\225(\344\270\255\347\255\211)/\346\227\213\350\275\254\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\346\225\260\345\255\227.go" @@ -0,0 +1,47 @@ +//题目链接:https://leetcode.cn/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/?plan=lcof&plan_progress=klczit3 + +package main + +// 尽管题目描述看起来并不复杂,但本题是一道实实在在的困难题 +// 我个人在这道题上花费时间超过5h,现在有时候还是会绕进去 +// + +// 二分思想很简单,细节是魔鬼!各种边界处理问题 +// Although the basic idea of binary search is comparatively straightforward, the details can be surprisingly tricky... ----Knuth +// +// 解题思路:二分查找,参照题解 [旋转数组的最小数字 - 旋转数组的最小数字 - 力扣(LeetCode)](https://leetcode.cn/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/solution/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-by-leetcode-s/) +// 强烈建议观看官方题解,有图片作参考会好理解一些 +// 左右边界 left 和 right 初始化为 0 和 len(numbers)-1,假设最小值出现在 mid 位置,我们考虑数组中的最后一个元素 x: +// 在最小值右侧的元素,它们的值一定都小于等于 x;而在最小值左侧的元素,它们的值一定都大于等于 x。因此,我们可以根据这一条性质,通过二分查找的方法找出最小值。 + +// 将中间元素 number[mid] 和 numbers[right] 作比较,可能会出现以下三种情况:(为什么拿这两个位置的数字作比较,题解没有说,但这绝对不是简简单单就能想到的,一定是做了推理,或者经验所得) +//1. numbers[mid] < numbers[right],说明 numbers[mid] 是最小值右侧的元素(也可能就是numbers[mid]),因此我们忽略当前整个搜索区间的右半部分。right=mid +//2. number[mid] > numbers[right],说明最小值在 mid 右侧,我们忽略二分查找区间的左半部分。left = mid + 1 +//3. numbers[mid] = numbers[right],在这种情况下,我们无法确定最小值在mid的左侧还是右侧,但我们能确定的是,由于它们值相同,所以无论 numbers[high]是不是最小值, +// 其都有一个替代值 numbers[mid],因此我们可以忽略当前搜索区间的右端点。right-- +// 最终 left 与 right 落于同一位置,即最小值所在的索引。 +// +// 在这里,谈一下个人的想法:看题解,有时候我们觉得看懂了,但关掉题解,写代码的时候就忘了该怎么做,或者隔几天就忘了,很多时候就是因为你只看到了题解部分, +// 没有看到题解之外的,作者是如何从题目所给条件推导到题解这一步的,就比如,我第一次看这道题的官方题解时,就没有思考过为什么查找的条件是 left < right,而不是 left <= right, +// 还有,为什么比较的是 mid 元素 和 right 元素,而不是 left 元素,这些题解都没有说,如果自己不去想清楚,那就做不到对这道题的完美把控。 +// 当然,现在我明白了,left=right 时,我们已经找到了最小值所在的索引。 +// 这种能力的获得不是一蹴而就的,而是慢慢获取的。我也在练习的过程中... + + + +func minArray(numbers []int) int { + low := 0 + high := len(numbers) - 1 + for low < high { + pivot := low + (high - low) / 2 + if numbers[pivot] < numbers[high] { + high = pivot + } else if numbers[pivot] > numbers[high] { + low = pivot + 1 + } else { + high-- + } + } + return numbers[low] +} + diff --git "a/LeetCode/\345\211\221\346\214\207offer/day5_\346\237\245\346\211\276\347\256\227\346\263\225(\344\270\255\347\255\211)/\347\254\254\344\270\200\344\270\252\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\345\255\227\347\254\246.go" "b/LeetCode/\345\211\221\346\214\207offer/day5_\346\237\245\346\211\276\347\256\227\346\263\225(\344\270\255\347\255\211)/\347\254\254\344\270\200\344\270\252\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\345\255\227\347\254\246.go" new file mode 100644 index 0000000..7d1a58d --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day5_\346\237\245\346\211\276\347\256\227\346\263\225(\344\270\255\347\255\211)/\347\254\254\344\270\200\344\270\252\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\345\255\227\347\254\246.go" @@ -0,0 +1,22 @@ +// 题目链接:https://leetcode.cn/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/ + +package main + + +// 解题思路:两次遍历字符串,第一次遍历的过程中,用哈希表存储每个字符出现的次数, +// 第二次遍历字符串时,哈希表查看当前字符出现次数,若哈希表值为1,返回该字符。 +func firstUniqChar(s string) byte { + if len(s) == 0{ + return ' ' + } + record := map[rune]int{} + for _,x := range s{ + record[x] ++ + } + for _,x := range s{ + if record[x] == 1{ + return byte(x) + } + } + return ' ' +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day6_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\347\256\200\345\215\225)/\344\273\216\344\270\212\345\210\260\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.go" "b/LeetCode/\345\211\221\346\214\207offer/day6_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\347\256\200\345\215\225)/\344\273\216\344\270\212\345\210\260\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.go" new file mode 100644 index 0000000..5452782 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day6_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\347\256\200\345\215\225)/\344\273\216\344\270\212\345\210\260\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.go" @@ -0,0 +1,48 @@ +// 题目链接:https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/ +// day6/31 +// 第 6 天主题为:搜素与回溯算法(简单) +// 包含三道题目: +// 剑指offer32-I.从上到下打印二叉树 +// 剑指offer32-II.从上到下打印二叉树II +// 剑指offer32-III.从上到下打印二叉树III + +// 本题题解参考自:https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/solution/mian-shi-ti-32-i-cong-shang-dao-xia-da-yin-er-ch-4/ +// 解题思路:从上到下打印二叉树,也就是二叉树的层序遍历,考察 BFS,BFS通常借助队列的先入先出特性来实现。 +//算法流程: +//1. 特例处理: 当树的根节点为空,则直接返回空切片 []int{} ; +//2. 初始化: 打印结果列表 res:=make([]int,0) ,包含根节点的队列 q:=[]*TreeNode{root}; +//3. BFS 循环: 当队列 queue 为空时跳出; +// 1. 出队: 队首元素出队,记为 node; +// 2. 打印: 将 node.val 添加至列表 res 尾部; +// 3. 添加子节点: 若 node 的左(右)子节点不为空,则将左(右)子节点加入队列 queue ; +//4. 返回值: 返回打印结果列表 res 即可。 +package main + + + +// Definition for a binary tree node. +type TreeNode struct { + Val int + Left *TreeNode + Right *TreeNode +} + +func levelOrder(root *TreeNode) []int { + if root==nil{ + return []int{} + } + res := []int{} + q := []*TreeNode{root} + for len(q)!=0{ + cur := q[0] + if cur.Left != nil{ + q=append(q,cur.Left) + } + if cur.Right != nil{ + q = append(q,cur.Right) + } + res = append(res,cur.Val) + q = q[1:] + } + return res +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day6_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\347\256\200\345\215\225)/\344\273\216\344\270\212\345\210\260\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221II.go" "b/LeetCode/\345\211\221\346\214\207offer/day6_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\347\256\200\345\215\225)/\344\273\216\344\270\212\345\210\260\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221II.go" new file mode 100644 index 0000000..193284b --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day6_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\347\256\200\345\215\225)/\344\273\216\344\270\212\345\210\260\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221II.go" @@ -0,0 +1,50 @@ +// 题目链接:https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/?envType=study-plan&id=lcof + + +package main + +// 解题思路:本质还是考察层序遍历,我们依旧使用队列来实现。 +// 将每层遍历到的节点放在列表中。首先,获取当前q的长度,即当前层节点的数目 n,然后依次访问队列 q 的前 n 个节点, +// 两步操作: +// 1. 将节点 val 添加到当前层的切片中; +// 2. 若当前节点存在孩子节点,按顺序将其添加至列表 q +// 最后,q = q[n:],删除列表中已经访问过的当前层节点,若 q 非空,下一次循环进入下一层节点的处理。 + +//算法流程:(算法流程来自题解:https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/solution/mian-shi-ti-32-ii-cong-shang-dao-xia-da-yin-er-c-5/) +// 写的很系统,值得学习 +//1. 特例处理: 当根节点为空,则返回空切片 []int{} ; +//2. 初始化: 打印结果列表 res:=[]int{} ,包含根节点的队列 queue:=[]*TreeNode{root} ; +//3. BFS 循环: 当队列 queue 为空时跳出; +// 1. 新建一个临时列表 tmp ,用于存储当前层打印结果; +// 2. 当前层打印循环: 循环次数为当前层节点数(即队列 queue 长度); +// 1. 出队: 队首元素出队,记为 node; +// 2. 打印: 将 node.val 添加至 tmp 尾部; +// 3. 添加子节点: 若 node 的左(右)子节点不为空,则将左(右)子节点加入队列 queue ; +// 3. 将当前层结果 tmp 添加入 res 。 +//4. 返回值: 返回打印结果列表 res 即可。 + +// 为避免函数命名冲突,次函数名添加后缀 “_2” +func levelOrder_2(root *TreeNode) [][]int { + if root == nil{ + return [][]int{} + } + res := [][]int{} + q := []*TreeNode{root} + for len(q) != 0{ + n := len(q) + temp := []int{} + for i:=0;i=0;i--{ + temp = append(temp,q[i].Val) + } + } + order ++ + q = q[n:] + res = append(res,temp) + } + return res +} + + +// 然后看了下官方的操作,挺不错的,先按照第二版从上到下打印二叉树的思路解题, +// 最后向返回数组添加某一层遍历结果时,先将该层遍历结果进行翻转,简单易懂。 +// 为避免函数命名冲突,次函数名添加后缀 “_4” +func levelOrder_4(root *TreeNode) (ans [][]int) { + if root == nil { + return + } + queue := []*TreeNode{root} + for level := 0; len(queue) > 0; level++ { + vals := []int{} + q := queue + queue = nil + for _, node := range q { + vals = append(vals, node.Val) + if node.Left != nil { + queue = append(queue, node.Left) + } + if node.Right != nil { + queue = append(queue, node.Right) + } + } + // 本质上和层序遍历一样,我们只需要把奇数层的元素翻转即可 + if level%2 == 1 { + for i, n := 0, len(vals); i < n/2; i++ { + vals[i], vals[n-1-i] = vals[n-1-i], vals[i] + } + } + ans = append(ans, vals) + } + return +} diff --git "a/LeetCode/\345\211\221\346\214\207offer/day7_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\347\256\200\345\215\225)2/\344\272\214\345\217\211\346\240\221\347\232\204\351\225\234\345\203\217.go" "b/LeetCode/\345\211\221\346\214\207offer/day7_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\347\256\200\345\215\225)2/\344\272\214\345\217\211\346\240\221\347\232\204\351\225\234\345\203\217.go" new file mode 100644 index 0000000..988aedb --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day7_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\347\256\200\345\215\225)2/\344\272\214\345\217\211\346\240\221\347\232\204\351\225\234\345\203\217.go" @@ -0,0 +1,92 @@ +// 题目链接:https://leetcode.cn/problems/er-cha-shu-de-jing-xiang-lcof/?envType=study-plan&id=lcof +// day7/31 +// 第 7 天主题为:搜索与回溯算法(简单),与前一天主题相同 +// 包含三道题目: +// 剑指offer26.树的子结构 +// 剑指offer27.二叉树的镜像 +// 剑指offer28.对称的二叉树 + + +package main + + +// Definition for a binary tree node. +type TreeNode struct { + Val int + Left *TreeNode + Right *TreeNode +} + +// 解题思路:观察例子可以发现,镜像 就是 每个节点的左右子树进行翻转。所以,两步完成本题: +//1. 遍历二叉树保存所有节点(前中后序遍历都可以),用节点的指针类型切片保存; +//2. 遍历切片,交换其左右子节点 node.Left,node.Right = node.Right,node.Left +func mirrorTree(root *TreeNode) *TreeNode { + nodes := []*TreeNode{} + var preorder func(root *TreeNode) + preorder = func(root *TreeNode){ + if root == nil{ + return + } + nodes = append(nodes,root) + if root.Left != nil{ + preorder(root.Left) + } + if root.Right != nil{ + preorder(root.Right) + } + } + preorder(root) + for _,node := range nodes{ + node.Left,node.Right = node.Right,node.Left + } + return root +} + +// 上面这种解法的时间空间复杂度均为 O(n) +// 我们也可以在遍历的过程中交换节点的左右子节点,省去 nodes 切片占用的内存空间,但这本质上并不会降低时间和空间复杂度, +// 因为我们在对二叉树进行遍历的过程中,调用了系统栈,系统需要使用 O(n) 大小的栈空间。 +// 前序遍历的过程中交换左右子节点 +// 为避免函数命名冲突,次函数名添加后缀 “_2” +func mirrorTree_2(root *TreeNode) *TreeNode { + var preorder func(root *TreeNode) + preorder = func(root *TreeNode){ + if root == nil{ + return + } + root.Left,root.Right = root.Right,root.Left + if root.Left != nil{ + preorder(root.Left) + } + if root.Right != nil{ + preorder(root.Right) + } + } + preorder(root) + return root +} + + +// 另外,在LeetCode题解区我还看到了有人用层序遍历的方式解题,本质上和上面两种思路是一样的,就是选择了另一种遍历二叉树的方式, +// 交换左右子节点的核心操作无任何变化,代码开头的判断 root 节点是否为空不可省略,因为 q 初始化默认加入root节点,若该节点为空,后续代码会出错。 +// 为避免函数命名冲突,次函数名添加后缀 “_3” +func mirrorTree_3(root *TreeNode) *TreeNode { + if root == nil{ + return root + } + q := []*TreeNode{root} + for len(q) != 0{ + node := q[0] + node.Left,node.Right = node.Right,node.Left + if node.Left != nil{ + q = append(q,node.Left) + } + if node.Right != nil{ + q = append(q,node.Right) + } + q = q[1:] + } + return root +} + + +// 二叉树的定义就是递归的,所以做二叉树的题目一定要有递归的想法,有思路后开始着手写代码,不要在脑海中模拟太多层的递归,否则很容易绕进去。 \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day7_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\347\256\200\345\215\225)2/\345\257\271\347\247\260\347\232\204\344\272\214\345\217\211\346\240\221.go" "b/LeetCode/\345\211\221\346\214\207offer/day7_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\347\256\200\345\215\225)2/\345\257\271\347\247\260\347\232\204\344\272\214\345\217\211\346\240\221.go" new file mode 100644 index 0000000..75eadd8 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day7_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\347\256\200\345\215\225)2/\345\257\271\347\247\260\347\232\204\344\272\214\345\217\211\346\240\221.go" @@ -0,0 +1,36 @@ +// 题目链接:https://leetcode.cn/problems/dui-cheng-de-er-cha-shu-lcof/?envType=study-plan&id=lcof + +package main + + +// 解题思路:如果二叉树只有一个根节点,那其必然是对称的,本题主要是判断节点的左右子树是否对称。 +// +// 判断左右子树是否对称,若两子树根节点均为空,说明对称,若某子树为空而另一颗子树不为空,说明不对称,两子树根节点值不相等的话也必定不对称吗,否则,对称,之后递归地判断 +//- 左子树的左子树 与 右子树的右子树 是否对称 +//- 左子树的右子树 与 右子树的左子树 是否对称 +// 两者均对称时,说明该左右子树对称;否则,不对称。 + +func isSymmetric(root *TreeNode) bool { + // 根节点为空,对称 + if root == nil{ + return true + } + var sym func(x,y *TreeNode) bool + // 递归判断左右子树是否对称 + sym = func(x,y *TreeNode) bool { + // 两者均为空说明对称 + if x == nil && y == nil{ + return true + // 其中某一子树为空,而另一子树不为空,不对称 + } else if x == nil || y == nil{ + return false + } + // 两者根节点值不相等时也不对称 + if x.Val != y.Val{ + return false + } + // 递归判断x的左右子树 与 y 的右左子树是否对称 + return sym(x.Left,y.Right) && sym(x.Right,y.Left) + } + return sym(root.Left,root.Right) +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day7_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\347\256\200\345\215\225)2/\346\240\221\347\232\204\345\255\220\347\273\223\346\236\204.go" "b/LeetCode/\345\211\221\346\214\207offer/day7_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\347\256\200\345\215\225)2/\346\240\221\347\232\204\345\255\220\347\273\223\346\236\204.go" new file mode 100644 index 0000000..23aeb56 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day7_\346\220\234\347\264\242\344\270\216\345\233\236\346\272\257\347\256\227\346\263\225(\347\256\200\345\215\225)2/\346\240\221\347\232\204\345\255\220\347\273\223\346\236\204.go" @@ -0,0 +1,60 @@ +// 题目链接:https://leetcode.cn/problems/shu-de-zi-jie-gou-lcof/ + +package main + + +// 此题解参考自:https://leetcode.cn/problems/shu-de-zi-jie-gou-lcof/solution/mian-shi-ti-26-shu-de-zi-jie-gou-xian-xu-bian-li-p/ +// 若树 B 是 A 的子结构,则 A 子结构的根节点可能是 A 中任意一个节点,我们判断 B 是否为 A 的子结构,就需要遍历 A 的所有节点, +// 然后判断是否有某节点 以 该节点为 根节点的子树包含(有点绕,其实就是,若 A 与 B 根节点相同, B 是否为 A 的子结构, +// 但当 B 为空的时候,B 必为 A 的子结构)。 +// 名词规定:树 A 的根节点记作 节点 A,树 B 的根节点称为 节点 B 。 +// recur(A, B)函数,用于判断树 A 中以 A为根节点的子结构是否包含树 B +//终止条件: +//- 当节点 B 为空:说明树 B 已匹配完成(越过叶子节点),因此返回 true ; +//- 当节点 A 为空:说明已经越过树 A 叶子节点(而 B 节点非空),即匹配失败,返回 false ; +//- 当节点 A 和 B 的值不同:说明匹配失败,返回 false ; +//返回值: +//- 判断 A 和 B 的左子节点是否相等,即 recur(A.left, B.left) ; +//- 判断 A 和 B 的右子节点是否相等,即 recur(A.right, B.right) ; +//两者取逻辑与后返回。 +// 特例处理:当 树 A 为空 或 树 B 为空时,直接返回 false(对应题目中的约定:空树不是任意一个树的子结构) 。 +// 之后我们对 A 作遍历(前中后序都可以),对其中每个节点与 B 进行 recur 判断,若存在 true 结果,返回最终的 true;若全为 false,说明 B 不是 A 的子结构,返回 false。 +func isSubStructure(A *TreeNode, B *TreeNode) bool { + if A == nil || B == nil{ + return false + } + nodes := []*TreeNode{} + // 对树的遍历,这里我采用前序遍历 + // 使用 中序遍历、后序遍历 or 层序遍历都是可以的 + var perorder func(root *TreeNode) + perorder = func(root *TreeNode){ + if root == nil{ + return + } + nodes = append(nodes,root) + if root.Left != nil{ + perorder(root.Left) + } + if root.Right != nil{ + perorder(root.Right) + } + } + perorder(A) + // 判断B是否为以为根节点的子树的在子结构 + var sub func(A,B *TreeNode) bool + sub = func(A,B *TreeNode) bool{ + if B == nil{ + return true + } + if A == nil || A.Val != B.Val { + return false + } + return sub(A.Left,B.Left) && sub(A.Right,B.Right) + } + for _,node := range nodes{ + if sub(node,B){ + return true + } + } + return false +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day8_\345\212\250\346\200\201\350\247\204\345\210\222(\347\256\200\345\215\225)/\346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.go" "b/LeetCode/\345\211\221\346\214\207offer/day8_\345\212\250\346\200\201\350\247\204\345\210\222(\347\256\200\345\215\225)/\346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.go" new file mode 100644 index 0000000..49fd990 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day8_\345\212\250\346\200\201\350\247\204\345\210\222(\347\256\200\345\215\225)/\346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.go" @@ -0,0 +1,51 @@ +// 题目链接:https://leetcode.cn/problems/fei-bo-na-qi-shu-lie-lcof/?envType=study-plan&id=lcof +// day8/31 +// 第 8 天主题为:动态规划(简单) +// 包含三道题目: +// 剑指offer10-I.斐波那契数列 +// 剑指offer10-II.青蛙跳台阶问题 +// 剑指offer63.股票的最大利润 + +//关于函数命名冲突的问题,从今天的题解开始,就不再额外标注了 +//我一般通过函数名添加后缀的方式解决 + +package main + +import "math" + +//解题思路:最基础的动态规划问题,题目已经给出状态转移方程,或者可以直接理解为 递归,一个阶段只有一个状态 +//动态规划三步: +//1. 确定dp数组及下标含义:dp[i] 代表 F(i),要求第 n 个斐波那契数,则 dp 数组长度为 n; +//1. 数组初始化:题目已给出,dp[0]=0,dp[1]=1; +//2. 状态转移方程:题目已给出,i>=2时,dp[i] = dp[i-1]+dp[i-2]。 +//最终返回 dp[n-1] 即可 + +func fib(n int) int { + if n <= 1{ + return n + } + x := int(math.Pow(10,9)) + 7 + dp := make([]int,n+1) + dp[0],dp[1] = 0,1 + for i:=2;i 0{ + res = max(res,prices[j]-prices[i]) + } + } + } + return res +} + + +//方法2:一次遍历 +//遍历股票价格的过程中,维护一个股票最小价格,若当前遍历到的价格小于该最小价格,则更新最小价格,否则,更新最大利润。 +//为避免函数命名冲突,次函数名添加后缀 “_2” +func maxProfit_2(prices []int) int { + n := len(prices) + if n == 0{ + return 0 + } + min_price := prices[0] + res := 0 + for i:=1;i y{ + return x + } + return y +} + +//这道题和动态规划有关系吗?我觉得关系不大吧,用递推更加合适一些。 +//主要是场景过于简单,只需要维护一个最小价格,每个阶段只有一个状态,且每个阶段的状态与之前阶段均无关。 \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day8_\345\212\250\346\200\201\350\247\204\345\210\222(\347\256\200\345\215\225)/\351\235\222\350\233\231\350\267\263\345\217\260\351\230\266\351\227\256\351\242\230.go" "b/LeetCode/\345\211\221\346\214\207offer/day8_\345\212\250\346\200\201\350\247\204\345\210\222(\347\256\200\345\215\225)/\351\235\222\350\233\231\350\267\263\345\217\260\351\230\266\351\227\256\351\242\230.go" new file mode 100644 index 0000000..e158bba --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day8_\345\212\250\346\200\201\350\247\204\345\210\222(\347\256\200\345\215\225)/\351\235\222\350\233\231\350\267\263\345\217\260\351\230\266\351\227\256\351\242\230.go" @@ -0,0 +1,43 @@ +//题目链接:https://leetcode.cn/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/ + +package main + +import "math" + + +//本质上和斐波那契数列一样,区别在于 dp 数组初始化不同。 +//跳到第 n 级台阶,我们可以从第 n-1 个台阶跳上去,也可以从 n-2 个台阶跳上去,跳法,即为跳到 n-1 级台阶 和 跳到 n-2 级台阶的跳法之和。 +//动态规划三步: +//1. 确定dp数组及下表含义:dp[i] 代表跳到第 i 级台阶的跳法数量,求跳到第 n 级台阶的跳法数量,dp[0]情况特殊,则 dp 数组长度设为 n+1; +//2. dp 数组初始化:由题意可知,dp[0]=1,dp[1]=1,dp[2]=2 +//3. 状态转移方程:当 i > 2 时,dp[i] = dp[i-1] + dp[i-2] +//最终,返回 dp[n] 即可。 +func numWays(n int) int { + if n <= 1{ + return 1 + } + x := int(math.Pow(10,9)+7) + dp := make([]int,n+1) + dp[0] = 1 + dp[1] = 1 + for i:=2;i<=n;i++{ + dp[i] = (dp[i-1]+dp[i-2]) % x + } + return dp[n] +} + +//由于每个阶段的状态只与前两个状态有关,所以我们可以用滚动数组代替 dp 数组解题, +//将空间复杂度从 O(n) 降低至 O(1)。 +func numWays_2(n int) int { + if n <= 1{ + return 1 + } + x := int(math.Pow(10,9)+7) + a,b := 1,1 + var res int + for i:=2;i<=n;i++{ + res = (a+b) % x + a,b = b,res + } + return res +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day9_\345\212\250\346\200\201\350\247\204\345\210\222(\344\270\255\347\255\211)/\347\244\274\347\211\251\347\232\204\346\234\200\345\244\247\344\273\267\345\200\274.go" "b/LeetCode/\345\211\221\346\214\207offer/day9_\345\212\250\346\200\201\350\247\204\345\210\222(\344\270\255\347\255\211)/\347\244\274\347\211\251\347\232\204\346\234\200\345\244\247\344\273\267\345\200\274.go" new file mode 100644 index 0000000..cd6bcb3 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day9_\345\212\250\346\200\201\350\247\204\345\210\222(\344\270\255\347\255\211)/\347\244\274\347\211\251\347\232\204\346\234\200\345\244\247\344\273\267\345\200\274.go" @@ -0,0 +1,51 @@ +// 题目链接:https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof/?envType=study-plan&id=lcof +// day9/31 +// 第 9 天主题为:动态规划(中等) +// 包含两道题目: +// 剑指offer42.连续子数组的最大和 +// 剑指offer47.礼物的最大价值 + +package main + +//经典二维dp,在每个网格的位置有两个选择,向右 或 向下走,当前网格可获得的最大礼物价值, +//就只能从其上面的格子或左边的格子 的最大礼物价值 加上当前格子的礼物价值 得到,回溯到起始节点,容易想到用 dp 解题。 +//动态规划三步骤: +//1. 确定dp数组大小及下标含义:dp[i][j] 代表 从棋盘左上角走到 (i,j) 下标位置可以获得的礼物最大价值, +// 则 len(dp) = len(grid),len(dp[0])=len(grid[0]),即 大小与给定棋盘大小相等; +//2. dp数组初始化:(一般情况下初始第一行和第一列)网格(0,0)为起始位置,dp[0][0] 没有别的选择,dp[0][0] = grid[0][0] +// 因为计算当前网格的最大礼物价值,需要知道其上方和左方网格的最大礼物价值,所以我们要初始化第一行和第一列的 dp 数组元素,防止越界情况的发生; +//3. 状态转移方程:i>1 且 j>1 时:dp[i][j] = max(dp[i-1][j],dp[i][j-1]) + grid[i][j] +// +//最后返回 dp[m-1][n-1] 即可。 + + +func maxValue(grid [][]int) int { + if len(grid)==0 || len(grid[0])==0{ + return 0 + } + m,n := len(grid),len(grid[0]) + dp := make([][]int,m) + for i:=0;i y{ + return x + } + return y +} \ No newline at end of file diff --git "a/LeetCode/\345\211\221\346\214\207offer/day9_\345\212\250\346\200\201\350\247\204\345\210\222(\344\270\255\347\255\211)/\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\222\214.go" "b/LeetCode/\345\211\221\346\214\207offer/day9_\345\212\250\346\200\201\350\247\204\345\210\222(\344\270\255\347\255\211)/\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\222\214.go" new file mode 100644 index 0000000..0d161c1 --- /dev/null +++ "b/LeetCode/\345\211\221\346\214\207offer/day9_\345\212\250\346\200\201\350\247\204\345\210\222(\344\270\255\347\255\211)/\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\222\214.go" @@ -0,0 +1,20 @@ +// 题目链接:https://leetcode.cn/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/?envType=study-plan&id=lcof +package main + +//简单的一维 dp 入门题目 +//动态规划三步: +//1. 确定dp数组大小及下标含义:dp[i] 代表以下标 i 为最后元素的子数组的最大值,dp 数组长度与给定数组 nums 长度相同。 +//2. dp 数组初始化:每个单独元素都是一个子数组,初始化 dp[0] = nums[0] +//3. 状态转移方程:从下标 1 开始,dp[i] = max(nums[i],nums[i]+dp[i-1]),若 dp[i-1]>0,说明以下标 i 截止的子数组的最大和要包含之前元素,包含多少呢?dp[i-1] 已经处理完了,我们只需要相加即可。 +// 最终需返回 dp 数组的最大值 +func maxSubArray(nums []int) int { + n := len(nums) + dp := make([]int,n) + dp[0] = nums[0] + res := nums[0] + for i:=1;i|[机智的程序员小熊](https://pic4.zhimg.com/80/v2-006a7dfa4d5af1bbde53a77f4a1b77f3_720w.jpg)|涉及各种后端语言、OpenStack、DevOps、容器等,还有一些思考| |[老衲的博客](https://blog.csdn.net/CalledJoker)||||| -|[yann的小站](https://www.ai-5g.com/)|运维、go语言||||| +|[yann的小站](https://www.ai-5g.com/)||||运维、go语言| +|[华扬的云原生](https://www.gqapwd.com/)||| @@ -17,4 +18,6 @@ |[Jack Cui](https://cuijiahua.com/)|剑指Offer全集题解,机器学习领域,建站文章写的比较全| |[wego](https://www.yuque.com/wegoer/set/blog)|一个线上go语言组织内容很丰富| |[星光博客](https://www.xgboke.com/wordpress/)|wordpress建站比较6、其他文章没什么意思| +|[keep_running](https://www.yuque.com/keep_running)|复合类技术网站| + diff --git a/interview/linux.md b/interview/linux.md index 8fb704c..cd9707f 100755 --- a/interview/linux.md +++ b/interview/linux.md @@ -217,6 +217,16 @@ perf top -g -p 246 * `lightdm.log`日志,该日志是和登录界面相关的一些日志信息,如果系统卡死的时候是在用户登录界面的时候卡死了,这个日志就有必要也看一眼 * 最后有可能是硬件问题 +## epoll 相比于 select 模型具备的优势: + +epoll 处理事件流模型是线程安全的; + +epoll 跟 select 模型相比调用 fd 文件描述符时使用了 mmap 共享用户和内核的部分空间,提高了效率; + +epoll 是基于事件驱动的,相比 select 需要扫描整个文件描述符的相关状态,epoll 基于事件驱动避免频繁扫描文件描述符,可以直接调用 callback 回调函数,效率更高; + +取消了 select 模型里面单个进程能够监视的文件描述符的数量存在的最大限制(1024),如果你有使用过早期的 Apache 版本的,它使用的select 模型,当请求超过 1000 以后就会出现延迟或者请求错误,而改用 Nginx 的话性能会得到明显的改善。 + ## 发散问题 ### Linux 打开文件句柄写入一个文件时,mv这个文件会发生什么