diff --git a/README.md b/README.md index a50dcd72..b1db51c9 100644 --- a/README.md +++ b/README.md @@ -141,9 +141,9 @@ 1. [字符串:344.反转字符串](./problems/0344.反转字符串.md) 2. [字符串:541.反转字符串II](./problems/0541.反转字符串II.md) -3. [字符串:替换数字](./problems/kama54.替换数字.md) +3. [字符串:替换数字](./problems/kamacoder/0054.替换数字.md) 4. [字符串:151.翻转字符串里的单词](./problems/0151.翻转字符串里的单词.md) -5. [字符串:右旋字符串](./problems/kama55.右旋字符串.md) +5. [字符串:右旋字符串](./problems/kamacoder/0055.右旋字符串.md) 6. [帮你把KMP算法学个通透](./problems/0028.实现strStr.md) 8. [字符串:459.重复的子字符串](./problems/0459.重复的子字符串.md) 9. [字符串:总结篇!](./problems/字符串总结.md) @@ -154,7 +154,7 @@ 1. [数组:27.移除元素](./problems/0027.移除元素.md) 2. [字符串:344.反转字符串](./problems/0344.反转字符串.md) -3. [字符串:替换数字](./problems/kama54.替换数字.md) +3. [字符串:替换数字](./problems/kamacoder/0054.替换数字.md) 4. [字符串:151.翻转字符串里的单词](./problems/0151.翻转字符串里的单词.md) 5. [链表:206.翻转链表](./problems/0206.翻转链表.md) 6. [链表:19.删除链表的倒数第 N 个结点](./problems/0019.删除链表的倒数第N个节点.md) diff --git a/problems/0001.两数之和.md b/problems/0001.两数之和.md index 044eac14..e982ae12 100644 --- a/problems/0001.两数之和.md +++ b/problems/0001.两数之和.md @@ -341,7 +341,7 @@ impl Solution { } ``` -### Javascript: +### JavaScript: ```javascript var twoSum = function (nums, target) { diff --git a/problems/0015.三数之和.md b/problems/0015.三数之和.md index f7146907..1685db74 100644 --- a/problems/0015.三数之和.md +++ b/problems/0015.三数之和.md @@ -34,7 +34,7 @@ ### 哈希解法 -两层for循环就可以确定 a 和b 的数值了,可以使用哈希法来确定 0-(a+b) 是否在 数组里出现过,其实这个思路是正确的,但是我们有一个非常棘手的问题,就是题目中说的不可以包含重复的三元组。 +两层for循环就可以确定 两个数值,可以使用哈希法来确定 第三个数 0-(a+b) 或者 0 - (a + c) 是否在 数组里出现过,其实这个思路是正确的,但是我们有一个非常棘手的问题,就是题目中说的不可以包含重复的三元组。 把符合条件的三元组放进vector中,然后再去重,这样是非常费时的,很容易超时,也是这道题目通过率如此之低的根源所在。 @@ -48,35 +48,41 @@ ```CPP class Solution { public: + // 在一个数组中找到3个数形成的三元组,它们的和为0,不能重复使用(三数下标互不相同),且三元组不能重复。 + // b(存储)== 0-(a+c)(检索) vector> threeSum(vector& nums) { vector> result; sort(nums.begin(), nums.end()); - // 找出a + b + c = 0 - // a = nums[i], b = nums[j], c = -(a + b) + for (int i = 0; i < nums.size(); i++) { - // 排序之后如果第一个元素已经大于零,那么不可能凑成三元组 - if (nums[i] > 0) { + // 如果a是正数,a 0) break; - } - if (i > 0 && nums[i] == nums[i - 1]) { //三元组元素a去重 + + // [a, a, ...] 如果本轮a和上轮a相同,那么找到的b,c也是相同的,所以去重a + if (i > 0 && nums[i] == nums[i - 1]) continue; - } + + // 这个set的作用是存储b unordered_set set; - for (int j = i + 1; j < nums.size(); j++) { - if (j > i + 2 - && nums[j] == nums[j-1] - && nums[j-1] == nums[j-2]) { // 三元组元素b去重 + + for (int k = i + 1; k < nums.size(); k++) { + // 去重b=c时的b和c + if (k > i + 2 && nums[k] == nums[k - 1] && nums[k - 1] == nums[k - 2]) continue; + + // a+b+c=0 <=> b=0-(a+c) + int target = 0 - (nums[i] + nums[k]); + if (set.find(target) != set.end()) { + result.push_back({nums[i], target, nums[k]}); // nums[k]成为c + set.erase(target); } - int c = 0 - (nums[i] + nums[j]); - if (set.find(c) != set.end()) { - result.push_back({nums[i], nums[j], c}); - set.erase(c);// 三元组元素c去重 - } else { - set.insert(nums[j]); + else { + set.insert(nums[k]); // nums[k]成为b } } } + return result; } }; diff --git a/problems/0019.删除链表的倒数第N个节点.md b/problems/0019.删除链表的倒数第N个节点.md index f0ef2366..fafef1f2 100644 --- a/problems/0019.删除链表的倒数第N个节点.md +++ b/problems/0019.删除链表的倒数第N个节点.md @@ -58,7 +58,7 @@ * fast和slow同时移动,直到fast指向末尾,如题: - +//图片中有错别词:应该将“只到”改为“直到” * 删除slow指向的下一个节点,如图: diff --git a/problems/0020.有效的括号.md b/problems/0020.有效的括号.md index d310f415..f2f5cdd1 100644 --- a/problems/0020.有效的括号.md +++ b/problems/0020.有效的括号.md @@ -275,7 +275,7 @@ def is_valid(strs) end ``` -### Javascript: +### JavaScript: ```javascript var isValid = function (s) { diff --git a/problems/0024.两两交换链表中的节点.md b/problems/0024.两两交换链表中的节点.md index 23dba84b..305bb7cc 100644 --- a/problems/0024.两两交换链表中的节点.md +++ b/problems/0024.两两交换链表中的节点.md @@ -286,7 +286,7 @@ func swapPairs(head *ListNode) *ListNode { } ``` -### Javascript: +### JavaScript: ```javascript var swapPairs = function (head) { diff --git a/problems/0027.移除元素.md b/problems/0027.移除元素.md index 480800e9..10817ba6 100644 --- a/problems/0027.移除元素.md +++ b/problems/0027.移除元素.md @@ -131,7 +131,24 @@ public: ## 其他语言版本 ### Java: - +```java +class Solution { + public int removeElement(int[] nums, int val) { + // 暴力法 + int n = nums.length; + for (int i = 0; i < n; i++) { + if (nums[i] == val) { + for (int j = i + 1; j < n; j++) { + nums[j - 1] = nums[j]; + } + i--; + n--; + } + } + return n; + } +} +``` ```java class Solution { public int removeElement(int[] nums, int val) { diff --git a/problems/0028.实现strStr.md b/problems/0028.实现strStr.md index e0cb123e..63a08d96 100644 --- a/problems/0028.实现strStr.md +++ b/problems/0028.实现strStr.md @@ -1456,6 +1456,70 @@ public int[] GetNext(string needle) } ``` +### C: + +> 前缀表统一右移和减一 + +```c + +int *build_next(char* needle, int len) { + + int *next = (int *)malloc(len * sizeof(int)); + assert(next); // 确保分配成功 + + // 初始化next数组 + next[0] = -1; // next[0] 设置为 -1,表示没有有效前缀匹配 + if (len <= 1) { // 如果模式串长度小于等于 1,直接返回 + return next; + } + next[1] = 0; // next[1] 设置为 0,表示第一个字符没有公共前后缀 + + // 构建next数组, i 从模式串的第三个字符开始, j 指向当前匹配的最长前缀长度 + int i = 2, j = 0; + while (i < len) { + if (needle[i - 1] == needle[j]) { + j++; + next[i] = j; + i++; + } else if (j > 0) { + // 如果不匹配且 j > 0, 回退到次长匹配前缀的长度 + j = next[j]; + } else { + next[i] = 0; + i++; + } + } + return next; +} + +int strStr(char* haystack, char* needle) { + + int needle_len = strlen(needle); + int haystack_len = strlen(haystack); + + int *next = build_next(needle, needle_len); + + int i = 0, j = 0; // i 指向主串的当前起始位置, j 指向模式串的当前匹配位置 + while (i <= haystack_len - needle_len) { + if (haystack[i + j] == needle[j]) { + j++; + if (j == needle_len) { + free(next); + next = NULL + return i; + } + } else { + i += j - next[j]; // 调整主串的起始位置 + j = j > 0 ? next[j] : 0; + } + } + + free(next); + next = NULL; + return -1; +} +``` +

diff --git a/problems/0037.解数独.md b/problems/0037.解数独.md index 70d52e9e..5f3f881c 100644 --- a/problems/0037.解数独.md +++ b/problems/0037.解数独.md @@ -366,40 +366,56 @@ class Solution: """ Do not return anything, modify board in-place instead. """ - self.backtracking(board) + row_used = [set() for _ in range(9)] + col_used = [set() for _ in range(9)] + box_used = [set() for _ in range(9)] + for row in range(9): + for col in range(9): + num = board[row][col] + if num == ".": + continue + row_used[row].add(num) + col_used[col].add(num) + box_used[(row // 3) * 3 + col // 3].add(num) + self.backtracking(0, 0, board, row_used, col_used, box_used) - def backtracking(self, board: List[List[str]]) -> bool: - # 若有解,返回True;若无解,返回False - for i in range(len(board)): # 遍历行 - for j in range(len(board[0])): # 遍历列 - # 若空格内已有数字,跳过 - if board[i][j] != '.': continue - for k in range(1, 10): - if self.is_valid(i, j, k, board): - board[i][j] = str(k) - if self.backtracking(board): return True - board[i][j] = '.' - # 若数字1-9都不能成功填入空格,返回False无解 - return False - return True # 有解 + def backtracking( + self, + row: int, + col: int, + board: List[List[str]], + row_used: List[List[int]], + col_used: List[List[int]], + box_used: List[List[int]], + ) -> bool: + if row == 9: + return True - def is_valid(self, row: int, col: int, val: int, board: List[List[str]]) -> bool: - # 判断同一行是否冲突 - for i in range(9): - if board[row][i] == str(val): - return False - # 判断同一列是否冲突 - for j in range(9): - if board[j][col] == str(val): - return False - # 判断同一九宫格是否有冲突 - start_row = (row // 3) * 3 - start_col = (col // 3) * 3 - for i in range(start_row, start_row + 3): - for j in range(start_col, start_col + 3): - if board[i][j] == str(val): - return False - return True + next_row, next_col = (row, col + 1) if col < 8 else (row + 1, 0) + if board[row][col] != ".": + return self.backtracking( + next_row, next_col, board, row_used, col_used, box_used + ) + + for num in map(str, range(1, 10)): + if ( + num not in row_used[row] + and num not in col_used[col] + and num not in box_used[(row // 3) * 3 + col // 3] + ): + board[row][col] = num + row_used[row].add(num) + col_used[col].add(num) + box_used[(row // 3) * 3 + col // 3].add(num) + if self.backtracking( + next_row, next_col, board, row_used, col_used, box_used + ): + return True + board[row][col] = "." + row_used[row].remove(num) + col_used[col].remove(num) + box_used[(row // 3) * 3 + col // 3].remove(num) + return False ``` ### Go @@ -460,7 +476,7 @@ func isvalid(row, col int, k byte, board [][]byte) bool { -### Javascript +### JavaScript ```Javascript var solveSudoku = function(board) { diff --git a/problems/0045.跳跃游戏II.md b/problems/0045.跳跃游戏II.md index 3eeec268..8919d393 100644 --- a/problems/0045.跳跃游戏II.md +++ b/problems/0045.跳跃游戏II.md @@ -374,7 +374,7 @@ func max(a, b int) int { } ``` -### Javascript +### JavaScript ```Javascript var jump = function(nums) { diff --git a/problems/0046.全排列.md b/problems/0046.全排列.md index ab7af3ba..ca465efd 100644 --- a/problems/0046.全排列.md +++ b/problems/0046.全排列.md @@ -272,7 +272,7 @@ func dfs(nums []int, cur int) { } ``` -### Javascript +### JavaScript ```js diff --git a/problems/0047.全排列II.md b/problems/0047.全排列II.md index 56006a77..08e3c616 100644 --- a/problems/0047.全排列II.md +++ b/problems/0047.全排列II.md @@ -283,7 +283,7 @@ func dfs(nums []int, cur int) { } ``` -### Javascript +### JavaScript ```javascript var permuteUnique = function (nums) { diff --git a/problems/0051.N皇后.md b/problems/0051.N皇后.md index 6ced679c..38fc07e7 100644 --- a/problems/0051.N皇后.md +++ b/problems/0051.N皇后.md @@ -451,7 +451,7 @@ func isValid(n, row, col int, chessboard [][]string) bool { ``` -### Javascript +### JavaScript ```Javascript /** * @param {number} n diff --git a/problems/0053.最大子序和.md b/problems/0053.最大子序和.md index b8ad2e80..705a00d7 100644 --- a/problems/0053.最大子序和.md +++ b/problems/0053.最大子序和.md @@ -326,7 +326,7 @@ pub fn max_sub_array(nums: Vec) -> i32 { } ``` -### Javascript: +### JavaScript: ```Javascript var maxSubArray = function(nums) { diff --git a/problems/0054.螺旋矩阵.md b/problems/0054.螺旋矩阵.md index 6c0bad40..3b7afb90 100644 --- a/problems/0054.螺旋矩阵.md +++ b/problems/0054.螺旋矩阵.md @@ -260,7 +260,7 @@ class Solution { } ``` -### Javascript +### JavaScript ``` /** * @param {number[][]} matrix diff --git a/problems/0055.跳跃游戏.md b/problems/0055.跳跃游戏.md index 82b433d7..896dc998 100644 --- a/problems/0055.跳跃游戏.md +++ b/problems/0055.跳跃游戏.md @@ -183,7 +183,7 @@ func max(a, b int ) int { } ``` -### Javascript +### JavaScript ```Javascript var canJump = function(nums) { diff --git a/problems/0056.合并区间.md b/problems/0056.合并区间.md index 134a9028..538be693 100644 --- a/problems/0056.合并区间.md +++ b/problems/0056.合并区间.md @@ -215,7 +215,7 @@ func max56(a, b int) int { ``` -### Javascript +### JavaScript ```javascript var merge = function (intervals) { intervals.sort((a, b) => a[0] - b[0]); diff --git a/problems/0062.不同路径.md b/problems/0062.不同路径.md index 5b46caa9..7025135a 100644 --- a/problems/0062.不同路径.md +++ b/problems/0062.不同路径.md @@ -411,7 +411,7 @@ func uniquePaths(m int, n int) int { } ``` -### Javascript +### JavaScript ```Javascript var uniquePaths = function(m, n) { diff --git a/problems/0063.不同路径II.md b/problems/0063.不同路径II.md index daf3d8c5..13923abe 100644 --- a/problems/0063.不同路径II.md +++ b/problems/0063.不同路径II.md @@ -465,7 +465,7 @@ func uniquePathsWithObstacles(obstacleGrid [][]int) int { } ``` -### Javascript +### JavaScript ```Javascript var uniquePathsWithObstacles = function(obstacleGrid) { diff --git a/problems/0070.爬楼梯.md b/problems/0070.爬楼梯.md index 1d0b192f..a2f664a4 100644 --- a/problems/0070.爬楼梯.md +++ b/problems/0070.爬楼梯.md @@ -327,7 +327,7 @@ func climbStairs(n int) int { return dp[n] } ``` -### Javascript +### JavaScript ```Javascript var climbStairs = function(n) { // dp[i] 为第 i 阶楼梯有多少种方法爬到楼顶 diff --git a/problems/0072.编辑距离.md b/problems/0072.编辑距离.md index b8de8bdc..0da3bf50 100644 --- a/problems/0072.编辑距离.md +++ b/problems/0072.编辑距离.md @@ -313,7 +313,7 @@ func Min(args ...int) int { } ``` -### Javascript: +### JavaScript: ```javascript const minDistance = (word1, word2) => { diff --git a/problems/0077.组合.md b/problems/0077.组合.md index 3a271ff8..c4be5a38 100644 --- a/problems/0077.组合.md +++ b/problems/0077.组合.md @@ -468,7 +468,7 @@ func dfs(n int, k int, start int) { } ``` -### Javascript +### JavaScript 未剪枝: ```js diff --git a/problems/0078.子集.md b/problems/0078.子集.md index 723d99a1..0c368b41 100644 --- a/problems/0078.子集.md +++ b/problems/0078.子集.md @@ -246,7 +246,7 @@ func dfs(nums []int, start int) { } ``` -### Javascript +### JavaScript ```Javascript var subsets = function(nums) { diff --git a/problems/0084.柱状图中最大的矩形.md b/problems/0084.柱状图中最大的矩形.md index c08a3045..5c6f4073 100644 --- a/problems/0084.柱状图中最大的矩形.md +++ b/problems/0084.柱状图中最大的矩形.md @@ -474,7 +474,128 @@ class Solution: ### Go: -> 单调栈 +暴力解法 + +```go +func largestRectangleArea(heights []int) int { + sum := 0 + for i := 0; i < len(heights); i++ { + left, right := i, i + for left >= 0 { + if heights[left] < heights[i] { + break + } + left-- + } + for right < len(heights) { + if heights[right] < heights[i] { + break + } + right++ + } + w := right - left - 1 + h := heights[i] + sum = max(sum, w * h) + } + return sum +} + +func max(x, y int) int { + if x > y { + return x + } + return y +} +``` + +双指针解法 + +```go +func largestRectangleArea(heights []int) int { + size := len(heights) + minLeftIndex := make([]int, size) + minRightIndex := make([]int, size) + + // 记录每个柱子 左边第一个小于该柱子的下标 + minLeftIndex[0] = -1 // 注意这里初始化,防止下面while死循环 + for i := 1; i < size; i++ { + t := i - 1 + // 这里不是用if,而是不断向左寻找的过程 + for t >= 0 && heights[t] >= heights[i] { + t = minLeftIndex[t] + } + minLeftIndex[i] = t + } + // 记录每个柱子 右边第一个小于该柱子的下标 + minRightIndex[size - 1] = size; // 注意这里初始化,防止下面while死循环 + for i := size - 2; i >= 0; i-- { + t := i + 1 + // 这里不是用if,而是不断向右寻找的过程 + for t < size && heights[t] >= heights[i] { + t = minRightIndex[t] + } + minRightIndex[i] = t + } + // 求和 + result := 0 + for i := 0; i < size; i++ { + sum := heights[i] * (minRightIndex[i] - minLeftIndex[i] - 1) + result = max(sum, result) + } + return result +} + +func max(x, y int) int { + if x > y { + return x + } + return y +} +``` + +单调栈 + +```go +func largestRectangleArea(heights []int) int { + result := 0 + heights = append([]int{0}, heights...) // 数组头部加入元素0 + heights = append(heights, 0) // 数组尾部加入元素0 + st := []int{0} + + // 第一个元素已经入栈,从下标1开始 + for i := 1; i < len(heights); i++ { + if heights[i] > heights[st[len(st)-1]] { + st = append(st, i) + } else if heights[i] == heights[st[len(st)-1]] { + st = st[:len(st)-1] + st = append(st, i) + } else { + for len(st) > 0 && heights[i] < heights[st[len(st)-1]] { + mid := st[len(st)-1] + st = st[:len(st)-1] + if len(st) > 0 { + left := st[len(st)-1] + right := i + w := right - left - 1 + h := heights[mid] + result = max(result, w * h) + } + } + st = append(st, i) + } + } + return result +} + +func max(x, y int) int { + if x > y { + return x + } + return y +} +``` + +单调栈精简 ```go func largestRectangleArea(heights []int) int { diff --git a/problems/0090.子集II.md b/problems/0090.子集II.md index 939ef369..811d3cc0 100644 --- a/problems/0090.子集II.md +++ b/problems/0090.子集II.md @@ -376,7 +376,7 @@ func dfs(nums []int, start int) { ``` -### Javascript +### JavaScript ```Javascript diff --git a/problems/0096.不同的二叉搜索树.md b/problems/0096.不同的二叉搜索树.md index e0e77310..25d79aff 100644 --- a/problems/0096.不同的二叉搜索树.md +++ b/problems/0096.不同的二叉搜索树.md @@ -221,7 +221,7 @@ func numTrees(n int)int{ } ``` -### Javascript +### JavaScript ```Javascript const numTrees =(n) => { diff --git a/problems/0098.验证二叉搜索树.md b/problems/0098.验证二叉搜索树.md index 023eeea5..fb4ca7d8 100644 --- a/problems/0098.验证二叉搜索树.md +++ b/problems/0098.验证二叉搜索树.md @@ -22,7 +22,7 @@ ## 算法公开课 -**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[你对二叉搜索树了解的还不够! | LeetCode:98.验证二叉搜索树](https://www.bilibili.com/video/BV18P411n7Q4),相信结合视频在看本篇题解,更有助于大家对本题的理解**。 +**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[你对二叉搜索树了解的还不够! | LeetCode:98.验证二叉搜索树](https://www.bilibili.com/video/BV18P411n7Q4),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 ## 思路 diff --git a/problems/0102.二叉树的层序遍历.md b/problems/0102.二叉树的层序遍历.md index cad32414..98e1e98a 100644 --- a/problems/0102.二叉树的层序遍历.md +++ b/problems/0102.二叉树的层序遍历.md @@ -356,7 +356,7 @@ func levelOrder(root *TreeNode) (res [][]int) { } ``` -#### Javascript: +#### JavaScript: ```javascript var levelOrder = function(root) { @@ -759,7 +759,7 @@ func levelOrderBottom(root *TreeNode) [][]int { } ``` -#### Javascript: +#### JavaScript: ```javascript var levelOrderBottom = function (root) { @@ -1101,7 +1101,7 @@ func rightSideView(root *TreeNode) []int { } ``` -#### Javascript: +#### JavaScript: ```javascript var rightSideView = function(root) { @@ -1421,7 +1421,7 @@ func averageOfLevels(root *TreeNode) []float64 { } ``` -#### Javascript: +#### JavaScript: ```javascript var averageOfLevels = function(root) { @@ -2109,7 +2109,7 @@ func largestValues(root *TreeNode) []int { } ``` -#### Javascript: +#### JavaScript: ```javascript var largestValues = function (root) { diff --git a/problems/0104.二叉树的最大深度.md b/problems/0104.二叉树的最大深度.md index 607e195b..fdc90096 100644 --- a/problems/0104.二叉树的最大深度.md +++ b/problems/0104.二叉树的最大深度.md @@ -604,7 +604,7 @@ func maxDepth(root *Node) int { } ``` -### Javascript : +### JavaScript : 104.二叉树的最大深度 diff --git a/problems/0112.路径总和.md b/problems/0112.路径总和.md index e6ccc6ae..b97013e6 100644 --- a/problems/0112.路径总和.md +++ b/problems/0112.路径总和.md @@ -830,7 +830,7 @@ func traverse(node *TreeNode, result *[][]int, currPath *[]int, targetSum int) { } ``` -### Javascript +### JavaScript 0112.路径总和 diff --git a/problems/0115.不同的子序列.md b/problems/0115.不同的子序列.md index 8682b88d..cf24c4c1 100644 --- a/problems/0115.不同的子序列.md +++ b/problems/0115.不同的子序列.md @@ -265,7 +265,7 @@ func numDistinct(s string, t string) int { } ``` -### Javascript: +### JavaScript: ```javascript const numDistinct = (s, t) => { diff --git a/problems/0122.买卖股票的最佳时机II.md b/problems/0122.买卖股票的最佳时机II.md index e255723d..6663a66d 100644 --- a/problems/0122.买卖股票的最佳时机II.md +++ b/problems/0122.买卖股票的最佳时机II.md @@ -249,7 +249,7 @@ func max(a, b int) int { } ``` -### Javascript: +### JavaScript: 贪心 diff --git a/problems/0123.买卖股票的最佳时机III.md b/problems/0123.买卖股票的最佳时机III.md index 8e224a89..1b7c09d2 100644 --- a/problems/0123.买卖股票的最佳时机III.md +++ b/problems/0123.买卖股票的最佳时机III.md @@ -316,8 +316,9 @@ class Solution: ### Go: +> 版本一 + ```go -// 版本一 func maxProfit(prices []int) int { dp := make([][]int, len(prices)) for i := 0; i < len(prices); i++ { @@ -345,8 +346,9 @@ func max(a, b int) int { } ``` +> 版本二 + ```go -// 版本二 func maxProfit(prices []int) int { if len(prices) == 0 { return 0 @@ -371,8 +373,9 @@ func max(x, y int) int { } ``` +> 版本三 + ```go -// 版本三 func maxProfit(prices []int) int { if len(prices) == 0 { return 0 @@ -397,6 +400,26 @@ func max(x, y int) int { } ``` +> 版本四:一维 dp 易懂版本 + +```go +func maxProfit(prices []int) int { + dp := make([]int, 4) + dp[0] = -prices[0] + dp[2] = -prices[0] + + for _, price := range prices[1:] { + dc := slices.Clone(dp) // 这句话是关键,把前一天的 dp 状态保存下来,防止被覆盖掉,后面只用它,不用 dp,逻辑简单易懂 + dp[0] = max(dc[0], -price) + dp[1] = max(dc[1], dc[0] + price) + dp[2] = max(dc[2], dc[1] - price) + dp[3] = max(dc[3], dc[2] + price) + } + + return dp[3] +} +``` + ### JavaScript: > 版本一: diff --git a/problems/0134.加油站.md b/problems/0134.加油站.md index 5cf50b3e..0248760d 100644 --- a/problems/0134.加油站.md +++ b/problems/0134.加油站.md @@ -396,7 +396,7 @@ func canCompleteCircuit(gas []int, cost []int) int { } ``` -### Javascript +### JavaScript 暴力: ```js var canCompleteCircuit = function(gas, cost) { diff --git a/problems/0135.分发糖果.md b/problems/0135.分发糖果.md index 29eaa06d..eb2081fe 100644 --- a/problems/0135.分发糖果.md +++ b/problems/0135.分发糖果.md @@ -233,7 +233,7 @@ func findMax(num1 int, num2 int) int { } ``` -### Javascript +### JavaScript ```Javascript var candy = function(ratings) { let candys = new Array(ratings.length).fill(1) diff --git a/problems/0143.重排链表.md b/problems/0143.重排链表.md index 87075431..ccddef5b 100644 --- a/problems/0143.重排链表.md +++ b/problems/0143.重排链表.md @@ -38,7 +38,7 @@ public: cur = head; int i = 1; int j = vec.size() - 1; // i j为之前前后的双指针 - int count = 0; // 计数,偶数去后面,奇数取前面 + int count = 0; // 计数,偶数取后面,奇数取前面 while (i <= j) { if (count % 2 == 0) { cur->next = vec[j]; @@ -73,7 +73,7 @@ public: } cur = head; - int count = 0; // 计数,偶数去后面,奇数取前面 + int count = 0; // 计数,偶数取后面,奇数取前面 ListNode* node; while(que.size()) { if (count % 2 == 0) { @@ -338,8 +338,85 @@ class Solution: return pre ``` ### Go + ```go -# 方法三 分割链表 +// 方法一 数组模拟 +/** + * Definition for singly-linked list. + * type ListNode struct { + * Val int + * Next *ListNode + * } + */ +func reorderList(head *ListNode) { + vec := make([]*ListNode, 0) + cur := head + if cur == nil { + return + } + for cur != nil { + vec = append(vec, cur) + cur = cur.Next + } + cur = head + i := 1 + j := len(vec) - 1 // i j为前后的双指针 + count := 0 // 计数,偶数取后面,奇数取前面 + for i <= j { + if count % 2 == 0 { + cur.Next = vec[j] + j-- + } else { + cur.Next = vec[i] + i++ + } + cur = cur.Next + count++ + } + cur.Next = nil // 注意结尾 +} +``` + +```go +// 方法二 双向队列模拟 +/** + * Definition for singly-linked list. + * type ListNode struct { + * Val int + * Next *ListNode + * } + */ +func reorderList(head *ListNode) { + que := make([]*ListNode, 0) + cur := head + if cur == nil { + return + } + + for cur.Next != nil { + que = append(que, cur.Next) + cur = cur.Next + } + + cur = head + count := 0 // 计数,偶数取后面,奇数取前面 + for len(que) > 0 { + if count % 2 == 0 { + cur.Next = que[len(que)-1] + que = que[:len(que)-1] + } else { + cur.Next = que[0] + que = que[1:] + } + count++ + cur = cur.Next + } + cur.Next = nil // 注意结尾 +} +``` + +```go +// 方法三 分割链表 func reorderList(head *ListNode) { var slow=head var fast=head diff --git a/problems/0150.逆波兰表达式求值.md b/problems/0150.逆波兰表达式求值.md index 7d4031d7..5fb28c29 100644 --- a/problems/0150.逆波兰表达式求值.md +++ b/problems/0150.逆波兰表达式求值.md @@ -188,34 +188,21 @@ class Solution(object): return stack.pop() ``` -另一种可行,但因为使用eval相对较慢的方法: +另一种可行,但因为使用eval()相对较慢的方法: ```python -from operator import add, sub, mul - -def div(x, y): - # 使用整数除法的向零取整方式 - return int(x / y) if x * y > 0 else -(abs(x) // abs(y)) - class Solution(object): - op_map = {'+': add, '-': sub, '*': mul, '/': div} - - def evalRPN(self, tokens): - """ - :type tokens: List[str] - :rtype: int - """ + def evalRPN(self, tokens: List[str]) -> int: stack = [] for token in tokens: - if token in self.op_map: - op1 = stack.pop() - op2 = stack.pop() - operation = self.op_map[token] - stack.append(operation(op2, op1)) + # 判断是否为数字,因为isdigit()不识别负数,故需要排除第一位的符号 + if token.isdigit() or (len(token)>1 and token[1].isdigit()): + stack.append(token) else: - stack.append(int(token)) - return stack.pop() - - + op2 = stack.pop() + op1 = stack.pop() + # 由题意"The division always truncates toward zero",所以使用int()可以天然取整 + stack.append(str(int(eval(op1 + token + op2)))) + return int(stack.pop()) ``` ### Go: diff --git a/problems/0151.翻转字符串里的单词.md b/problems/0151.翻转字符串里的单词.md index 9a0cbea4..7c0b7cb1 100644 --- a/problems/0151.翻转字符串里的单词.md +++ b/problems/0151.翻转字符串里的单词.md @@ -513,6 +513,29 @@ class Solution: return "".join(result) ``` + +(版本五) 遇到空格就说明前面的是一个单词,把它加入到一个数组中。 + +```python +class Solution: + def reverseWords(self, s: str) -> str: + words = [] + word = '' + s += ' ' # 帮助处理最后一个字词 + + for char in s: + if char == ' ': # 遇到空格就说明前面的可能是一个单词 + if word != '': # 确认是单词,把它加入到一个数组中 + words.append(word) + word = '' # 清空当前单词 + continue + + word += char # 收集单词的字母 + + words.reverse() + return ' '.join(words) +``` + ### Go: 版本一: diff --git a/problems/0188.买卖股票的最佳时机IV.md b/problems/0188.买卖股票的最佳时机IV.md index 0b1622ac..cbba12c9 100644 --- a/problems/0188.买卖股票的最佳时机IV.md +++ b/problems/0188.买卖股票的最佳时机IV.md @@ -297,8 +297,7 @@ class Solution { ### Python: -版本一 - +> 版本一 ```python class Solution: def maxProfit(self, k: int, prices: List[int]) -> int: @@ -313,7 +312,8 @@ class Solution: dp[i][j+2] = max(dp[i-1][j+2], dp[i-1][j+1] + prices[i]) return dp[-1][2*k] ``` -版本二 + +> 版本二 ```python class Solution: def maxProfit(self, k: int, prices: List[int]) -> int: @@ -329,9 +329,31 @@ class Solution: dp[j] = max(dp[j],dp[j-1]+prices[i]) return dp[2*k] ``` + +> 版本三: 一维 dp 数组(易理解版本) +```python +class Solution: + def maxProfit(self, k: int, prices: List[int]) -> int: + dp = [0] * k * 2 + for i in range(k): + dp[i * 2] = -prices[0] + + for price in prices[1:]: + dc = dp.copy() # 这句话是关键,把前一天的 dp 状态保存下来,防止被覆盖掉,后面只用它,不用 dp,逻辑简单易懂 + + for i in range(2 * k): + if i % 2 == 1: + dp[i] = max(dc[i], dc[i - 1] + price) + else: + pre = 0 if i == 0 else dc[i - 1] + dp[i] = max(dc[i], pre - price) + + return dp[-1] +``` + ### Go: -版本一: +> 版本一: ```go // 买卖股票的最佳时机IV 动态规划 @@ -368,7 +390,7 @@ func max(a, b int) int { } ``` -版本二: 三维 dp数组 +> 版本二: 三维 dp数组 ```go func maxProfit(k int, prices []int) int { length := len(prices) @@ -443,7 +465,31 @@ func max(a, b int) int { } ``` +> 版本四:一维 dp 数组(易理解版本) +```go +func maxProfit(k int, prices []int) int { + dp := make([]int, 2 * k) + for i := range k { + dp[i * 2] = -prices[0] + } + + for j := 1; j < len(prices); j++ { + dc := slices.Clone(dp) // 这句话是关键,把前一天的 dp 状态保存下来,防止被覆盖掉,后面只用它,不用 dp,逻辑简单易懂 + + for i := range k * 2 { + if i % 2 == 1 { + dp[i] = max(dc[i], dc[i - 1] + prices[j]) + } else { + pre := 0; if i >= 1 { pre = dc[i - 1] } + dp[i] = max(dc[i], pre - prices[j]) + } + } + } + + return dp[2 * k - 1] +} +``` ### JavaScript: diff --git a/problems/0203.移除链表元素.md b/problems/0203.移除链表元素.md index f6b5ef6d..5a4bbb74 100644 --- a/problems/0203.移除链表元素.md +++ b/problems/0203.移除链表元素.md @@ -337,6 +337,37 @@ public ListNode removeElements(ListNode head, int val) { ``` +递归 + +```java +/** + * 时间复杂度 O(n) + * 空间复杂度 O(n) + * @param head + * @param val + * @return + */ +class Solution { + public ListNode removeElements(ListNode head, int val) { + if (head == null) { + return head; + } + + // 假设 removeElements() 返回后面完整的已经去掉val节点的子链表 + // 在当前递归层用当前节点接住后面的子链表 + // 随后判断当前层的node是否需要被删除,如果是,就返回 + // 也可以先判断是否需要删除当前node,但是这样条件语句会比较不好想 + head.next = removeElements(head.next, val); + if (head.val == val) { + return head.next; + } + return head; + + // 实际上就是还原一个从尾部开始重新构建链表的过程 + } +} +``` + ### Python: ```python @@ -737,7 +768,45 @@ public class Solution } } ``` +### Ruby# +```ruby +# 定义链表节点 +class ListNode + attr_accessor :val, :next + def initialize(val = 0, _next = nil) + @val = val + @next = _next + end +end + +# 删除链表中值为 val 的节点 +def remove_elements(head, val) + # 创建一个虚拟头节点,这样可以简化删除头节点的处理 + # 虚拟头节点的值为 0,指向当前链表的头节点 + dummy = ListNode.new(0) + dummy.next = head + + # 初始化当前节点为虚拟头节点 + current = dummy + + # 遍历链表,直到当前节点的下一个节点为空 + while current.next + # 如果当前节点的下一个节点的值等于 val + if current.next.val == val + # 跳过该节点,即将当前节点的 next 指向下一个节点的 next + current.next = current.next.next + else + # 否则继续遍历,当前节点向前移动 + current = current.next + end + end + + # 返回删除 val 后的新链表的头节点,虚拟头节点的 next 就是新的头节点 + dummy.next +end + +```

diff --git a/problems/0209.长度最小的子数组.md b/problems/0209.长度最小的子数组.md index e399ac90..c6d89976 100644 --- a/problems/0209.长度最小的子数组.md +++ b/problems/0209.长度最小的子数组.md @@ -266,7 +266,7 @@ var minSubArrayLen = function(target, nums) { }; ``` -### Typescript: +### TypeScript: ```typescript function minSubArrayLen(target: number, nums: number[]): number { diff --git a/problems/0226.翻转二叉树.md b/problems/0226.翻转二叉树.md index 55dc3cbf..c34ca4bf 100644 --- a/problems/0226.翻转二叉树.md +++ b/problems/0226.翻转二叉树.md @@ -81,7 +81,7 @@ if (root == NULL) return root; 3. 确定单层递归的逻辑 -因为是先前序遍历,所以先进行交换左右孩子节点,然后反转左子树,反转右子树。 +因为是前序遍历,所以先进行交换左右孩子节点,然后反转左子树,反转右子树。 ```cpp swap(root->left, root->right); @@ -348,14 +348,13 @@ class Solution: while stack: node = stack.pop() node.left, node.right = node.right, node.left + if node.right: + stack.append(node.right) if node.left: stack.append(node.left) - if node.right: - stack.append(node.right) return root ``` - 递归法:中序遍历: ```python # Definition for a binary tree node. @@ -374,7 +373,7 @@ class Solution: return root ``` -迭代法:中序遍历: +迭代法,伪中序遍历(结果是对的,看起来像是中序遍历,实际上它是前序遍历,只不过把中间节点处理逻辑放到了中间。还是要用'统一写法'才是真正的中序遍历): ```python # Definition for a binary tree node. # class TreeNode: @@ -386,18 +385,17 @@ class Solution: def invertTree(self, root: TreeNode) -> TreeNode: if not root: return None - stack = [root] + stack = [root] while stack: - node = stack.pop() - if node.left: - stack.append(node.left) - node.left, node.right = node.right, node.left - if node.left: - stack.append(node.left) + node = stack.pop() + if node.right: + stack.append(node.right) + node.left, node.right = node.right, node.left # 放到中间,依然是前序遍历 + if node.right: + stack.append(node.right) return root ``` - 递归法:后序遍历: ```python # Definition for a binary tree node. @@ -416,7 +414,7 @@ class Solution: return root ``` -迭代法:后序遍历: +迭代法,伪后序遍历(结果是对的,看起来像是后序遍历,实际上它是前序遍历,只不过把中间节点处理逻辑放到了最后。还是要用'统一写法'才是真正的后序遍历): ```python # Definition for a binary tree node. # class TreeNode: @@ -427,23 +425,19 @@ class Solution: class Solution: def invertTree(self, root: TreeNode) -> TreeNode: if not root: - return None - stack = [root] + return None + stack = [root] while stack: - node = stack.pop() + node = stack.pop() + if node.right: + stack.append(node.right) if node.left: stack.append(node.left) - if node.right: - stack.append(node.right) node.left, node.right = node.right, node.left return root ``` - - - - 迭代法:广度优先遍历(层序遍历): ```python # Definition for a binary tree node. diff --git a/problems/0232.用栈实现队列.md b/problems/0232.用栈实现队列.md index 7017107b..657567cf 100644 --- a/problems/0232.用栈实现队列.md +++ b/problems/0232.用栈实现队列.md @@ -113,7 +113,7 @@ public: ``` -* 时间复杂度: push和empty为O(1), pop和peek为O(n) +* 时间复杂度: 都为O(1)。pop和peek看起来像O(n),实际上一个循环n会被使用n次,最后还是O(1)。 * 空间复杂度: O(n) diff --git a/problems/0235.二叉搜索树的最近公共祖先.md b/problems/0235.二叉搜索树的最近公共祖先.md index 597c2dff..192bb031 100644 --- a/problems/0235.二叉搜索树的最近公共祖先.md +++ b/problems/0235.二叉搜索树的最近公共祖先.md @@ -38,7 +38,7 @@ ## 算法公开课 -**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[二叉搜索树找祖先就有点不一样了!| 235. 二叉搜索树的最近公共祖先](https://www.bilibili.com/video/BV1Zt4y1F7ww?share_source=copy_web),相信结合视频在看本篇题解,更有助于大家对本题的理解**。 +**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[二叉搜索树找祖先就有点不一样了!| 235. 二叉搜索树的最近公共祖先](https://www.bilibili.com/video/BV1Zt4y1F7ww?share_source=copy_web),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 ## 思路 diff --git a/problems/0236.二叉树的最近公共祖先.md b/problems/0236.二叉树的最近公共祖先.md index 2e94f7a7..8cd505a8 100644 --- a/problems/0236.二叉树的最近公共祖先.md +++ b/problems/0236.二叉树的最近公共祖先.md @@ -36,7 +36,7 @@ ## 算法公开课 -**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[自底向上查找,有点难度! | LeetCode:236. 二叉树的最近公共祖先](https://www.bilibili.com/video/BV1jd4y1B7E2),相信结合视频在看本篇题解,更有助于大家对本题的理解**。 +**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[自底向上查找,有点难度! | LeetCode:236. 二叉树的最近公共祖先](https://www.bilibili.com/video/BV1jd4y1B7E2),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 ## 思路 @@ -45,7 +45,7 @@ 那么二叉树如何可以自底向上查找呢? -回溯啊,二叉树回溯的过程就是从低到上。 +回溯啊,二叉树回溯的过程就是从底到上。 后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。 diff --git a/problems/0239.滑动窗口最大值.md b/problems/0239.滑动窗口最大值.md index 23bf615b..caa24d8d 100644 --- a/problems/0239.滑动窗口最大值.md +++ b/problems/0239.滑动窗口最大值.md @@ -299,7 +299,7 @@ class Solution { ``` ### Python: - +#### 解法一:使用自定义的单调队列类 ```python from collections import deque @@ -339,6 +339,35 @@ class Solution: return result ``` + +#### 解法二:直接用单调队列 +```python +from collections import deque +class Solution: + def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: + max_list = [] # 结果集合 + kept_nums = deque() # 单调队列 + + for i in range(len(nums)): + update_kept_nums(kept_nums, nums[i]) # 右侧新元素加入 + + if i >= k and nums[i - k] == kept_nums[0]: # 左侧旧元素如果等于单调队列头元素,需要移除头元素 + kept_nums.popleft() + + if i >= k - 1: + max_list.append(kept_nums[0]) + + return max_list + +def update_kept_nums(kept_nums, num): # num 是新加入的元素 + # 所有小于新元素的队列尾部元素,在新元素出现后,都是没有价值的,都需要被移除 + while kept_nums and num > kept_nums[-1]: + kept_nums.pop() + + kept_nums.append(num) + +``` + ### Go: ```go @@ -401,7 +430,7 @@ func maxSlidingWindow(nums []int, k int) []int { } ``` -### Javascript: +### JavaScript: ```javascript /** diff --git a/problems/0279.完全平方数.md b/problems/0279.完全平方数.md index 570632bd..dc5a7e9e 100644 --- a/problems/0279.完全平方数.md +++ b/problems/0279.完全平方数.md @@ -346,7 +346,7 @@ func min(a, b int) int { } ``` -### Javascript: +### JavaScript: ```Javascript // 先遍历物品,再遍历背包 diff --git a/problems/0300.最长上升子序列.md b/problems/0300.最长上升子序列.md index f1a146b7..442938c0 100644 --- a/problems/0300.最长上升子序列.md +++ b/problems/0300.最长上升子序列.md @@ -248,7 +248,7 @@ func lengthOfLIS(nums []int ) int { } ``` -### Javascript: +### JavaScript: ```javascript const lengthOfLIS = (nums) => { diff --git a/problems/0309.最佳买卖股票时机含冷冻期.md b/problems/0309.最佳买卖股票时机含冷冻期.md index 4913b8bd..b98a416c 100644 --- a/problems/0309.最佳买卖股票时机含冷冻期.md +++ b/problems/0309.最佳买卖股票时机含冷冻期.md @@ -274,7 +274,7 @@ class Solution { ``` ### Python: -版本一 +> 版本一 ```python from typing import List @@ -294,7 +294,8 @@ class Solution: return max(dp[n-1][3], dp[n-1][1], dp[n-1][2]) # 返回最后一天不持有股票的最大利润 ``` -版本二 + +> 版本二 ```python class Solution: def maxProfit(self, prices: List[int]) -> int: @@ -320,6 +321,36 @@ class Solution: return max(dp[-1][1], dp[-1][2]) ``` + +> 版本三 +```python +class Solution: + def maxProfit(self, prices: List[int]) -> int: + # 0: holding stocks + # (1) keep holding stocks: dp[i][0] = dp[i - 1][0] + # (2) buy stocks: dp[i][0] = dp[i - 1][1] - price, or dp[i - 1][3] - price + # 1: keep no stocks: dp[i][1] = dp[i - 1][1] + # 2: sell stocks: dp[i][2] = dp[i - 1][0] + price + # 3: cooldown day: dp[i][3] = dp[i - 1][2] + dp = [-prices[0], 0, 0, 0] + + for price in prices[1:]: + dc = dp.copy() # 这句话是关键,把前一天的 dp 状态保存下来,防止被覆盖掉,后面只用它,不用 dp,逻辑简单易懂 + dp[0] = max( + dc[0], + dc[1] - price, + dc[3] - price + ) + dp[1] = max( + dc[1], + dc[3] + ) + dp[2] = dc[0] + price + dp[3] = dc[2] + + return max(dp) +``` + ### Go: ```go @@ -393,7 +424,7 @@ func max(a, b int) int { -### Javascript: +### JavaScript: > 不同的状态定义 感觉更容易理解些 ```javascript diff --git a/problems/0322.零钱兑换.md b/problems/0322.零钱兑换.md index 0ed5cf68..e55e20be 100644 --- a/problems/0322.零钱兑换.md +++ b/problems/0322.零钱兑换.md @@ -427,7 +427,7 @@ impl Solution { } ``` -### Javascript: +### JavaScript: ```javascript // 遍历物品 diff --git a/problems/0332.重新安排行程.md b/problems/0332.重新安排行程.md index 1d9c524b..78e14074 100644 --- a/problems/0332.重新安排行程.md +++ b/problems/0332.重新安排行程.md @@ -172,7 +172,7 @@ if (result.size() == ticketNum + 1) { 回溯的过程中,如何遍历一个机场所对应的所有机场呢? -这里刚刚说过,在选择映射函数的时候,不能选择`unordered_map> targets`, 因为一旦有元素增删multiset的迭代器就会失效,当然可能有牛逼的容器删除元素迭代器不会失效,这里就不在讨论了。 +这里刚刚说过,在选择映射函数的时候,不能选择`unordered_map> targets`, 因为一旦有元素增删multiset的迭代器就会失效,当然可能有牛逼的容器删除元素迭代器不会失效,这里就不再讨论了。 **可以说本题既要找到一个对数据进行排序的容器,而且还要容易增删元素,迭代器还不能失效**。 @@ -535,7 +535,7 @@ func findItinerary(tickets [][]string) []string { } ``` -### Javascript +### JavaScript ```Javascript diff --git a/problems/0337.打家劫舍III.md b/problems/0337.打家劫舍III.md index 12e31aba..a3130df7 100644 --- a/problems/0337.打家劫舍III.md +++ b/problems/0337.打家劫舍III.md @@ -477,14 +477,7 @@ func max(x, y int) int { ```go func rob(root *TreeNode) int { res := robTree(root) - return max(res[0], res[1]) -} - -func max(a, b int) int { - if a > b { - return a - } - return b + return slices.Max(res) } func robTree(cur *TreeNode) []int { @@ -498,7 +491,7 @@ func robTree(cur *TreeNode) []int { // 考虑去偷当前的屋子 robCur := cur.Val + left[0] + right[0] // 考虑不去偷当前的屋子 - notRobCur := max(left[0], left[1]) + max(right[0], right[1]) + notRobCur := slices.Max(left) + slices.Max(right) // 注意顺序:0:不偷,1:去偷 return []int{notRobCur, robCur} diff --git a/problems/0343.整数拆分.md b/problems/0343.整数拆分.md index 3ba23e52..5d0110f6 100644 --- a/problems/0343.整数拆分.md +++ b/problems/0343.整数拆分.md @@ -385,7 +385,7 @@ func integerBreak(n int) int { } ``` -### Javascript +### JavaScript ```Javascript var integerBreak = function(n) { let dp = new Array(n + 1).fill(0) diff --git a/problems/0376.摆动序列.md b/problems/0376.摆动序列.md index 9e6714ce..e2ea9904 100644 --- a/problems/0376.摆动序列.md +++ b/problems/0376.摆动序列.md @@ -72,7 +72,7 @@ #### 情况一:上下坡中有平坡 -例如 [1,2,2,2,1]这样的数组,如图: +例如 [1,2,2,2,2,1]这样的数组,如图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230106170449.png) @@ -466,7 +466,7 @@ func max(a, b int) int { } ``` -### Javascript +### JavaScript **贪心** diff --git a/problems/0377.组合总和Ⅳ.md b/problems/0377.组合总和Ⅳ.md index e96f8dc6..ba8546c6 100644 --- a/problems/0377.组合总和Ⅳ.md +++ b/problems/0377.组合总和Ⅳ.md @@ -254,7 +254,7 @@ func combinationSum4(nums []int, target int) int { } ``` -### Javascript: +### JavaScript: ```javascript const combinationSum4 = (nums, target) => { diff --git a/problems/0406.根据身高重建队列.md b/problems/0406.根据身高重建队列.md index d92a7f3f..d6fc415b 100644 --- a/problems/0406.根据身高重建队列.md +++ b/problems/0406.根据身高重建队列.md @@ -270,7 +270,7 @@ func reconstructQueue(people [][]int) [][]int { } ``` -### Javascript +### JavaScript ```Javascript var reconstructQueue = function(people) { diff --git a/problems/0435.无重叠区间.md b/problems/0435.无重叠区间.md index 20961602..d6321315 100644 --- a/problems/0435.无重叠区间.md +++ b/problems/0435.无重叠区间.md @@ -311,7 +311,7 @@ func min(a, b int) int { } ``` -### Javascript +### JavaScript - 按右边界排序 ```Javascript var eraseOverlapIntervals = function(intervals) { diff --git a/problems/0450.删除二叉搜索树中的节点.md b/problems/0450.删除二叉搜索树中的节点.md index cab9880a..831655e8 100644 --- a/problems/0450.删除二叉搜索树中的节点.md +++ b/problems/0450.删除二叉搜索树中的节点.md @@ -801,6 +801,40 @@ impl Solution { } ``` +### Ruby +> 递归法: +```ruby +# @param {TreeNode} root +# @param {Integer} key +# @return {TreeNode} +def delete_node(root, key) + return nil if root.nil? + + right = root.right + left = root.left + + if root.val == key + return right if left.nil? + return left if right.nil? + + node = right + while node.left + node = node.left + end + node.left = left + + return right + end + + if root.val > key + root.left = delete_node(left, key) + else + root.right = delete_node(right, key) + end + + return root +end +```

diff --git a/problems/0452.用最少数量的箭引爆气球.md b/problems/0452.用最少数量的箭引爆气球.md index 2099275d..14456f92 100644 --- a/problems/0452.用最少数量的箭引爆气球.md +++ b/problems/0452.用最少数量的箭引爆气球.md @@ -226,7 +226,7 @@ func min(a, b int) int { } ``` -### Javascript +### JavaScript ```Javascript var findMinArrowShots = function(points) { points.sort((a, b) => { diff --git a/problems/0455.分发饼干.md b/problems/0455.分发饼干.md index 22dd7570..5e8fb730 100644 --- a/problems/0455.分发饼干.md +++ b/problems/0455.分发饼干.md @@ -278,7 +278,7 @@ pub fn find_content_children(mut children: Vec, mut cookies: Vec) -> i } ``` -### Javascript +### JavaScript ```js var findContentChildren = function (g, s) { diff --git a/problems/0474.一和零.md b/problems/0474.一和零.md index 72d62243..e514e729 100644 --- a/problems/0474.一和零.md +++ b/problems/0474.一和零.md @@ -362,7 +362,7 @@ func max(a,b int) int { } ``` -### Javascript +### JavaScript ```javascript const findMaxForm = (strs, m, n) => { const dp = Array.from(Array(m+1), () => Array(n+1).fill(0)); diff --git a/problems/0491.递增子序列.md b/problems/0491.递增子序列.md index 8f642a5f..7832095a 100644 --- a/problems/0491.递增子序列.md +++ b/problems/0491.递增子序列.md @@ -375,7 +375,7 @@ func dfs(nums []int, start int) { } ``` -### Javascript +### JavaScript ```Javascript diff --git a/problems/0494.目标和.md b/problems/0494.目标和.md index 75e09538..6ddf774d 100644 --- a/problems/0494.目标和.md +++ b/problems/0494.目标和.md @@ -705,6 +705,31 @@ class Solution: ``` ### Go +回溯法思路 +```go +func findTargetSumWays(nums []int, target int) int { + var result int + var backtracking func(nums []int, target int, index int, currentSum int) + + backtracking = func(nums []int, target int, index int, currentSum int) { + if index == len(nums) { + if currentSum == target { + result++ + } + return + } + + // 选择加上当前数字 + backtracking(nums, target, index+1, currentSum+nums[index]) + + // 选择减去当前数字 + backtracking(nums, target, index+1, currentSum-nums[index]) + } + + backtracking(nums, target, 0, 0) + return result +} +``` 二维dp ```go func findTargetSumWays(nums []int, target int) int { @@ -790,7 +815,7 @@ func abs(x int) int { } ``` -### Javascript +### JavaScript ```javascript const findTargetSumWays = (nums, target) => { diff --git a/problems/0496.下一个更大元素I.md b/problems/0496.下一个更大元素I.md index 54182d30..02e73a58 100644 --- a/problems/0496.下一个更大元素I.md +++ b/problems/0496.下一个更大元素I.md @@ -195,6 +195,62 @@ public: 建议大家把情况一二三想清楚了,先写出版本一的代码,然后在其基础上在做精简! ## 其他语言版本 + +### C + +``` C +/* 先用单调栈的方法计算出结果,再根据nums1中的元素去查找对应的结果 */ +/** + * Note: The returned array must be malloced, assume caller calls free(). + */ +int* nextGreaterElement(int* nums1, int nums1Size, int* nums2, int nums2Size, int* returnSize) { + + /* stcak */ + int top = -1; + int stack_len = nums2Size; + int stack[stack_len]; + //memset(stack, 0x00, sizeof(stack)); + + /* nums2 result */ + int* result_nums2 = (int *)malloc(sizeof(int) * nums2Size); + //memset(result_nums2, 0x00, sizeof(int) * nums2Size); + + /* result */ + int* result = (int *)malloc(sizeof(int) * nums1Size); + //memset(result, 0x00, sizeof(int) * nums1Size); + *returnSize = nums1Size; + + /* init */ + stack[++top] = 0; /* stack loaded with array subscripts */ + + for (int i = 0; i < nums2Size; i++) { + result_nums2[i] = -1; + } + + /* get the result_nums2 */ + for (int i = 1; i < nums2Size; i++) { + if (nums2[i] <= nums2[stack[top]]) { + stack[++top] = i; /* push */ + } else { + while ((top >= 0) && (nums2[i] > nums2[stack[top]])) { + result_nums2[stack[top]] = nums2[i]; + top--; /* pop */ + } + stack[++top] = i; + } + } + + /* get the result */ + for (int i = 0; i < nums1Size; i++) { + for (int j = 0; j < nums2Size; j++) { + if (nums1[i] == nums2[j]) { + result[i] = result_nums2[j]; + } + } + } + return result; +} +``` ### Java ```java diff --git a/problems/0501.二叉搜索树中的众数.md b/problems/0501.二叉搜索树中的众数.md index 93b3fb54..c89f8031 100644 --- a/problems/0501.二叉搜索树中的众数.md +++ b/problems/0501.二叉搜索树中的众数.md @@ -35,7 +35,7 @@ ## 算法公开课 -**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[不仅双指针,还有代码技巧可以惊艳到你! | LeetCode:501.二叉搜索树中的众数](https://www.bilibili.com/video/BV1fD4y117gp),相信结合视频在看本篇题解,更有助于大家对本题的理解**。 +**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[不仅双指针,还有代码技巧可以惊艳到你! | LeetCode:501.二叉搜索树中的众数](https://www.bilibili.com/video/BV1fD4y117gp),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 ## 思路 diff --git a/problems/0503.下一个更大元素II.md b/problems/0503.下一个更大元素II.md index 5751bb91..b466337d 100644 --- a/problems/0503.下一个更大元素II.md +++ b/problems/0503.下一个更大元素II.md @@ -185,7 +185,7 @@ class Solution: > 版本二:针对版本一的优化 -```python3 +```python class Solution: def nextGreaterElements(self, nums: List[int]) -> List[int]: res = [-1] * len(nums) @@ -213,6 +213,40 @@ class Solution: ### Go: ```go +// 版本一 +func nextGreaterElements(nums []int) []int { + // 拼接一个新的nums + numsNew := make([]int, len(nums) * 2) + copy(numsNew, nums) + copy(numsNew[len(nums):], nums) + // 用新的nums大小来初始化result + result := make([]int, len(numsNew)) + for i := range result { + result[i] = -1 + } + + // 开始单调栈 + st := []int{0} + for i := 1; i < len(numsNew); i++ { + if numsNew[i] < numsNew[st[len(st)-1]] { + st = append(st, i) + } else if numsNew[i] == numsNew[st[len(st)-1]] { + st = append(st, i) + } else { + for len(st) > 0 && numsNew[i] > numsNew[st[len(st)-1]] { + result[st[len(st)-1]] = numsNew[i] + st = st[:len(st)-1] + } + st = append(st, i) + } + } + result = result[:len(result)/2] + return result +} +``` + +```go +// 版本二 func nextGreaterElements(nums []int) []int { length := len(nums) result := make([]int,length) diff --git a/problems/0509.斐波那契数.md b/problems/0509.斐波那契数.md index 21b07802..1c4127fc 100644 --- a/problems/0509.斐波那契数.md +++ b/problems/0509.斐波那契数.md @@ -292,7 +292,7 @@ func fib(n int) int { return c } ``` -### Javascript +### JavaScript 解法一 ```Javascript var fib = function(n) { diff --git a/problems/0516.最长回文子序列.md b/problems/0516.最长回文子序列.md index af36b94d..166310aa 100644 --- a/problems/0516.最长回文子序列.md +++ b/problems/0516.最长回文子序列.md @@ -224,7 +224,7 @@ func longestPalindromeSubseq(s string) int { } ``` -### Javascript: +### JavaScript: ```javascript const longestPalindromeSubseq = (s) => { diff --git a/problems/0518.零钱兑换II.md b/problems/0518.零钱兑换II.md index 2c82027f..833d5ed6 100644 --- a/problems/0518.零钱兑换II.md +++ b/problems/0518.零钱兑换II.md @@ -498,7 +498,7 @@ impl Solution { } ``` -### Javascript: +### JavaScript: ```javascript const change = (amount, coins) => { diff --git a/problems/0530.二叉搜索树的最小绝对差.md b/problems/0530.二叉搜索树的最小绝对差.md index 7fe64ad2..2533a618 100644 --- a/problems/0530.二叉搜索树的最小绝对差.md +++ b/problems/0530.二叉搜索树的最小绝对差.md @@ -21,7 +21,7 @@ ## 算法公开课 -**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[二叉搜索树中,需要掌握如何双指针遍历!| LeetCode:530.二叉搜索树的最小绝对差](https://www.bilibili.com/video/BV1DD4y11779),相信结合视频在看本篇题解,更有助于大家对本题的理解**。 +**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[二叉搜索树中,需要掌握如何双指针遍历!| LeetCode:530.二叉搜索树的最小绝对差](https://www.bilibili.com/video/BV1DD4y11779),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 ## 思路 diff --git a/problems/0541.反转字符串II.md b/problems/0541.反转字符串II.md index 5e75d3c3..b3e7b022 100644 --- a/problems/0541.反转字符串II.md +++ b/problems/0541.反转字符串II.md @@ -282,7 +282,7 @@ class Solution: return ''.join(res) ``` -### Python3 (v2): +#### Python3 (v2): ```python class Solution: @@ -297,6 +297,21 @@ class Solution: return s ``` +#### Python3 (v3): + +```python +class Solution: + def reverseStr(self, s: str, k: int) -> str: + i = 0 + chars = list(s) + + while i < len(chars): + chars[i:i + k] = chars[i:i + k][::-1] # 反转后,更改原值为反转后值 + i += k * 2 + + return ''.join(chars) +``` + ### Go: ```go diff --git a/problems/0583.两个字符串的删除操作.md b/problems/0583.两个字符串的删除操作.md index 14a55631..b9f9ad96 100644 --- a/problems/0583.两个字符串的删除操作.md +++ b/problems/0583.两个字符串的删除操作.md @@ -33,7 +33,7 @@ dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。 -这里dp数组的定义有点点绕,大家要撸清思路。 +这里dp数组的定义有点点绕,大家要理清思路。 2. 确定递推公式 @@ -255,6 +255,8 @@ class Solution(object): ``` ### Go: +动态规划一 + ```go func minDistance(word1 string, word2 string) int { dp := make([][]int, len(word1)+1) @@ -287,7 +289,39 @@ func min(a, b int) int { return b } ``` -### Javascript: + + +动态规划二 + +```go +func minDistance(word1 string, word2 string) int { + dp := make([][]int, len(word1) + 1) + for i := range dp { + dp[i] = make([]int, len(word2) + 1) + } + for i := 1; i <= len(word1); i++ { + for j := 1; j <= len(word2); j++ { + if word1[i-1] == word2[j-1] { + dp[i][j] = dp[i-1][j-1] + 1 + } else { + dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + } + } + } + return len(word1) + len(word2) - dp[len(word1)][len(word2)] * 2 +} + +func max(x, y int) int { + if x > y { + return x + } + return y +} +``` + + +### JavaScript: + ```javascript // 方法一 diff --git a/problems/0647.回文子串.md b/problems/0647.回文子串.md index 89e4ad11..cf32d7ed 100644 --- a/problems/0647.回文子串.md +++ b/problems/0647.回文子串.md @@ -102,7 +102,7 @@ dp[i][j]可以初始化为true么? 当然不行,怎能刚开始就全都匹 4. 确定遍历顺序 -遍历顺序可有有点讲究了。 +遍历顺序可就有点讲究了。 首先从递推公式中可以看出,情况三是根据dp[i + 1][j - 1]是否为true,在对dp[i][j]进行赋值true的。 @@ -465,7 +465,7 @@ func countSubstrings(s string) int { } ``` -### Javascript: +### JavaScript: > 动态规划 ```javascript diff --git a/problems/0669.修剪二叉搜索树.md b/problems/0669.修剪二叉搜索树.md index aef84659..32573386 100644 --- a/problems/0669.修剪二叉搜索树.md +++ b/problems/0669.修剪二叉搜索树.md @@ -22,7 +22,7 @@ ## 算法公开课 -**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[你修剪的方式不对,我来给你纠正一下!| LeetCode:669. 修剪二叉搜索树](https://www.bilibili.com/video/BV17P41177ud?share_source=copy_web),相信结合视频在看本篇题解,更有助于大家对本题的理解**。 +**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[你修剪的方式不对,我来给你纠正一下!| LeetCode:669. 修剪二叉搜索树](https://www.bilibili.com/video/BV17P41177ud?share_source=copy_web),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 ## 思路 diff --git a/problems/0674.最长连续递增序列.md b/problems/0674.最长连续递增序列.md index 57a38404..8b967092 100644 --- a/problems/0674.最长连续递增序列.md +++ b/problems/0674.最长连续递增序列.md @@ -359,7 +359,7 @@ impl Solution { ``` -### Javascript: +### JavaScript: > 动态规划: ```javascript diff --git a/problems/0707.设计链表.md b/problems/0707.设计链表.md index 0cb2f2f2..ed1726d9 100644 --- a/problems/0707.设计链表.md +++ b/problems/0707.设计链表.md @@ -422,38 +422,38 @@ void myLinkedListFree(MyLinkedList* obj) { ```Java //单链表 -class ListNode { - int val; - ListNode next; - ListNode(){} - ListNode(int val) { - this.val=val; - } -} class MyLinkedList { + + class ListNode { + int val; + ListNode next; + ListNode(int val) { + this.val=val; + } + } //size存储链表元素的个数 - int size; - //虚拟头结点 - ListNode head; + private int size; + //注意这里记录的是虚拟头结点 + private ListNode head; //初始化链表 public MyLinkedList() { - size = 0; - head = new ListNode(0); + this.size = 0; + this.head = new ListNode(0); } - //获取第index个节点的数值,注意index是从0开始的,第0个节点就是头结点 + //获取第index个节点的数值,注意index是从0开始的,第0个节点就是虚拟头结点 public int get(int index) { //如果index非法,返回-1 if (index < 0 || index >= size) { return -1; } - ListNode currentNode = head; - //包含一个虚拟头节点,所以查找第 index+1 个节点 + ListNode cur = head; + //第0个节点是虚拟头节点,所以查找第 index+1 个节点 for (int i = 0; i <= index; i++) { - currentNode = currentNode.next; + cur = cur.next; } - return currentNode.val; + return cur.val; } public void addAtHead(int val) { @@ -473,7 +473,6 @@ class MyLinkedList { while (cur.next != null) { cur = cur.next; } - cur.next = newNode; size++; @@ -485,55 +484,53 @@ class MyLinkedList { // 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点 // 如果 index 大于链表的长度,则返回空 public void addAtIndex(int index, int val) { - if (index > size) { + if (index < 0 || index > size) { return; } - if (index < 0) { - index = 0; - } - size++; + //找到要插入节点的前驱 - ListNode pred = head; + ListNode pre = head; for (int i = 0; i < index; i++) { - pred = pred.next; + pre = pre.next; } - ListNode toAdd = new ListNode(val); - toAdd.next = pred.next; - pred.next = toAdd; + ListNode newNode = new ListNode(val); + newNode.next = pre.next; + pre.next = newNode; + size++; } - //删除第index个节点 public void deleteAtIndex(int index) { if (index < 0 || index >= size) { return; } - size--; - //因为有虚拟头节点,所以不用对Index=0的情况进行特殊处理 - ListNode pred = head; + + //因为有虚拟头节点,所以不用对index=0的情况进行特殊处理 + ListNode pre = head; for (int i = 0; i < index ; i++) { - pred = pred.next; + pre = pre.next; } - pred.next = pred.next.next; + pre.next = pre.next.next; + size--; } } +``` +```Java //双链表 -class ListNode{ - int val; - ListNode next,prev; - ListNode() {}; - ListNode(int val){ - this.val = val; - } -} - - class MyLinkedList { + class ListNode{ + int val; + ListNode next, prev; + ListNode(int val){ + this.val = val; + } + } + //记录链表中元素的数量 - int size; + private int size; //记录链表的虚拟头结点和尾结点 - ListNode head,tail; + private ListNode head, tail; public MyLinkedList() { //初始化操作 @@ -541,25 +538,25 @@ class MyLinkedList { this.head = new ListNode(0); this.tail = new ListNode(0); //这一步非常关键,否则在加入头结点的操作中会出现null.next的错误!!! - head.next=tail; - tail.prev=head; + this.head.next = tail; + this.tail.prev = head; } public int get(int index) { //判断index是否有效 - if(index>=size){ + if(index < 0 || index >= size){ return -1; } - ListNode cur = this.head; + ListNode cur = head; //判断是哪一边遍历时间更短 if(index >= size / 2){ //tail开始 cur = tail; - for(int i=0; i< size-index; i++){ + for(int i = 0; i < size - index; i++){ cur = cur.prev; } }else{ - for(int i=0; i<= index; i++){ + for(int i = 0; i <= index; i++){ cur = cur.next; } } @@ -568,24 +565,23 @@ class MyLinkedList { public void addAtHead(int val) { //等价于在第0个元素前添加 - addAtIndex(0,val); + addAtIndex(0, val); } public void addAtTail(int val) { //等价于在最后一个元素(null)前添加 - addAtIndex(size,val); + addAtIndex(size, val); } public void addAtIndex(int index, int val) { - //index大于链表长度 - if(index>size){ + //判断index是否有效 + if(index < 0 || index > size){ return; } - size++; //找到前驱 - ListNode pre = this.head; - for(int i=0; i=size){ + //判断index是否有效 + if(index < 0 || index >= size){ return; } + //删除操作 - size--; - ListNode pre = this.head; - for(int i=0; i { diff --git a/problems/0738.单调递增的数字.md b/problems/0738.单调递增的数字.md index 3d46d5ad..ff438e98 100644 --- a/problems/0738.单调递增的数字.md +++ b/problems/0738.单调递增的数字.md @@ -190,9 +190,9 @@ class Solution: 贪心(版本一) ```python class Solution: - def monotoneIncreasingDigits(self, N: int) -> int: + def monotoneIncreasingDigits(self, n: int) -> int: # 将整数转换为字符串 - strNum = str(N) + strNum = str(n) # flag用来标记赋值9从哪里开始 # 设置为字符串长度,为了防止第二个for循环在flag没有被赋值的情况下执行 flag = len(strNum) @@ -216,9 +216,9 @@ class Solution: 贪心(版本二) ```python class Solution: - def monotoneIncreasingDigits(self, N: int) -> int: + def monotoneIncreasingDigits(self, n: int) -> int: # 将整数转换为字符串 - strNum = list(str(N)) + strNum = list(str(n)) # 从右往左遍历字符串 for i in range(len(strNum) - 1, 0, -1): @@ -238,9 +238,9 @@ class Solution: ```python class Solution: - def monotoneIncreasingDigits(self, N: int) -> int: + def monotoneIncreasingDigits(self, n: int) -> int: # 将整数转换为字符串 - strNum = list(str(N)) + strNum = list(str(n)) # 从右往左遍历字符串 for i in range(len(strNum) - 1, 0, -1): @@ -258,8 +258,8 @@ class Solution: ```python class Solution: - def monotoneIncreasingDigits(self, N: int) -> int: - strNum = str(N) + def monotoneIncreasingDigits(self, n: int) -> int: + strNum = str(n) for i in range(len(strNum) - 1, 0, -1): # 如果当前字符比前一个字符小,说明需要修改前一个字符 if strNum[i - 1] > strNum[i]: @@ -272,12 +272,12 @@ class Solution: ``` ### Go ```go -func monotoneIncreasingDigits(N int) int { +func monotoneIncreasingDigits(n int) int { s := strconv.Itoa(N)//将数字转为字符串,方便使用下标 ss := []byte(s)//将字符串转为byte数组,方便更改。 n := len(ss) if n <= 1 { - return N + return n } for i := n-1; i > 0; i-- { if ss[i-1] > ss[i] { //前一个大于后一位,前一位减1,后面的全部置为9 @@ -292,7 +292,7 @@ func monotoneIncreasingDigits(N int) int { } ``` -### Javascript +### JavaScript ```Javascript var monotoneIncreasingDigits = function(n) { n = n.toString() diff --git a/problems/0739.每日温度.md b/problems/0739.每日温度.md index 45af5286..dd633aed 100644 --- a/problems/0739.每日温度.md +++ b/problems/0739.每日温度.md @@ -215,6 +215,38 @@ public: ## 其他语言版本 +### C: + +```C +/** + * Note: The returned array must be malloced, assume caller calls free(). + */ +int* dailyTemperatures(int* temperatures, int temperaturesSize, int* returnSize) { + int len = temperaturesSize; + *returnSize = len; + + int *result = (int *)malloc(sizeof(int) * len); + memset(result, 0x00, sizeof(int) * len); + + int stack[len]; + memset(stack, 0x00, sizeof(stack)); + int top = 0; + + for (int i = 1; i < len; i++) { + if (temperatures[i] <= temperatures[stack[top]]) { /* push */ + stack[++top] = i; + } else { + while (top >= 0 && temperatures[i] > temperatures[stack[top]]) { /* stack not empty */ + result[stack[top]] = i - stack[top]; + top--; /* pop */ + } + stack[++top] = i; /* push */ + } + } + return result; +} +``` + ### Java: ```java diff --git a/problems/0763.划分字母区间.md b/problems/0763.划分字母区间.md index 52186927..19d862db 100644 --- a/problems/0763.划分字母区间.md +++ b/problems/0763.划分字母区间.md @@ -312,7 +312,7 @@ func max(a, b int) int { } ``` -### Javascript +### JavaScript ```Javascript var partitionLabels = function(s) { let hash = {} diff --git a/problems/0860.柠檬水找零.md b/problems/0860.柠檬水找零.md index 804ff13c..b7887d45 100644 --- a/problems/0860.柠檬水找零.md +++ b/problems/0860.柠檬水找零.md @@ -226,7 +226,7 @@ func lemonadeChange(bills []int) bool { } ``` -### Javascript +### JavaScript ```Javascript var lemonadeChange = function(bills) { let fiveCount = 0 diff --git a/problems/0922.按奇偶排序数组II.md b/problems/0922.按奇偶排序数组II.md index 1ac6800c..28680dbf 100644 --- a/problems/0922.按奇偶排序数组II.md +++ b/problems/0922.按奇偶排序数组II.md @@ -11,9 +11,9 @@ [力扣题目链接](https://leetcode.cn/problems/sort-array-by-parity-ii/) -给定一个非负整数数组 A, A 中一半整数是奇数,一半整数是偶数。 +给定一个非负整数数组 nums, nums 中一半整数是奇数,一半整数是偶数。 -对数组进行排序,以便当 A[i] 为奇数时,i 也是奇数;当 A[i] 为偶数时, i 也是偶数。 +对数组进行排序,以便当 nums[i] 为奇数时,i 也是奇数;当 nums[i] 为偶数时, i 也是偶数。 你可以返回任何满足上述条件的数组作为答案。 @@ -35,17 +35,17 @@ ```CPP class Solution { public: - vector sortArrayByParityII(vector& A) { - vector even(A.size() / 2); // 初始化就确定数组大小,节省开销 - vector odd(A.size() / 2); - vector result(A.size()); + vector sortArrayByParityII(vector& nums) { + vector even(nums.size() / 2); // 初始化就确定数组大小,节省开销 + vector odd(nums.size() / 2); + vector result(nums.size()); int evenIndex = 0; int oddIndex = 0; int resultIndex = 0; - // 把A数组放进偶数数组,和奇数数组 - for (int i = 0; i < A.size(); i++) { - if (A[i] % 2 == 0) even[evenIndex++] = A[i]; - else odd[oddIndex++] = A[i]; + // 把nums数组放进偶数数组,和奇数数组 + for (int i = 0; i < nums.size(); i++) { + if (nums[i] % 2 == 0) even[evenIndex++] = nums[i]; + else odd[oddIndex++] = nums[i]; } // 把偶数数组,奇数数组分别放进result数组中 for (int i = 0; i < evenIndex; i++) { @@ -62,22 +62,22 @@ public: ### 方法二 -以上代码我是建了两个辅助数组,而且A数组还相当于遍历了两次,用辅助数组的好处就是思路清晰,优化一下就是不用这两个辅助树,代码如下: +以上代码我是建了两个辅助数组,而且nums数组还相当于遍历了两次,用辅助数组的好处就是思路清晰,优化一下就是不用这两个辅助数组,代码如下: ```CPP class Solution { public: - vector sortArrayByParityII(vector& A) { - vector result(A.size()); + vector sortArrayByParityII(vector& nums) { + vector result(nums.size()); int evenIndex = 0; // 偶数下标 int oddIndex = 1; // 奇数下标 - for (int i = 0; i < A.size(); i++) { - if (A[i] % 2 == 0) { - result[evenIndex] = A[i]; + for (int i = 0; i < nums.size(); i++) { + if (nums[i] % 2 == 0) { + result[evenIndex] = nums[i]; evenIndex += 2; } else { - result[oddIndex] = A[i]; + result[oddIndex] = nums[i]; oddIndex += 2; } } @@ -96,15 +96,15 @@ public: ```CPP class Solution { public: - vector sortArrayByParityII(vector& A) { + vector sortArrayByParityII(vector& nums) { int oddIndex = 1; - for (int i = 0; i < A.size(); i += 2) { - if (A[i] % 2 == 1) { // 在偶数位遇到了奇数 - while(A[oddIndex] % 2 != 0) oddIndex += 2; // 在奇数位找一个偶数 - swap(A[i], A[oddIndex]); // 替换 + for (int i = 0; i < nums.size(); i += 2) { + if (nums[i] % 2 == 1) { // 在偶数位遇到了奇数 + while(nums[oddIndex] % 2 != 0) oddIndex += 2; // 在奇数位找一个偶数 + swap(nums[i], nums[oddIndex]); // 替换 } } - return A; + return nums; } }; ``` @@ -253,6 +253,37 @@ func sortArrayByParityII(nums []int) []int { } return result; } + +// 方法二 +func sortArrayByParityII(nums []int) []int { + result := make([]int, len(nums)) + evenIndex := 0 // 偶数下标 + oddIndex := 1 // 奇数下标 + for _, v := range nums { + if v % 2 == 0 { + result[evenIndex] = v + evenIndex += 2 + } else { + result[oddIndex] = v + oddIndex += 2 + } + } + return result +} + +// 方法三 +func sortArrayByParityII(nums []int) []int { + oddIndex := 1 + for i := 0; i < len(nums); i += 2 { + if nums[i] % 2 == 1 { // 在偶数位遇到了奇数 + for nums[oddIndex] % 2 != 0 { + oddIndex += 2 // 在奇数位找一个偶数 + } + nums[i], nums[oddIndex] = nums[oddIndex], nums[i] + } + } + return nums +} ``` ### JavaScript diff --git a/problems/0968.监控二叉树.md b/problems/0968.监控二叉树.md index d59496c8..327c54f7 100644 --- a/problems/0968.监控二叉树.md +++ b/problems/0968.监控二叉树.md @@ -536,7 +536,7 @@ func min(a, b int) int { ``` -### Javascript +### JavaScript ```Javascript var minCameraCover = function(root) { diff --git a/problems/0977.有序数组的平方.md b/problems/0977.有序数组的平方.md index 1a6604c2..b8488e10 100644 --- a/problems/0977.有序数组的平方.md +++ b/problems/0977.有序数组的平方.md @@ -301,7 +301,7 @@ impl Solution { } } ``` -### Javascript: +### JavaScript: ```Javascript /** @@ -327,7 +327,7 @@ var sortedSquares = function(nums) { }; ``` -### Typescript: +### TypeScript: 双指针法: diff --git a/problems/1005.K次取反后最大化的数组和.md b/problems/1005.K次取反后最大化的数组和.md index 4e049600..1a781777 100644 --- a/problems/1005.K次取反后最大化的数组和.md +++ b/problems/1005.K次取反后最大化的数组和.md @@ -207,7 +207,7 @@ func largestSumAfterKNegations(nums []int, K int) int { ``` -### Javascript +### JavaScript ```Javascript var largestSumAfterKNegations = function(nums, k) { diff --git a/problems/1035.不相交的线.md b/problems/1035.不相交的线.md index 8ee52c5d..5164e1f7 100644 --- a/problems/1035.不相交的线.md +++ b/problems/1035.不相交的线.md @@ -8,11 +8,16 @@ [力扣题目链接](https://leetcode.cn/problems/uncrossed-lines/) -我们在两条独立的水平线上按给定的顺序写下 A 和 B 中的整数。 +在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 -现在,我们可以绘制一些连接两个数字 A[i] 和 B[j] 的直线,只要 A[i] == B[j],且我们绘制的直线不与任何其他连线(非水平线)相交。 +现在,可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线,这些直线需要同时满足: -以这种方法绘制线条,并返回我们可以绘制的最大连线数。 +* nums1[i] == nums2[j] +* 且绘制的直线不与任何其他连线(非水平线)相交。 + +请注意,连线即使在端点也不能相交:每个数字只能属于一条连线。 + +以这种方法绘制线条,并返回可以绘制的最大连线数。 ![1035.不相交的线](https://code-thinking-1253855093.file.myqcloud.com/pics/2021032116363533.png) @@ -26,16 +31,16 @@ 相信不少录友看到这道题目都没啥思路,我们来逐步分析一下。 -绘制一些连接两个数字 A[i] 和 B[j] 的直线,只要 A[i] == B[j],且直线不能相交! +绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线,只要 nums1[i] == nums2[j],且直线不能相交! -直线不能相交,这就是说明在字符串A中 找到一个与字符串B相同的子序列,且这个子序列不能改变相对顺序,只要相对顺序不改变,链接相同数字的直线就不会相交。 +直线不能相交,这就是说明在字符串nums1中 找到一个与字符串nums2相同的子序列,且这个子序列不能改变相对顺序,只要相对顺序不改变,连接相同数字的直线就不会相交。 -拿示例一A = [1,4,2], B = [1,2,4]为例,相交情况如图: +拿示例一nums1 = [1,4,2], nums2 = [1,2,4]为例,相交情况如图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210914145158.png) -其实也就是说A和B的最长公共子序列是[1,4],长度为2。 这个公共子序列指的是相对顺序不变(即数字4在字符串A中数字1的后面,那么数字4也应该在字符串B数字1的后面) +其实也就是说nums1和nums2的最长公共子序列是[1,4],长度为2。 这个公共子序列指的是相对顺序不变(即数字4在字符串nums1中数字1的后面,那么数字4也应该在字符串nums2数字1的后面) 这么分析完之后,大家可以发现:**本题说是求绘制的最大连线数,其实就是求两个字符串的最长公共子序列的长度!** @@ -52,18 +57,18 @@ ```CPP class Solution { public: - int maxUncrossedLines(vector& A, vector& B) { - vector> dp(A.size() + 1, vector(B.size() + 1, 0)); - for (int i = 1; i <= A.size(); i++) { - for (int j = 1; j <= B.size(); j++) { - if (A[i - 1] == B[j - 1]) { + int maxUncrossedLines(vector& nums1, vector& nums2) { + vector> dp(nums1.size() + 1, vector(nums2.size() + 1, 0)); + for (int i = 1; i <= nums1.size(); i++) { + for (int j = 1; j <= nums2.size(); j++) { + if (nums1[i - 1] == nums2[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); } } } - return dp[A.size()][B.size()]; + return dp[nums1.size()][nums2.size()]; } }; ``` @@ -110,11 +115,11 @@ public: ```python class Solution: - def maxUncrossedLines(self, A: List[int], B: List[int]) -> int: - dp = [[0] * (len(B)+1) for _ in range(len(A)+1)] - for i in range(1, len(A)+1): - for j in range(1, len(B)+1): - if A[i-1] == B[j-1]: + def maxUncrossedLines(self, nums1: List[int], nums2: List[int]) -> int: + dp = [[0] * (len(nums2)+1) for _ in range(len(nums1)+1)] + for i in range(1, len(nums1)+1): + for j in range(1, len(nums2)+1): + if nums1[i-1] == nums2[j-1]: dp[i][j] = dp[i-1][j-1] + 1 else: dp[i][j] = max(dp[i-1][j], dp[i][j-1]) @@ -124,23 +129,22 @@ class Solution: ### Go: ```go -func maxUncrossedLines(A []int, B []int) int { - m, n := len(A), len(B) - dp := make([][]int, m+1) +func maxUncrossedLines(nums1 []int, nums2 []int) int { + dp := make([][]int, len(nums1) + 1) for i := range dp { - dp[i] = make([]int, n+1) + dp[i] = make([]int, len(nums2) + 1) } - for i := 1; i <= len(A); i++ { - for j := 1; j <= len(B); j++ { - if (A[i - 1] == B[j - 1]) { + for i := 1; i <= len(nums1); i++ { + for j := 1; j <= len(nums2); j++ { + if (nums1[i - 1] == nums2[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + 1 } else { dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) } } } - return dp[m][n] + return dp[len(nums1)][len(nums2)] } diff --git a/problems/1143.最长公共子序列.md b/problems/1143.最长公共子序列.md index 25f32838..6d05ccf3 100644 --- a/problems/1143.最长公共子序列.md +++ b/problems/1143.最长公共子序列.md @@ -80,7 +80,7 @@ if (text1[i - 1] == text2[j - 1]) { 先看看dp[i][0]应该是多少呢? -test1[0, i-1]和空串的最长公共子序列自然是0,所以dp[i][0] = 0; +text1[0, i-1]和空串的最长公共子序列自然是0,所以dp[i][0] = 0; 同理dp[0][j]也是0。 diff --git a/problems/1365.有多少小于当前数字的数字.md b/problems/1365.有多少小于当前数字的数字.md index 64f61096..f0a77f55 100644 --- a/problems/1365.有多少小于当前数字的数字.md +++ b/problems/1365.有多少小于当前数字的数字.md @@ -115,7 +115,7 @@ public: ## 其他语言版本 -### Java: +### Java: ```Java public int[] smallerNumbersThanCurrent(int[] nums) { @@ -138,18 +138,51 @@ public int[] smallerNumbersThanCurrent(int[] nums) { ### Python: -```python +> 暴力法: + +```python3 class Solution: + def smallerNumbersThanCurrent(self, nums: List[int]) -> List[int]: + res = [0 for _ in range(len(nums))] + for i in range(len(nums)): + cnt = 0 + for j in range(len(nums)): + if j == i: + continue + if nums[i] > nums[j]: + cnt += 1 + res[i] = cnt + return res +``` + +> 排序+hash: + +```python3 +class Solution: + # 方法一:使用字典 def smallerNumbersThanCurrent(self, nums: List[int]) -> List[int]: res = nums[:] - hash = dict() + hash_dict = dict() res.sort() # 从小到大排序之后,元素下标就是小于当前数字的数字 for i, num in enumerate(res): - if num not in hash.keys(): # 遇到了相同的数字,那么不需要更新该 number 的情况 - hash[num] = i + if num not in hash_dict.keys(): # 遇到了相同的数字,那么不需要更新该 number 的情况 + hash_dict[num] = i for i, num in enumerate(nums): - res[i] = hash[num] + res[i] = hash_dict[num] return res + + # 方法二:使用数组 + def smallerNumbersThanCurrent(self, nums: List[int]) -> List[int]: + # 同步进行排序和创建新数组的操作,这样可以减少一次冗余的数组复制操作,以减少一次O(n) 的复制时间开销 + sort_nums = sorted(nums) + # 题意中 0 <= nums[i] <= 100,故range的参数设为101 + hash_lst = [0 for _ in range(101)] + # 从后向前遍历,这样hash里存放的就是相同元素最左面的数值和下标了 + for i in range(len(sort_nums)-1,-1,-1): + hash_lst[sort_nums[i]] = i + for i in range(len(nums)): + nums[i] = hash_lst[nums[i]] + return nums ``` ### Go: @@ -220,7 +253,7 @@ var smallerNumbersThanCurrent = function(nums) { }; ``` -### TypeScript: +### TypeScript: > 暴力法: @@ -241,7 +274,7 @@ function smallerNumbersThanCurrent(nums: number[]): number[] { }; ``` -> 排序+hash +> 排序+hash: ```typescript function smallerNumbersThanCurrent(nums: number[]): number[] { @@ -260,7 +293,7 @@ function smallerNumbersThanCurrent(nums: number[]): number[] { }; ``` -### rust +### Rust: ```rust use std::collections::HashMap; impl Solution { diff --git a/problems/1971.寻找图中是否存在路径.md b/problems/1971.寻找图中是否存在路径.md index 2c5901ae..93e9b663 100644 --- a/problems/1971.寻找图中是否存在路径.md +++ b/problems/1971.寻找图中是否存在路径.md @@ -213,7 +213,7 @@ class Solution: return find(source) == find(destination) ``` -### Javascript: +### JavaScript: Javascript 并查集解法如下: diff --git a/problems/kamacoder/0044.开发商购买土地.md b/problems/kamacoder/0044.开发商购买土地.md index ea2c696e..739e2cad 100644 --- a/problems/kamacoder/0044.开发商购买土地.md +++ b/problems/kamacoder/0044.开发商购买土地.md @@ -388,6 +388,62 @@ if __name__ == "__main__": main() ``` + +### JavaScript + +前缀和 +```js +function func() { + const readline = require('readline') + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }) + let inputLines = [] + rl.on('line', function (line) { + inputLines.push(line.trim()) + }) + + rl.on('close', function () { + let [n, m] = inputLines[0].split(" ").map(Number) + let c = new Array(n).fill(0) + let r = new Array(m).fill(0) + let arr = new Array(n) + let sum = 0//数组总和 + let min = Infinity//设置最小值的初始值为无限大 + //定义数组 + for (let s = 0; s < n; s++) { + arr[s] = inputLines[s + 1].split(" ").map(Number) + } + //每一行的和 + for (let i = 0; i < n; i++) { + for (let j = 0; j < m; j++) { + c[i] += arr[i][j] + sum += arr[i][j] + } + } + //每一列的和 + for (let i = 0; i < n; i++) { + for (let j = 0; j < m; j++) { + r[j] += arr[i][j] + } + } + let sum1 = 0, sum2 = 0 + //横向切割 + for (let i = 0; i < n; i++) { + sum1 += c[i] + min = min < Math.abs(sum - 2 * sum1) ? min : Math.abs(sum - 2 * sum1) + } + //纵向切割 + for (let j = 0; j < m; j++) { + sum2 += r[j] + min = min < Math.abs(sum - 2 * sum2) ? min : Math.abs(sum - 2 * sum2) + } + console.log(min); + }) +} +``` + ### C 前缀和 diff --git a/problems/kamacoder/0047.参会dijkstra堆.md b/problems/kamacoder/0047.参会dijkstra堆.md index 9905a420..75c12f8a 100644 --- a/problems/kamacoder/0047.参会dijkstra堆.md +++ b/problems/kamacoder/0047.参会dijkstra堆.md @@ -911,7 +911,7 @@ func main() { ### Rust -### Javascript +### JavaScript ### TypeScript diff --git a/problems/kamacoder/0047.参会dijkstra朴素.md b/problems/kamacoder/0047.参会dijkstra朴素.md index 4e41237f..e71e9d53 100644 --- a/problems/kamacoder/0047.参会dijkstra朴素.md +++ b/problems/kamacoder/0047.参会dijkstra朴素.md @@ -867,7 +867,7 @@ if __name__ == "__main__": ### Rust -### Javascript +### JavaScript ```js function dijkstra(grid, start, end) { diff --git a/problems/kamacoder/0053.寻宝-Kruskal.md b/problems/kamacoder/0053.寻宝-Kruskal.md index 6a227985..861efe68 100644 --- a/problems/kamacoder/0053.寻宝-Kruskal.md +++ b/problems/kamacoder/0053.寻宝-Kruskal.md @@ -547,7 +547,7 @@ if __name__ == "__main__": ### Rust -### Javascript +### JavaScript ```js function kruskal(v, edges) { diff --git a/problems/kamacoder/0053.寻宝-prim.md b/problems/kamacoder/0053.寻宝-prim.md index d2c9578a..8e26bea4 100644 --- a/problems/kamacoder/0053.寻宝-prim.md +++ b/problems/kamacoder/0053.寻宝-prim.md @@ -692,7 +692,7 @@ if __name__ == "__main__": ### Rust -### Javascript +### JavaScript ```js function prim(v, edges) { const grid = Array.from({ length: v + 1 }, () => new Array(v + 1).fill(10001)); // Fixed grid initialization diff --git a/problems/kamacoder/0055.右旋字符串.md b/problems/kamacoder/0055.右旋字符串.md index 363d9ffa..2b32cb44 100644 --- a/problems/kamacoder/0055.右旋字符串.md +++ b/problems/kamacoder/0055.右旋字符串.md @@ -350,7 +350,29 @@ function reverseStr(s, start, end) { ### Swift: +```swift +func rotateWords(_ s: String, _ k: Int) -> String { + var chars = Array(s) + // 先反转整体 + reverseWords(&chars, start: 0, end: s.count - 1) + // 反转前半段 + reverseWords(&chars, start: 0, end: k - 1) + // 反转后半段 + reverseWords(&chars, start: k, end: s.count - 1) + return String(chars) +} +// 反转start...end 的字符数组 +func reverseWords(_ chars: inout [Character], start: Int, end: Int) { + var left = start + var right = end + while left < right, right < chars.count { + (chars[left], chars[right]) = (chars[right], chars[left]) + left += 1 + right -= 1 + } +} +``` ### PHP: diff --git a/problems/kamacoder/0094.城市间货物运输I-SPFA.md b/problems/kamacoder/0094.城市间货物运输I-SPFA.md index 9ba92599..99986aaa 100644 --- a/problems/kamacoder/0094.城市间货物运输I-SPFA.md +++ b/problems/kamacoder/0094.城市间货物运输I-SPFA.md @@ -462,7 +462,7 @@ if __name__ == "__main__": ### Rust -### Javascript +### JavaScript ```js async function main() { diff --git a/problems/kamacoder/0094.城市间货物运输I.md b/problems/kamacoder/0094.城市间货物运输I.md index 9021e0fe..2afc014b 100644 --- a/problems/kamacoder/0094.城市间货物运输I.md +++ b/problems/kamacoder/0094.城市间货物运输I.md @@ -483,7 +483,7 @@ if __name__ == "__main__": ### Rust -### Javascript +### JavaScript ```js async function main() { diff --git a/problems/kamacoder/0095.城市间货物运输II.md b/problems/kamacoder/0095.城市间货物运输II.md index ac6ccf3c..a3896b88 100644 --- a/problems/kamacoder/0095.城市间货物运输II.md +++ b/problems/kamacoder/0095.城市间货物运输II.md @@ -333,6 +333,8 @@ public class Main { ### Python +Bellman-Ford方法求解含有负回路的最短路问题 + ```python import sys @@ -388,11 +390,57 @@ if __name__ == "__main__": ``` +SPFA方法求解含有负回路的最短路问题 + +```python +from collections import deque +from math import inf + +def main(): + n, m = [int(i) for i in input().split()] + graph = [[] for _ in range(n+1)] + min_dist = [inf for _ in range(n+1)] + count = [0 for _ in range(n+1)] # 记录节点加入队列的次数 + for _ in range(m): + s, t, v = [int(i) for i in input().split()] + graph[s].append([t, v]) + + min_dist[1] = 0 # 初始化 + count[1] = 1 + d = deque([1]) + flag = False + + while d: # 主循环 + cur_node = d.popleft() + for next_node, val in graph[cur_node]: + if min_dist[next_node] > min_dist[cur_node] + val: + min_dist[next_node] = min_dist[cur_node] + val + count[next_node] += 1 + if next_node not in d: + d.append(next_node) + if count[next_node] == n: # 如果某个点松弛了n次,说明有负回路 + flag = True + if flag: + break + + if flag: + print("circle") + else: + if min_dist[-1] == inf: + print("unconnected") + else: + print(min_dist[-1]) + + +if __name__ == "__main__": + main() +``` + ### Go ### Rust -### Javascript +### JavaScript ### TypeScript diff --git a/problems/kamacoder/0096.城市间货物运输III.md b/problems/kamacoder/0096.城市间货物运输III.md index a41332cf..0e13846d 100644 --- a/problems/kamacoder/0096.城市间货物运输III.md +++ b/problems/kamacoder/0096.城市间货物运输III.md @@ -702,7 +702,129 @@ public class Main { ``` +```java +class Edge { + public int u; // 边的端点1 + public int v; // 边的端点2 + public int val; // 边的权值 + + public Edge() { + } + + public Edge(int u, int v) { + this.u = u; + this.v = v; + this.val = 0; + } + + public Edge(int u, int v, int val) { + this.u = u; + this.v = v; + this.val = val; + } +} + +/** + * SPFA算法(版本3):处理含【负权回路】的有向图的最短路径问题 + * bellman_ford(版本3) 的队列优化算法版本 + * 限定起点、终点、至多途径k个节点 + */ +public class SPFAForSSSP { + + /** + * SPFA算法 + * + * @param n 节点个数[1,n] + * @param graph 邻接表 + * @param startIdx 开始节点(源点) + */ + public static int[] spfa(int n, List> graph, int startIdx, int k) { + // 定义最大范围 + int maxVal = Integer.MAX_VALUE; + // minDist[i] 源点到节点i的最短距离 + int[] minDist = new int[n + 1]; // 有效节点编号范围:[1,n] + Arrays.fill(minDist, maxVal); // 初始化为maxVal + minDist[startIdx] = 0; // 设置源点到源点的最短路径为0 + + // 定义queue记录每一次松弛更新的节点 + Queue queue = new LinkedList<>(); + queue.offer(startIdx); // 初始化:源点开始(queue和minDist的更新是同步的) + + + // SPFA算法核心:只对上一次松弛的时候更新过的节点关联的边进行松弛操作 + while (k + 1 > 0 && !queue.isEmpty()) { // 限定松弛 k+1 次 + int curSize = queue.size(); // 记录当前队列节点个数(上一次松弛更新的节点个数,用作分层统计) + while (curSize-- > 0) { //分层控制,限定本次松弛只针对上一次松弛更新的节点,不对新增的节点做处理 + // 记录当前minDist状态,作为本次松弛的基础 + int[] minDist_copy = Arrays.copyOfRange(minDist, 0, minDist.length); + + // 取出节点 + int cur = queue.poll(); + // 获取cur节点关联的边,进行松弛操作 + List relateEdges = graph.get(cur); + for (Edge edge : relateEdges) { + int u = edge.u; // 与`cur`对照 + int v = edge.v; + int weight = edge.val; + if (minDist_copy[u] + weight < minDist[v]) { + minDist[v] = minDist_copy[u] + weight; // 更新 + // 队列同步更新(此处有一个针对队列的优化:就是如果已经存在于队列的元素不需要重复添加) + if (!queue.contains(v)) { + queue.offer(v); // 与minDist[i]同步更新,将本次更新的节点加入队列,用做下一个松弛的参考基础 + } + } + } + } + // 当次松弛结束,次数-1 + k--; + } + + // 返回minDist + return minDist; + } + + public static void main(String[] args) { + // 输入控制 + Scanner sc = new Scanner(System.in); + System.out.println("1.输入N个节点、M条边(u v weight)"); + int n = sc.nextInt(); + int m = sc.nextInt(); + + System.out.println("2.输入M条边"); + List> graph = new ArrayList<>(); // 构建邻接表 + for (int i = 0; i <= n; i++) { + graph.add(new ArrayList<>()); + } + while (m-- > 0) { + int u = sc.nextInt(); + int v = sc.nextInt(); + int weight = sc.nextInt(); + graph.get(u).add(new Edge(u, v, weight)); + } + + System.out.println("3.输入src dst k(起点、终点、至多途径k个点)"); + int src = sc.nextInt(); + int dst = sc.nextInt(); + int k = sc.nextInt(); + + // 调用算法 + int[] minDist = SPFAForSSSP.spfa(n, graph, src, k); + // 校验起点->终点 + if (minDist[dst] == Integer.MAX_VALUE) { + System.out.println("unreachable"); + } else { + System.out.println("最短路径:" + minDist[n]); + } + } +} +``` + + + ### Python + +Bellman-Ford方法求解单源有限最短路 + ```python def main(): # 輸入 @@ -736,6 +858,48 @@ def main(): +if __name__ == "__main__": + main() +``` + +SPFA方法求解单源有限最短路 + +```python +from collections import deque +from math import inf + + +def main(): + n, m = [int(i) for i in input().split()] + graph = [[] for _ in range(n+1)] + for _ in range(m): + v1, v2, val = [int(i) for i in input().split()] + graph[v1].append([v2, val]) + src, dst, k = [int(i) for i in input().split()] + min_dist = [inf for _ in range(n+1)] + min_dist[src] = 0 # 初始化起点的距离 + que = deque([src]) + + while k != -1 and que: + visited = [False for _ in range(n+1)] # 用于保证每次松弛时一个节点最多加入队列一次 + que_size = len(que) + temp_dist = min_dist.copy() # 用于记录上一次遍历的结果 + for _ in range(que_size): + cur_node = que.popleft() + for next_node, val in graph[cur_node]: + if min_dist[next_node] > temp_dist[cur_node] + val: + min_dist[next_node] = temp_dist[cur_node] + val + if not visited[next_node]: + que.append(next_node) + visited[next_node] = True + k -= 1 + + if min_dist[dst] == inf: + print("unreachable") + else: + print(min_dist[dst]) + + if __name__ == "__main__": main() ``` @@ -744,7 +908,7 @@ if __name__ == "__main__": ### Rust -### Javascript +### JavaScript ### TypeScript diff --git a/problems/kamacoder/0097.小明逛公园.md b/problems/kamacoder/0097.小明逛公园.md index 9c813b3a..dfbd6aa9 100644 --- a/problems/kamacoder/0097.小明逛公园.md +++ b/problems/kamacoder/0097.小明逛公园.md @@ -425,6 +425,71 @@ floyd算法的时间复杂度相对较高,适合 稠密图且源点较多的 ### Java +- 基于三维数组的Floyd算法 + +```java +public class FloydBase { + + // public static int MAX_VAL = Integer.MAX_VALUE; + public static int MAX_VAL = 10005; // 边的最大距离是10^4(不选用Integer.MAX_VALUE是为了避免相加导致数值溢出) + + public static void main(String[] args) { + // 输入控制 + Scanner sc = new Scanner(System.in); + System.out.println("1.输入N M"); + int n = sc.nextInt(); + int m = sc.nextInt(); + + System.out.println("2.输入M条边"); + + // ① dp定义(grid[i][j][k] 节点i到节点j 可能经过节点K(k∈[1,n]))的最短路径 + int[][][] grid = new int[n + 1][n + 1][n + 1]; + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= n; j++) { + for (int k = 0; k <= n; k++) { + grid[i][j][k] = grid[j][i][k] = MAX_VAL; // 其余设置为最大值 + } + } + } + + // ② dp 推导:grid[i][j][k] = min{grid[i][k][k-1] + grid[k][j][k-1], grid[i][j][k-1]} + while (m-- > 0) { + int u = sc.nextInt(); + int v = sc.nextInt(); + int weight = sc.nextInt(); + grid[u][v][0] = grid[v][u][0] = weight; // 初始化(处理k=0的情况) ③ dp初始化 + } + + // ④ dp推导:floyd 推导 + for (int k = 1; k <= n; k++) { + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= n; j++) { + grid[i][j][k] = Math.min(grid[i][k][k - 1] + grid[k][j][k - 1], grid[i][j][k - 1]); + } + } + } + + System.out.println("3.输入[起点-终点]计划个数"); + int x = sc.nextInt(); + + System.out.println("4.输入每个起点src 终点dst"); + + while (x-- > 0) { + int src = sc.nextInt(); + int dst = sc.nextInt(); + // 根据floyd推导结果输出计划路径的最小距离 + if (grid[src][dst][n] == MAX_VAL) { + System.out.println("-1"); + } else { + System.out.println(grid[src][dst][n]); + } + } + } +} +``` + + + ### Python 基于三维数组的Floyd @@ -493,7 +558,7 @@ if __name__ == '__main__': ### Rust -### Javascript +### JavaScript ### TypeScript diff --git a/problems/kamacoder/0098.所有可达路径.md b/problems/kamacoder/0098.所有可达路径.md index a2f60a90..7f56f9f4 100644 --- a/problems/kamacoder/0098.所有可达路径.md +++ b/problems/kamacoder/0098.所有可达路径.md @@ -726,7 +726,7 @@ func main() { ### Rust -### Javascript +### JavaScript #### 邻接矩阵写法 diff --git a/problems/kamacoder/0099.岛屿的数量广搜.md b/problems/kamacoder/0099.岛屿的数量广搜.md index 9d31c922..d654e236 100644 --- a/problems/kamacoder/0099.岛屿的数量广搜.md +++ b/problems/kamacoder/0099.岛屿的数量广搜.md @@ -254,6 +254,7 @@ directions = [[0, 1], [1, 0], [0, -1], [-1, 0]] def bfs(grid, visited, x, y): que = deque([]) que.append([x,y]) + visited[x][y] = True while que: cur_x, cur_y = que.popleft() for i, j in directions: @@ -360,7 +361,7 @@ func main() { ### Rust -### Javascript +### JavaScript ```javascript const r1 = require('readline').createInterface({ input: process.stdin }); diff --git a/problems/kamacoder/0099.岛屿的数量深搜.md b/problems/kamacoder/0099.岛屿的数量深搜.md index da1c0739..5a21f387 100644 --- a/problems/kamacoder/0099.岛屿的数量深搜.md +++ b/problems/kamacoder/0099.岛屿的数量深搜.md @@ -328,7 +328,7 @@ if __name__ == '__main__': ### Rust -### Javascript +### JavaScript ```javascript const r1 = require('readline').createInterface({ input: process.stdin }); diff --git a/problems/kamacoder/0100.岛屿的最大面积.md b/problems/kamacoder/0100.岛屿的最大面积.md index d86f6cd8..4f2f9d67 100644 --- a/problems/kamacoder/0100.岛屿的最大面积.md +++ b/problems/kamacoder/0100.岛屿的最大面积.md @@ -648,7 +648,7 @@ fn main() { ``` -### Javascript +### JavaScript ```javascript // 广搜版 diff --git a/problems/kamacoder/0101.孤岛的总面积.md b/problems/kamacoder/0101.孤岛的总面积.md index 26c92c07..123e36ce 100644 --- a/problems/kamacoder/0101.孤岛的总面积.md +++ b/problems/kamacoder/0101.孤岛的总面积.md @@ -257,14 +257,62 @@ public class Main { ### Python +#### 深搜版 +```python +position = [[1, 0], [0, 1], [-1, 0], [0, -1]] +count = 0 + +def dfs(grid, x, y): + global count + grid[x][y] = 0 + count += 1 + for i, j in position: + next_x = x + i + next_y = y + j + if next_x < 0 or next_y < 0 or next_x >= len(grid) or next_y >= len(grid[0]): + continue + if grid[next_x][next_y] == 1: + dfs(grid, next_x, next_y) + +n, m = map(int, input().split()) + +# 邻接矩阵 +grid = [] +for i in range(n): + grid.append(list(map(int, input().split()))) + +# 清除边界上的连通分量 +for i in range(n): + if grid[i][0] == 1: + dfs(grid, i, 0) + if grid[i][m - 1] == 1: + dfs(grid, i, m - 1) + +for j in range(m): + if grid[0][j] == 1: + dfs(grid, 0, j) + if grid[n - 1][j] == 1: + dfs(grid, n - 1, j) + +count = 0 # 将count重置为0 +# 统计内部所有剩余的连通分量 +for i in range(n): + for j in range(m): + if grid[i][j] == 1: + dfs(grid, i, j) + +print(count) +``` + +#### 广搜版 ```python from collections import deque # 处理输入 -n, m = list(map(int, input().strip())) +n, m = list(map(int, input().split())) g = [] for _ in range(n): - row = list(map(int, input().strip())) + row = list(map(int, input().split())) g.append(row) # 定义四个方向、孤岛面积(遍历完边缘后会被重置) @@ -293,17 +341,22 @@ def bfs(r, c): for i in range(n): - if g[i][0] == 1: bfs(i, 0) - if g[i][m-1] == 1: bfs(i, m-1) + if g[i][0] == 1: + bfs(i, 0) + if g[i][m-1] == 1: + bfs(i, m-1) for i in range(m): - if g[0][i] == 1: bfs(0, i) - if g[n-1][i] == 1: bfs(n-1, i) + if g[0][i] == 1: + bfs(0, i) + if g[n-1][i] == 1: + bfs(n-1, i) count = 0 for i in range(n): for j in range(m): - if g[i][j] == 1: bfs(i, j) + if g[i][j] == 1: + bfs(i, j) print(count) ``` @@ -467,7 +520,7 @@ func main() { ### Rust -### Javascript +### JavaScript #### 深搜版 diff --git a/problems/kamacoder/0102.沉没孤岛.md b/problems/kamacoder/0102.沉没孤岛.md index 4eabc516..a7e28df5 100644 --- a/problems/kamacoder/0102.沉没孤岛.md +++ b/problems/kamacoder/0102.沉没孤岛.md @@ -322,7 +322,7 @@ for row in g: ### Rust -### Javascript +### JavaScript #### 深搜版 diff --git a/problems/kamacoder/0103.水流问题.md b/problems/kamacoder/0103.水流问题.md index 9a34bf09..3066c99f 100644 --- a/problems/kamacoder/0103.水流问题.md +++ b/problems/kamacoder/0103.水流问题.md @@ -413,10 +413,85 @@ if __name__ == "__main__": ``` ### Go +```go +package main + +import ( + "os" + "fmt" + "strings" + "strconv" + "bufio" +) + +var directions = [][]int{{0, -1}, {0, 1}, {-1, 0}, {1, 0}} // 四个方向的偏移量 + +func main() { + scanner := bufio.NewScanner(os.Stdin) + + scanner.Scan() + lineList := strings.Fields(scanner.Text()) + N, _ := strconv.Atoi(lineList[0]) + M, _ := strconv.Atoi(lineList[1]) + + grid := make([][]int, N) + visited := make([][]bool, N) // 用于标记是否访问过 + for i := 0; i < N; i++ { + grid[i] = make([]int, M) + visited[i] = make([]bool, M) + scanner.Scan() + lineList = strings.Fields(scanner.Text()) + + for j := 0; j < M; j++ { + grid[i][j], _ = strconv.Atoi(lineList[j]) + } + } + + // 遍历每个单元格,使用DFS检查是否可达两组边界 + for i := 0; i < N; i++ { + for j := 0; j < M; j++ { + canReachFirst, canReachSecond := dfs(grid, visited, i, j) + if canReachFirst && canReachSecond { + fmt.Println(strconv.Itoa(i) + " " + strconv.Itoa(j)) + } + } + } +} + +func dfs(grid [][]int, visited [][]bool, startx int, starty int) (bool, bool) { + visited[startx][starty] = true + canReachFirst := startx == 0 || starty == 0 || startx == len(grid)-1 || starty == len(grid[0])-1 + canReachSecond := startx == len(grid)-1 || starty == len(grid[0])-1 || startx == 0 || starty == 0 + + if canReachFirst && canReachSecond { + return true, true + } + + for _, direction := range directions { + nextx := startx + direction[0] + nexty := starty + direction[1] + + if nextx < 0 || nextx >= len(grid) || nexty < 0 || nexty >= len(grid[0]) { + continue + } + + if grid[nextx][nexty] <= grid[startx][starty] && !visited[nextx][nexty] { + hasReachFirst, hasReachSecond := dfs(grid, visited, nextx, nexty) + if !canReachFirst { + canReachFirst = hasReachFirst + } + if !canReachSecond { + canReachSecond = hasReachSecond + } + } + } + return canReachFirst, canReachSecond +} +``` ### Rust -### Javascript +### JavaScript #### 深搜 diff --git a/problems/kamacoder/0104.建造最大岛屿.md b/problems/kamacoder/0104.建造最大岛屿.md index 3dcbfee6..ffb6cae3 100644 --- a/problems/kamacoder/0104.建造最大岛屿.md +++ b/problems/kamacoder/0104.建造最大岛屿.md @@ -530,7 +530,7 @@ if __name__ == "__main__": ### Rust -### Javascript +### JavaScript ```javascript const r1 = require('readline').createInterface({ input: process.stdin }); diff --git a/problems/kamacoder/0105.有向图的完全可达性.md b/problems/kamacoder/0105.有向图的完全可达性.md index 838b0212..6901c655 100644 --- a/problems/kamacoder/0105.有向图的完全可达性.md +++ b/problems/kamacoder/0105.有向图的完全可达性.md @@ -489,7 +489,55 @@ func main() { ### Rust -### Javascript +### JavaScript + +```javascript +const rl = require('readline').createInterface({ + input:process.stdin, + output:process.stdout +}) + +let inputLines = [] + +rl.on('line' , (line)=>{ + inputLines.push(line) +}) + +rl.on('close',()=>{ + let [n , edgesCount]= inputLines[0].trim().split(' ').map(Number) + + let graph = Array.from({length:n+1} , ()=>{return[]}) + + for(let i = 1 ; i < inputLines.length ; i++ ){ + let [from , to] = inputLines[i].trim().split(' ').map(Number) + graph[from].push(to) + } + + let visited = new Array(n + 1).fill(false) + + let dfs = (graph , key , visited)=>{ + if(visited[key]){ + return + } + + visited[key] = true + for(let nextKey of graph[key]){ + dfs(graph,nextKey , visited) + } + } + + dfs(graph , 1 , visited) + + for(let i = 1 ; i <= n;i++){ + if(visited[i] === false){ + console.log(-1) + return + } + } + console.log(1) + +}) +``` ### TypeScript diff --git a/problems/kamacoder/0106.岛屿的周长.md b/problems/kamacoder/0106.岛屿的周长.md index 235d9445..91a1a438 100644 --- a/problems/kamacoder/0106.岛屿的周长.md +++ b/problems/kamacoder/0106.岛屿的周长.md @@ -343,7 +343,7 @@ func parseLine(line string, count int) []int { ### Rust -### Javascript +### JavaScript ### TypeScript diff --git a/problems/kamacoder/0107.寻找存在的路径.md b/problems/kamacoder/0107.寻找存在的路径.md index 09ad0d5e..779907c8 100644 --- a/problems/kamacoder/0107.寻找存在的路径.md +++ b/problems/kamacoder/0107.寻找存在的路径.md @@ -340,7 +340,7 @@ func main() { ### Rust -### Javascript +### JavaScript ```java const r1 = require('readline').createInterface({ input: process.stdin }); diff --git a/problems/kamacoder/0108.冗余连接.md b/problems/kamacoder/0108.冗余连接.md index 18a86ad6..121c2bfd 100644 --- a/problems/kamacoder/0108.冗余连接.md +++ b/problems/kamacoder/0108.冗余连接.md @@ -44,7 +44,7 @@ ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240527110320.png) -图中的 1 2,2 3,1 3 等三条边在删除后都能使原图变为一棵合法的树。但是 1 3 由于是标准输出里最后出现的那条边,所以输出结果为 1 3 +图中的 1 2,2 3,1 3 等三条边在删除后都能使原图变为一棵合法的树。但是 1 3 由于是标准输入里最后出现的那条边,所以输出结果为 1 3 数据范围: @@ -221,7 +221,7 @@ if __name__ == "__main__": ### Rust -### Javascript +### JavaScript ```javascript const r1 = require('readline').createInterface({ input: process.stdin }); diff --git a/problems/kamacoder/0109.冗余连接II.md b/problems/kamacoder/0109.冗余连接II.md index 56959d87..070bc685 100644 --- a/problems/kamacoder/0109.冗余连接II.md +++ b/problems/kamacoder/0109.冗余连接II.md @@ -467,7 +467,7 @@ if __name__ == "__main__": ### Rust -### Javascript +### JavaScript ```javascript const r1 = require('readline').createInterface({ input: process.stdin }); diff --git a/problems/kamacoder/0110.字符串接龙.md b/problems/kamacoder/0110.字符串接龙.md index ef261c4d..3b6c20ca 100644 --- a/problems/kamacoder/0110.字符串接龙.md +++ b/problems/kamacoder/0110.字符串接龙.md @@ -258,7 +258,7 @@ if __name__=='__main__': ### Rust -### Javascript +### JavaScript ```javascript const r1 = require('readline').createInterface({ input: process.stdin }); diff --git a/problems/kamacoder/0117.软件构建.md b/problems/kamacoder/0117.软件构建.md index 348187d6..58c17763 100644 --- a/problems/kamacoder/0117.软件构建.md +++ b/problems/kamacoder/0117.软件构建.md @@ -449,7 +449,7 @@ if __name__ == "__main__": ### Rust -### Javascript +### JavaScript ```javascript const r1 = require('readline').createInterface({ input: process.stdin }); diff --git a/problems/kamacoder/0126.骑士的攻击astar.md b/problems/kamacoder/0126.骑士的攻击astar.md index 6ea6ca83..8ad32644 100644 --- a/problems/kamacoder/0126.骑士的攻击astar.md +++ b/problems/kamacoder/0126.骑士的攻击astar.md @@ -373,7 +373,7 @@ for _ in range(n): ### Rust -### Javascript +### JavaScript ```js class MinHeap { @@ -514,7 +514,170 @@ main() ### C - +```C +#include +#include +#include + +// 定义一个结构体,表示棋盘上骑士的位置和相关的 A* 算法参数 +typedef struct { + int x, y; // 骑士在棋盘上的坐标 + int g; // 从起点到当前节点的实际消耗 + int h; // 从当前节点到目标节点的估计消耗(启发式函数值) + int f; // 总的估计消耗(f = g + h) +} Knight; + +#define MAX_HEAP_SIZE 2000000 // 假设优先队列的最大容量 + +// 定义一个优先队列,使用最小堆来实现 A* 算法中的 Open 列表 +typedef struct { + Knight data[MAX_HEAP_SIZE]; + int size; +} PriorityQueue; + +// 初始化优先队列 +void initQueue(PriorityQueue *pq) { + pq->size = 0; +} + +// 将骑士节点插入优先队列 +void push(PriorityQueue *pq, Knight k) { + if (pq->size >= MAX_HEAP_SIZE) { + // 堆已满,无法插入新节点 + return; + } + int i = pq->size++; + pq->data[i] = k; + // 上滤操作,维护最小堆的性质,使得 f 值最小的节点在堆顶 + while (i > 0) { + int parent = (i - 1) / 2; + if (pq->data[parent].f <= pq->data[i].f) { + break; + } + // 交换父节点和当前节点 + Knight temp = pq->data[parent]; + pq->data[parent] = pq->data[i]; + pq->data[i] = temp; + i = parent; + } +} + +// 从优先队列中弹出 f 值最小的骑士节点 +Knight pop(PriorityQueue *pq) { + Knight min = pq->data[0]; + pq->size--; + pq->data[0] = pq->data[pq->size]; + // 下滤操作,维护最小堆的性质 + int i = 0; + while (1) { + int left = 2 * i + 1; + int right = 2 * i + 2; + int smallest = i; + if (left < pq->size && pq->data[left].f < pq->data[smallest].f) { + smallest = left; + } + if (right < pq->size && pq->data[right].f < pq->data[smallest].f) { + smallest = right; + } + if (smallest == i) { + break; + } + // 交换当前节点与最小子节点 + Knight temp = pq->data[smallest]; + pq->data[smallest] = pq->data[i]; + pq->data[i] = temp; + i = smallest; + } + return min; +} + +// 判断优先队列是否为空 +int isEmpty(PriorityQueue *pq) { + return pq->size == 0; +} + +// 启发式函数:计算从当前位置到目标位置的欧几里得距离的平方(避免开方,提高效率) +int heuristic(int x, int y, int goal_x, int goal_y) { + int dx = x - goal_x; + int dy = y - goal_y; + return dx * dx + dy * dy; // 欧几里得距离的平方 +} + +// 用于记录从起点到棋盘上每个位置的最小移动次数 +int moves[1001][1001]; + +// 骑士在棋盘上的8个可能移动方向 +int dir[8][2] = { + {-2, -1}, {-2, 1}, {-1, 2}, {1, 2}, + {2, 1}, {2, -1}, {1, -2}, {-1, -2} +}; + +// 使用 A* 算法寻找从起点到目标点的最短路径 +int astar(int start_x, int start_y, int goal_x, int goal_y) { + PriorityQueue pq; + initQueue(&pq); + + // 初始化 moves 数组,-1 表示未访问过的位置 + memset(moves, -1, sizeof(moves)); + moves[start_x][start_y] = 0; // 起点位置的移动次数为 0 + + // 初始化起始节点 + Knight start; + start.x = start_x; + start.y = start_y; + start.g = 0; + start.h = heuristic(start_x, start_y, goal_x, goal_y); + start.f = start.g + start.h; // 总的估计消耗 + + push(&pq, start); // 将起始节点加入优先队列 + + while (!isEmpty(&pq)) { + Knight current = pop(&pq); // 取出 f 值最小的节点 + + // 如果已经到达目标位置,返回所需的最小移动次数 + if (current.x == goal_x && current.y == goal_y) { + return moves[current.x][current.y]; + } + + // 遍历当前节点的所有可能移动方向 + for (int i = 0; i < 8; i++) { + int nx = current.x + dir[i][0]; + int ny = current.y + dir[i][1]; + + // 检查新位置是否在棋盘范围内且未被访问过 + if (nx >= 1 && nx <= 1000 && ny >= 1 && ny <= 1000 && moves[nx][ny] == -1) { + moves[nx][ny] = moves[current.x][current.y] + 1; // 更新移动次数 + + // 创建新节点,表示骑士移动到的新位置 + Knight neighbor; + neighbor.x = nx; + neighbor.y = ny; + neighbor.g = current.g + 5; // 每次移动的消耗为 5(骑士移动的距离平方) + neighbor.h = heuristic(nx, ny, goal_x, goal_y); + neighbor.f = neighbor.g + neighbor.h; + + push(&pq, neighbor); // 将新节点加入优先队列 + } + } + } + + return -1; // 如果无法到达目标位置,返回 -1 +} + +int main() { + int n; + scanf("%d", &n); + while (n--) { + int a1, a2, b1, b2; // 起点和目标点的坐标 + scanf("%d %d %d %d", &a1, &a2, &b1, &b2); + + int result = astar(a1, a2, b1, b2); // 使用 A* 算法计算最短路径 + printf("%d\n", result); // 输出最小移动次数 + } + return 0; +} + +``` diff --git a/problems/二叉树的统一迭代法.md b/problems/二叉树的统一迭代法.md index 13c50737..037cf110 100644 --- a/problems/二叉树的统一迭代法.md +++ b/problems/二叉树的统一迭代法.md @@ -27,11 +27,16 @@ **那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。** -如何标记呢,**就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。** 这种方法也可以叫做标记法。 +如何标记呢? + +* 方法一:**就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。** 这种方法可以叫做`空指针标记法`。 + +* 方法二:**加一个 `boolean` 值跟随每个节点,`false` (默认值) 表示需要为该节点和它的左右儿子安排在栈中的位次,`true` 表示该节点的位次之前已经安排过了,可以收割节点了。** +这种方法可以叫做`boolean 标记法`,样例代码见下文`C++ 和 Python 的 boolean 标记法`。 这种方法更容易理解,在面试中更容易写出来。 ### 迭代法中序遍历 -中序遍历代码如下:(详细注释) +> 中序遍历(空指针标记法)代码如下:(详细注释) ```CPP class Solution { @@ -70,6 +75,45 @@ public: 可以看出我们将访问的节点直接加入到栈中,但如果是处理的节点则后面放入一个空节点, 这样只有空节点弹出的时候,才将下一个节点放进结果集。 +> 中序遍历(boolean 标记法): +```c++ +class Solution { +public: + vector inorderTraversal(TreeNode* root) { + vector result; + stack> st; + if (root != nullptr) + st.push(make_pair(root, false)); // 多加一个参数,false 为默认值,含义见下文注释 + + while (!st.empty()) { + auto node = st.top().first; + auto visited = st.top().second; //多加一个 visited 参数,使“迭代统一写法”成为一件简单的事 + st.pop(); + + if (visited) { // visited 为 True,表示该节点和两个儿子位次之前已经安排过了,现在可以收割节点了 + result.push_back(node->val); + continue; + } + + // visited 当前为 false, 表示初次访问本节点,此次访问的目的是“把自己和两个儿子在栈中安排好位次”。 + + // 中序遍历是'左中右',右儿子最先入栈,最后出栈。 + if (node->right) + st.push(make_pair(node->right, false)); + + // 把自己加回到栈中,位置居中。 + // 同时,设置 visited 为 true,表示下次再访问本节点时,允许收割。 + st.push(make_pair(node, true)); + + if (node->left) + st.push(make_pair(node->left, false)); // 左儿子最后入栈,最先出栈 + } + + return result; + } +}; +``` + 此时我们再来看前序遍历代码。 ### 迭代法前序遍历 @@ -105,7 +149,7 @@ public: ### 迭代法后序遍历 -后续遍历代码如下: (**注意此时我们和中序遍历相比仅仅改变了两行代码的顺序**) +> 后续遍历代码如下: (**注意此时我们和中序遍历相比仅仅改变了两行代码的顺序**) ```CPP class Solution { @@ -136,6 +180,42 @@ public: }; ``` +> 迭代法后序遍历(boolean 标记法): +```c++ +class Solution { +public: + vector postorderTraversal(TreeNode* root) { + vector result; + stack> st; + if (root != nullptr) + st.push(make_pair(root, false)); // 多加一个参数,false 为默认值,含义见下文 + + while (!st.empty()) { + auto node = st.top().first; + auto visited = st.top().second; //多加一个 visited 参数,使“迭代统一写法”成为一件简单的事 + st.pop(); + + if (visited) { // visited 为 True,表示该节点和两个儿子位次之前已经安排过了,现在可以收割节点了 + result.push_back(node->val); + continue; + } + + // visited 当前为 false, 表示初次访问本节点,此次访问的目的是“把自己和两个儿子在栈中安排好位次”。 + // 后序遍历是'左右中',节点自己最先入栈,最后出栈。 + // 同时,设置 visited 为 true,表示下次再访问本节点时,允许收割。 + st.push(make_pair(node, true)); + + if (node->right) + st.push(make_pair(node->right, false)); // 右儿子位置居中 + + if (node->left) + st.push(make_pair(node->left, false)); // 左儿子最后入栈,最先出栈 + } + + return result; + } +}; +``` ## 总结 此时我们写出了统一风格的迭代法,不用在纠结于前序写出来了,中序写不出来的情况了。 @@ -234,7 +314,7 @@ class Solution { ### Python: -迭代法前序遍历: +> 迭代法前序遍历(空指针标记法): ```python class Solution: def preorderTraversal(self, root: TreeNode) -> List[int]: @@ -257,7 +337,7 @@ class Solution: return result ``` -迭代法中序遍历: +> 迭代法中序遍历(空指针标记法): ```python class Solution: def inorderTraversal(self, root: TreeNode) -> List[int]: @@ -282,7 +362,7 @@ class Solution: return result ``` -迭代法后序遍历: +> 迭代法后序遍历(空指针标记法): ```python class Solution: def postorderTraversal(self, root: TreeNode) -> List[int]: @@ -306,6 +386,61 @@ class Solution: return result ``` +> 中序遍历,统一迭代(boolean 标记法): +```python +class Solution: + def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]: + values = [] + stack = [(root, False)] if root else [] # 多加一个参数,False 为默认值,含义见下文 + + while stack: + node, visited = stack.pop() # 多加一个 visited 参数,使“迭代统一写法”成为一件简单的事 + + if visited: # visited 为 True,表示该节点和两个儿子的位次之前已经安排过了,现在可以收割节点了 + values.append(node.val) + continue + + # visited 当前为 False, 表示初次访问本节点,此次访问的目的是“把自己和两个儿子在栈中安排好位次”。 + # 中序遍历是'左中右',右儿子最先入栈,最后出栈。 + if node.right: + stack.append((node.right, False)) + + stack.append((node, True)) # 把自己加回到栈中,位置居中。同时,设置 visited 为 True,表示下次再访问本节点时,允许收割 + + if node.left: + stack.append((node.left, False)) # 左儿子最后入栈,最先出栈 + + return values +``` + +> 后序遍历,统一迭代(boolean 标记法): +```python +class Solution: + def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]: + values = [] + stack = [(root, False)] if root else [] # 多加一个参数,False 为默认值,含义见下文 + + while stack: + node, visited = stack.pop() # 多加一个 visited 参数,使“迭代统一写法”成为一件简单的事 + + if visited: # visited 为 True,表示该节点和两个儿子位次之前已经安排过了,现在可以收割节点了 + values.append(node.val) + continue + + # visited 当前为 False, 表示初次访问本节点,此次访问的目的是“把自己和两个儿子在栈中安排好位次” + # 后序遍历是'左右中',节点自己最先入栈,最后出栈。 + # 同时,设置 visited 为 True,表示下次再访问本节点时,允许收割。 + stack.append((node, True)) + + if node.right: + stack.append((node.right, False)) # 右儿子位置居中 + + if node.left: + stack.append((node.left, False)) # 左儿子最后入栈,最先出栈 + + return values +``` + ### Go: > 前序遍历统一迭代法 diff --git a/problems/二叉树的迭代遍历.md b/problems/二叉树的迭代遍历.md index 5f59c388..a3c5b38f 100644 --- a/problems/二叉树的迭代遍历.md +++ b/problems/二叉树的迭代遍历.md @@ -240,14 +240,14 @@ class Solution { # 前序遍历-迭代-LC144_二叉树的前序遍历 class Solution: def preorderTraversal(self, root: TreeNode) -> List[int]: - # 根结点为空则返回空列表 + # 根节点为空则返回空列表 if not root: return [] stack = [root] result = [] while stack: node = stack.pop() - # 中结点先处理 + # 中节点先处理 result.append(node.val) # 右孩子先入栈 if node.right: @@ -262,25 +262,27 @@ class Solution: # 中序遍历-迭代-LC94_二叉树的中序遍历 class Solution: def inorderTraversal(self, root: TreeNode) -> List[int]: + if not root: return [] - stack = [] # 不能提前将root结点加入stack中 + stack = [] # 不能提前将root节点加入stack中 + result = [] cur = root while cur or stack: - # 先迭代访问最底层的左子树结点 + # 先迭代访问最底层的左子树节点 if cur: stack.append(cur) cur = cur.left - # 到达最左结点后处理栈顶结点 + # 到达最左节点后处理栈顶节点 else: cur = stack.pop() result.append(cur.val) - # 取栈顶元素右结点 + # 取栈顶元素右节点 cur = cur.right return result ``` - ```python +```python # 后序遍历-迭代-LC145_二叉树的后序遍历 class Solution: @@ -291,7 +293,7 @@ class Solution: result = [] while stack: node = stack.pop() - # 中结点先处理 + # 中节点先处理 result.append(node.val) # 左孩子先入栈 if node.left: @@ -303,6 +305,44 @@ class Solution: return result[::-1] ``` +#### Python 后序遍历的迭代新解法: +* 本解法不同于前文介绍的`逆转前序遍历调整后的结果`,而是采用了对每个节点直接处理。这个实现方法在面试中不容易写出来,在下一节,我将改造本代码,奉上代码更简洁、更套路化、更容易实现的统一方法。 + +```python +class Solution: + def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]: + values = [] + stack = [] + popped_nodes = set() # 记录值已经被收割了的 nodes,这是关键,已经被收割的节点还在树中,还会被访问到,但逻辑上已经等同于 null 节点。 + current = root + + while current or stack: + if current: # 一次处理完一个节点和他的左右儿子节点,不处理孙子节点,孙子节点由左右儿子等会分别处理。 + stack.append(current) # 入栈自己 + + if current.right: + stack.append(current.right) # 入栈右儿子 + + if current.left: # 因为栈是后进先出,后序是‘左右中’,所以后加左儿子 + stack.append(current.left) # 入栈左儿子 + + current = None # 会导致后面A处出栈 + continue + + node = stack.pop() # A处,出的是左儿子,如果无左儿子,出的就是右儿子,如果连右儿子也没有,出的就是自己了。 + + # 如果 node 是叶子节点,就可以收割了;如果左右儿子都已经被收割了,也可以收割 + if (node.left is None or node.left in popped_nodes) and \ + (node.right is None or node.right in popped_nodes): + popped_nodes.add(node) + values.append(node.val) + continue + + current = node # 不符合收割条件,说明 node 下还有未入栈的儿子,就去入栈 + + return values +``` + ### Go: > 迭代法前序遍历 diff --git a/problems/二叉树的递归遍历.md b/problems/二叉树的递归遍历.md index f2a97f4d..8f61b8c6 100644 --- a/problems/二叉树的递归遍历.md +++ b/problems/二叉树的递归遍历.md @@ -283,7 +283,7 @@ func postorderTraversal(root *TreeNode) (res []int) { } ``` -### Javascript: +### JavaScript: 前序遍历: ```Javascript diff --git a/problems/动态规划理论基础.md b/problems/动态规划理论基础.md index c9420d24..9ffb4533 100644 --- a/problems/动态规划理论基础.md +++ b/problems/动态规划理论基础.md @@ -106,7 +106,7 @@ **如果这灵魂三问自己都做到了,基本上这道题目也就解决了**,或者更清晰的知道自己究竟是哪一点不明白,是状态转移不明白,还是实现代码不知道该怎么写,还是不理解遍历dp数组的顺序。 -然后在问问题,目的性就很强了,群里的小伙伴也可以快速知道提问者的疑惑了。 +然后再问问题,目的性就很强了,群里的小伙伴也可以快速知道提问者的疑惑了。 **注意这里不是说不让大家问问题哈, 而是说问问题之前要有自己的思考,问题要问到点子上!** diff --git a/problems/双指针总结.md b/problems/双指针总结.md index a77490cd..409e80ab 100644 --- a/problems/双指针总结.md +++ b/problems/双指针总结.md @@ -77,7 +77,7 @@ for (int i = 0; i < array.size(); i++) { 时间复杂度可以做到O(n^2),但还是比较费时的,因为不好做剪枝操作。 -所以这道题目使用双指针法才是最为合适的,用双指针做这道题目才能就能真正体会到,**通过前后两个指针不算向中间逼近,在一个for循环下完成两个for循环的工作。** +所以这道题目使用双指针法才是最为合适的,用双指针做这道题目才能就能真正体会到,**通过前后两个指针不断向中间逼近,在一个for循环下完成两个for循环的工作。** 只用双指针法时间复杂度为O(n^2),但比哈希法的O(n^2)效率高得多,哈希法在使用两层for循环的时候,能做的剪枝操作很有限。 diff --git a/problems/周总结/20201107回溯周末总结.md b/problems/周总结/20201107回溯周末总结.md index 2d20a197..7e333c76 100644 --- a/problems/周总结/20201107回溯周末总结.md +++ b/problems/周总结/20201107回溯周末总结.md @@ -75,7 +75,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; 除了这些难点,**本题还有细节,例如:切割过的地方不能重复切割所以递归函数需要传入i + 1**。 -所以本题应该是一个道hard题目了。 +所以本题应该是一道hard题目了。 **本题的树形结构中,和代码的逻辑有一个小出入,已经判断不是回文的子串就不会进入递归了,纠正如下:** diff --git a/problems/栈与队列总结.md b/problems/栈与队列总结.md index 113f4a06..df022c77 100644 --- a/problems/栈与队列总结.md +++ b/problems/栈与队列总结.md @@ -107,7 +107,7 @@ cd a/b/c/../../ 设计单调队列的时候,pop,和push操作要保持如下规则: 1. pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作 -2. push(value):如果push的元素value大于入口元素的数值,那么就将队列出口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止 +2. push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止 保持如上规则,每次窗口移动的时候,只要问que.front()就可以返回当前窗口的最大值。 diff --git a/problems/背包理论基础01背包-1.md b/problems/背包理论基础01背包-1.md index c1644616..d9b953c0 100644 --- a/problems/背包理论基础01背包-1.md +++ b/problems/背包理论基础01背包-1.md @@ -85,7 +85,7 @@ leetcode上没有纯01背包的问题,都是01背包应用方面的题目, i 来表示物品、j表示背包容量。 -(如果想用j 表示物品,i表示背包容量 行不行? 都可以的,个人习惯而已) +(如果想用j 表示物品,i 表示背包容量 行不行? 都可以的,个人习惯而已) 我们来尝试把上面的 二维表格填写一下。 @@ -490,7 +490,7 @@ func max(x, y int) int { ``` -### Javascript +### JavaScript ```js const readline = require('readline').createInterface({ diff --git a/problems/贪心算法理论基础.md b/problems/贪心算法理论基础.md index f042c0ac..6fde2dbb 100644 --- a/problems/贪心算法理论基础.md +++ b/problems/贪心算法理论基础.md @@ -78,7 +78,7 @@ * 求解每一个子问题的最优解 * 将局部最优解堆叠成全局最优解 -这个四步其实过于理论化了,我们平时在做贪心类的题目 很难去按照这四步去思考,真是有点“鸡肋”。 +这个四步其实过于理论化了,我们平时在做贪心类的题目时,如果按照这四步去思考,真是有点“鸡肋”。 做题的时候,只要想清楚 局部最优 是什么,如果推导出全局最优,其实就够了。 diff --git a/problems/面试题02.07.链表相交.md b/problems/面试题02.07.链表相交.md index b0f2e8ea..48944b5e 100644 --- a/problems/面试题02.07.链表相交.md +++ b/problems/面试题02.07.链表相交.md @@ -535,6 +535,45 @@ public ListNode GetIntersectionNode(ListNode headA, ListNode headB) } ``` +### Swift: +```swift +func getIntersectionNode(_ headA: ListNode?, _ headB: ListNode?) -> ListNode? { + var lenA = 0 + var lenB = 0 + var nodeA = headA + var nodeB = headB + // 计算链表A和链表B的长度 + while nodeA != nil { + lenA += 1 + nodeA = nodeA?.next + } + while nodeB != nil { + lenB += 1 + nodeB = nodeB?.next + } + // 重置指针 + nodeA = headA + nodeB = headB + // 如果链表A更长,让它先走lenA-lenB步 + if lenA > lenB { + for _ in 0..<(lenA - lenB) { + nodeA = nodeA?.next + } + } else if lenB > lenA { + // 如果链表B更长,让它先走lenB-lenA步 + for _ in 0..<(lenB - lenA) { + nodeB = nodeB?.next + } + } + // 同时遍历两个链表,寻找交点 + while nodeA !== nodeB { + nodeA = nodeA?.next + nodeB = nodeB?.next + } + return nodeA +} +``` +