diff --git a/problems/0024.两两交换链表中的节点.md b/problems/0024.两两交换链表中的节点.md index 905a0539..10337a7f 100644 --- a/problems/0024.两两交换链表中的节点.md +++ b/problems/0024.两两交换链表中的节点.md @@ -337,5 +337,48 @@ object Solution { } ``` +PHP: +```php +//虚拟头结点 +function swapPairs($head) { + if ($head == null || $head->next == null) { + return $head; + } + + $dummyNode = new ListNode(0, $head); + $preNode = $dummyNode; //虚拟头结点 + $curNode = $head; + $nextNode = $head->next; + while($curNode && $nextNode) { + $nextNextNode = $nextNode->next; //存下一个节点 + $nextNode->next = $curNode; //交换curHead 和 nextHead + $curNode->next = $nextNextNode; + $preNode->next = $nextNode; //上一个节点的下一个指向指向nextHead + + //更新当前的几个指针 + $preNode = $preNode->next->next; + $curNode = $nextNextNode; + $nextNode = $nextNextNode->next; + } + + return $dummyNode->next; +} + +//递归版本 +function swapPairs($head) +{ + // 终止条件 + if ($head === null || $head->next === null) { + return $head; + } + + //结果要返回的头结点 + $next = $head->next; + $head->next = $this->swapPairs($next->next); //当前头结点->next指向更新 + $next->next = $head; //当前第二个节点的->next指向更新 + return $next; //返回翻转后的头结点 +} +``` + -----------------------
diff --git a/problems/0027.移除元素.md b/problems/0027.移除元素.md index 12f88c75..33e0c8c1 100644 --- a/problems/0027.移除元素.md +++ b/problems/0027.移除元素.md @@ -146,7 +146,7 @@ public: }; ``` - + ## 相关题目推荐 * 26.删除排序数组中的重复项 @@ -154,10 +154,6 @@ public: * 844.比较含退格的字符串 * 977.有序数组的平方 - - - - ## 其他语言版本 @@ -165,18 +161,35 @@ Java: ```java class Solution { public int removeElement(int[] nums, int val) { - // 快慢指针 - int fastIndex = 0; - int slowIndex; - for (slowIndex = 0; fastIndex < nums.length; fastIndex++) { + int slowIndex = 0; + for (int fastIndex = 0; fastIndex < nums.length; fastIndex++) { if (nums[fastIndex] != val) { nums[slowIndex] = nums[fastIndex]; slowIndex++; } } return slowIndex; - + } +} +``` +```java +//相向双指针法 +class Solution { + public int removeElement(int[] nums, int val) { + int left = 0; + int right = nums.length - 1; + while(right >= 0 && nums[right] == val) right--; //将right移到从右数第一个值不为val的位置 + while(left <= right) { + if(nums[left] == val) { //left位置的元素需要移除 + //将right位置的元素移到left(覆盖),right位置移除 + nums[left] = nums[right]; + right--; + } + left++; + while(right >= 0 && nums[right] == val) right--; + } + return left; } } ``` diff --git a/problems/0035.搜索插入位置.md b/problems/0035.搜索插入位置.md index f2bbb8ca..5ed3ac56 100644 --- a/problems/0035.搜索插入位置.md +++ b/problems/0035.搜索插入位置.md @@ -226,7 +226,32 @@ class Solution { } } ``` +```java +//第二种二分法:左闭右开 +public int searchInsert(int[] nums, int target) { + int left = 0; + int right = nums.length; + while (left < right) { //左闭右开 [left, right) + int middle = left + ((right - left) >> 1); + if (nums[middle] > target) { + right = middle; // target 在左区间,在[left, middle)中 + } else if (nums[middle] < target) { + left = middle + 1; // target 在右区间,在 [middle+1, right)中 + } else { // nums[middle] == target + return middle; // 数组中找到目标值的情况,直接返回下标 + } + } + // 目标值在数组所有元素之前 [0,0) + // 目标值插入数组中的位置 [left, right) ,return right 即可 + // 目标值在数组所有元素之后的情况 [left, right),因为是右开区间,所以 return right + return right; +} +``` + + + Golang: + ```golang // 第一种二分法 func searchInsert(nums []int, target int) int { diff --git a/problems/0077.组合.md b/problems/0077.组合.md index 5a4811ba..17e4fb35 100644 --- a/problems/0077.组合.md +++ b/problems/0077.组合.md @@ -114,7 +114,7 @@ vector> result; // 存放符合条件结果的集合 vector path; // 用来存放符合条件结果 ``` -其实不定义这两个全局遍历也是可以的,把这两个变量放进递归函数的参数里,但函数里参数太多影响可读性,所以我定义全局变量了。 +其实不定义这两个全局变量也是可以的,把这两个变量放进递归函数的参数里,但函数里参数太多影响可读性,所以我定义全局变量了。 函数里一定有两个参数,既然是集合n里面取k的数,那么n和k是两个int型的参数。 diff --git a/problems/0096.不同的二叉搜索树.md b/problems/0096.不同的二叉搜索树.md index 9adc0457..51de1e23 100644 --- a/problems/0096.不同的二叉搜索树.md +++ b/problems/0096.不同的二叉搜索树.md @@ -252,6 +252,24 @@ function numTrees(n: number): number { }; ``` +### Rust + +```Rust +impl Solution { + pub fn num_trees(n: i32) -> i32 { + let n = n as usize; + let mut dp = vec![0; n + 1]; + dp[0] = 1; + for i in 1..=n { + for j in 1..=i { + dp[i] += dp[j - 1] * dp[i - j]; + } + } + dp[n] + } +} +``` + ### C ```c diff --git a/problems/0131.分割回文串.md b/problems/0131.分割回文串.md index a54d6576..37132503 100644 --- a/problems/0131.分割回文串.md +++ b/problems/0131.分割回文串.md @@ -206,6 +206,65 @@ public: return result; } }; +``` +# 优化 + +上面的代码还存在一定的优化空间, 在于如何更高效的计算一个子字符串是否是回文字串。上述代码```isPalindrome```函数运用双指针的方法来判定对于一个字符串```s```, 给定起始下标和终止下标, 截取出的子字符串是否是回文字串。但是其中有一定的重复计算存在: + +例如给定字符串```"abcde"```, 在已知```"bcd"```不是回文字串时, 不再需要去双指针操作```"abcde"```而可以直接判定它一定不是回文字串。 + +具体来说, 给定一个字符串`s`, 长度为```n```, 它成为回文字串的充分必要条件是```s[0] == s[n-1]```且```s[1:n-1]```是回文字串。 + +大家如果熟悉动态规划这种算法的话, 我们可以高效地事先一次性计算出, 针对一个字符串```s```, 它的任何子串是否是回文字串, 然后在我们的回溯函数中直接查询即可, 省去了双指针移动判定这一步骤. + +具体参考代码如下: + +```CPP +class Solution { +private: + vector> result; + vector path; // 放已经回文的子串 + vector> isPalindrome; // 放事先计算好的是否回文子串的结果 + void backtracking (const string& s, int startIndex) { + // 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了 + if (startIndex >= s.size()) { + result.push_back(path); + return; + } + for (int i = startIndex; i < s.size(); i++) { + if (isPalindrome[startIndex][i]) { // 是回文子串 + // 获取[startIndex,i]在s中的子串 + string str = s.substr(startIndex, i - startIndex + 1); + path.push_back(str); + } else { // 不是回文,跳过 + continue; + } + backtracking(s, i + 1); // 寻找i+1为起始位置的子串 + path.pop_back(); // 回溯过程,弹出本次已经填在的子串 + } + } + void computePalindrome(const string& s) { + // isPalindrome[i][j] 代表 s[i:j](双边包括)是否是回文字串 + isPalindrome.resize(s.size(), vector(s.size(), false)); // 根据字符串s, 刷新布尔矩阵的大小 + for (int i = s.size() - 1; i >= 0; i--) { + // 需要倒序计算, 保证在i行时, i+1行已经计算好了 + for (int j = i; j < s.size(); j++) { + if (j == i) {isPalindrome[i][j] = true;} + else if (j - i == 1) {isPalindrome[i][j] = (s[i] == s[j]);} + else {isPalindrome[i][j] = (s[i] == s[j] && isPalindrome[i+1][j-1]);} + } + } + } +public: + vector> partition(string s) { + result.clear(); + path.clear(); + computePalindrome(s); + backtracking(s, 0); + return result; + } +}; + ``` # 总结 diff --git a/problems/0150.逆波兰表达式求值.md b/problems/0150.逆波兰表达式求值.md index 21bd8593..8107e4e0 100644 --- a/problems/0150.逆波兰表达式求值.md +++ b/problems/0150.逆波兰表达式求值.md @@ -65,6 +65,8 @@ # 思路 +《代码随想录》算法视频公开课:[栈的最后表演! | LeetCode:150. 逆波兰表达式求值](https://www.bilibili.com/video/BV1kd4y1o7on),相信结合视频在看本篇题解,更有助于大家对本题的理解。 + 在上一篇文章中[1047.删除字符串中的所有相邻重复项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)提到了 递归就是用栈来实现的。 所以**栈与递归之间在某种程度上是可以转换的!** 这一点我们在后续讲解二叉树的时候,会更详细的讲解到。 diff --git a/problems/0239.滑动窗口最大值.md b/problems/0239.滑动窗口最大值.md index 6ec98506..3f402f12 100644 --- a/problems/0239.滑动窗口最大值.md +++ b/problems/0239.滑动窗口最大值.md @@ -32,6 +32,8 @@ # 思路 +《代码随想录》算法视频公开课:[单调队列正式登场!| LeetCode:239. 滑动窗口最大值](https://www.bilibili.com/video/BV1XS4y1p7qj),相信结合视频在看本篇题解,更有助于大家对本题的理解。 + 这是使用单调队列的经典题目。 难点是如何求一个区间里的最大值呢? (这好像是废话),暴力一下不就得了。 diff --git a/problems/0343.整数拆分.md b/problems/0343.整数拆分.md index a4d532fd..0a568b57 100644 --- a/problems/0343.整数拆分.md +++ b/problems/0343.整数拆分.md @@ -192,7 +192,7 @@ public: ## 其他语言版本 -### Java +### Java ```Java class Solution { public int integerBreak(int n) { @@ -259,6 +259,21 @@ func max(a,b int) int{ } ``` +### Rust +```rust +pub fn integer_break(n: i32) -> i32 { + let n = n as usize; + let mut dp = vec![0; n + 1]; + dp[2] = 1; + for i in 3..=n { + for j in 1..i-1 { + dp[i] = dp[i].max((i - j) * j).max(dp[i - j] * j); + } + } + dp[n] as i32 +} +``` + ### Javascript ```Javascript var integerBreak = function(n) { @@ -299,6 +314,27 @@ function integerBreak(n: number): number { }; ``` +### Rust + +```Rust +impl Solution { + fn max(a: i32, b: i32) -> i32{ + if a > b { a } else { b } + } + pub fn integer_break(n: i32) -> i32 { + let n = n as usize; + let mut dp = vec![0; n + 1]; + dp[2] = 1; + for i in 3..=n { + for j in 1..i - 1 { + dp[i] = Self::max(dp[i], Self::max(((i - j) * j) as i32, dp[i - j] * j as i32)); + } + } + dp[n] + } +} +``` + ### C ```c diff --git a/problems/0347.前K个高频元素.md b/problems/0347.前K个高频元素.md index 430dcea1..d4059b9b 100644 --- a/problems/0347.前K个高频元素.md +++ b/problems/0347.前K个高频元素.md @@ -31,6 +31,14 @@ # 思路 +《代码随想录》算法视频公开课:[优先级队列正式登场!大顶堆、小顶堆该怎么用?| LeetCode:347.前 K 个高频元素](https://www.bilibili.com/video/BV1Xg41167Lz),相信结合视频在看本篇题解,更有助于大家对本题的理解。 + +

+ +

+ + + 这道题目主要涉及到如下三块内容: 1. 要统计元素出现频率 2. 对频率排序 diff --git a/problems/0376.摆动序列.md b/problems/0376.摆动序列.md index a41a0f0a..d15ed2d0 100644 --- a/problems/0376.摆动序列.md +++ b/problems/0376.摆动序列.md @@ -266,22 +266,58 @@ class Solution: ### Go +**贪心** ```golang func wiggleMaxLength(nums []int) int { - var count,preDiff,curDiff int - count=1 - if len(nums)<2{ - return count - } - for i:=0;i 0 && preDiff <= 0) || (preDiff >= 0 && curDiff < 0){ - preDiff=curDiff - count++ - } - } - return count + var count, preDiff, curDiff int //初始化默认为0 + count = 1 // 初始化为1,因为最小的序列是1个数 + if len(nums) < 2 { + return count + } + for i := 0; i < len(nums)-1; i++ { + curDiff = nums[i+1] - nums[i] + if (curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0) { + count++ + } + } + return count +} +``` + +**动态规划** +```golang +func wiggleMaxLength(nums []int) int { + n := len(nums) + if n <= 1 { + return n + } + dp := make([][2]int, n) + // i 0 作为波峰的最大长度 + // i 1 作为波谷的最大长度 + dp[0][0] = 1 + dp[0][1] = 1 + for i := 0; i < n; i++ { + for j := 0; j < i; j++ { + if nums[j] > nums[i] { //nums[i]为波谷 + dp[i][1] = max(dp[i][1], dp[j][0]+1) + } + if nums[j] < nums[i] { //nums[i]为波峰 或者相等 + dp[i][0] = max(dp[i][0], dp[j][1]+1) + } + if nums[j] == nums[i] { //添加一种情况,nums[i]为相等 + dp[i][0] = max(dp[i][0], dp[j][0]) //波峰 + dp[i][1] = max(dp[i][1], dp[j][1]) //波谷 + } + } + } + return max(dp[n-1][0], dp[n-1][1]) +} +func max(a, b int) int { + if a > b { + return a + } else { + return b + } } ``` diff --git a/problems/0416.分割等和子集.md b/problems/0416.分割等和子集.md index 83b267ac..03eae8ef 100644 --- a/problems/0416.分割等和子集.md +++ b/problems/0416.分割等和子集.md @@ -417,7 +417,32 @@ var canPartition = function(nums) { ``` +### Rust +```Rust +impl Solution { + fn max(a: usize, b: usize) -> usize { + if a > b { a } else { b } + } + pub fn can_partition(nums: Vec) -> bool { + let nums = nums.iter().map(|x| *x as usize).collect::>(); + let mut sum = 0; + let mut dp: Vec = vec![0; 10001]; + for i in 0..nums.len() { + sum += nums[i]; + } + if sum % 2 == 1 { return false; } + let target = sum / 2; + for i in 0..nums.len() { + for j in (nums[i]..=target).rev() { + dp[j] = Self::max(dp[j], dp[j - nums[i]] + nums[i]); + } + } + if dp[target] == target { return true; } + false + } +} +``` ### C: diff --git a/problems/0695.岛屿的最大面积.md b/problems/0695.岛屿的最大面积.md index b0945292..3739882a 100644 --- a/problems/0695.岛屿的最大面积.md +++ b/problems/0695.岛屿的最大面积.md @@ -16,6 +16,17 @@ # 思路 +这道题目也是 dfs bfs基础类题目。 + + +## DFS + +很多同学,写dfs其实也是凭感觉来,有的时候dfs函数中写终止条件才能过,有的时候 dfs函数不写终止添加也能过! + +这里其实涉及到dfs的两种写法, + +以下代码使用dfs实现,如果对dfs不太了解的话,建议先看这篇题解:[797.所有可能的路径](https://leetcode.cn/problems/all-paths-from-source-to-target/solution/by-carlsun-2-66pf/), + 写法一,dfs只处理下一个节点 ```CPP class Solution { @@ -94,3 +105,55 @@ public: } }; ``` + +以上两种写法的区别,我在题解: [DFS,BDF 你没注意的细节都给你列出来了!LeetCode:200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/solution/by-carlsun-2-n72a/)做了详细介绍。 + +## BFS + +```CPP +class Solution { +private: + int count; + int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向 + void bfs(vector>& grid, vector>& visited, int x, int y) { + queue que; + que.push(x); + que.push(y); + visited[x][y] = true; // 加入队列就意味节点是陆地可到达的点 + count++; + while(!que.empty()) { + int xx = que.front();que.pop(); + int yy = que.front();que.pop(); + for (int i = 0 ;i < 4; i++) { + int nextx = xx + dir[i][0]; + int nexty = yy + dir[i][1]; + if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue; // 越界 + if (!visited[nextx][nexty] && grid[nextx][nexty] == 1) { // 节点没有被访问过且是陆地 + visited[nextx][nexty] = true; + count++; + que.push(nextx); + que.push(nexty); + } + } + } + } + +public: + int maxAreaOfIsland(vector>& grid) { + int n = grid.size(), m = grid[0].size(); + vector> visited = vector>(n, vector(m, false)); + int result = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (!visited[i][j] && grid[i][j] == 1) { + count = 0; + bfs(grid, visited, i, j); // 将与其链接的陆地都标记上 true + result = max(result, count); + } + } + } + return result; + } +}; + +``` diff --git a/problems/0704.二分查找.md b/problems/0704.二分查找.md index eec97a78..53f5331a 100644 --- a/problems/0704.二分查找.md +++ b/problems/0704.二分查找.md @@ -290,10 +290,11 @@ func search(nums []int, target int) int { */ var search = function(nums, target) { // right是数组最后一个数的下标,num[right]在查找范围内,是左闭右闭区间 - let left = 0, right = nums.length - 1; + let mid, left = 0, right = nums.length - 1; // 当left=right时,由于nums[right]在查找范围内,所以要包括此情况 while (left <= right) { - let mid = left + Math.floor((right - left)/2); + // 位运算 + 防止大数溢出 + mid = left + ((right - left) >> 1); // 如果中间数大于目标值,要把中间数排除查找范围,所以右边界更新为mid-1;如果右边界更新为mid,那中间数还在下次查找范围内 if (nums[mid] > target) { right = mid - 1; // 去左面闭区间寻找 @@ -316,10 +317,11 @@ var search = function(nums, target) { */ var search = function(nums, target) { // right是数组最后一个数的下标+1,nums[right]不在查找范围内,是左闭右开区间 - let left = 0, right = nums.length; + let mid, left = 0, right = nums.length; // 当left=right时,由于nums[right]不在查找范围,所以不必包括此情况 while (left < right) { - let mid = left + Math.floor((right - left)/2); + // 位运算 + 防止大数溢出 + mid = left + ((right - left) >> 1); // 如果中间值大于目标值,中间值不应在下次查找的范围内,但中间值的前一个值应在; // 由于right本来就不在查找范围内,所以将右边界更新为中间值,如果更新右边界为mid-1则将中间值的前一个值也踢出了下次寻找范围 if (nums[mid] > target) { @@ -340,9 +342,10 @@ var search = function(nums, target) { ```typescript function search(nums: number[], target: number): number { - let left: number = 0, right: number = nums.length - 1; + let mid: number, left: number = 0, right: number = nums.length - 1; while (left <= right) { - let mid: number = left + Math.floor((right - left) / 2); + // 位运算 + 防止大数溢出 + mid = left + ((right - left) >> 1); if (nums[mid] > target) { right = mid - 1; } else if (nums[mid] < target) { @@ -359,9 +362,10 @@ function search(nums: number[], target: number): number { ```typescript function search(nums: number[], target: number): number { - let left: number = 0, right: number = nums.length; + let mid: number, left: number = 0, right: number = nums.length; while (left < right) { - let mid: number = left + Math.floor((right - left) / 2); + // 位运算 + 防止大数溢出 + mid = left +((right - left) >> 1); if (nums[mid] > target) { right = mid; } else if (nums[mid] < target) { diff --git a/problems/0922.按奇偶排序数组II.md b/problems/0922.按奇偶排序数组II.md index 49547a15..4e281a3c 100644 --- a/problems/0922.按奇偶排序数组II.md +++ b/problems/0922.按奇偶排序数组II.md @@ -147,6 +147,59 @@ class Solution { } ``` +### java + +```java +//方法一:采用额外的数组空间 +class Solution { + public int[] sortArrayByParityII(int[] nums) { + //定义结果数组 result + int[] result = new int[nums.length]; + int even = 0, odd = 1; + for(int i = 0; i < nums.length; i++){ + //如果为偶数 + if(nums[i] % 2 == 0){ + result[even] = nums[i]; + even += 2; + }else{ + result[odd] = nums[i]; + odd += 2; + } + } + return result; + } +} +``` +```java +//方法二:不采用额外的数组空间 +class Solution922 { + public int[] sortArrayByParityII(int[] nums) { + //定义双指针 + int oddPoint = 1, evenPoint = 0; + //开始移动并交换,最后一层必然为相互交换后再移动或者相同直接移动 + while(oddPoint < nums.length && evenPoint < nums.length){ + //进行判断 + if(nums[oddPoint] % 2 == 0 && nums[evenPoint] % 2 == 1){ //如果均不满足 + int temp = 0; + temp = nums[oddPoint]; + nums[oddPoint] = nums[evenPoint]; + nums[evenPoint] = temp; + oddPoint += 2; + evenPoint += 2; + }else if(nums[oddPoint] % 2 == 0 && nums[evenPoint] % 2 == 0){ //偶数满足 + evenPoint += 2; + }else if(nums[oddPoint] % 2 == 1 && nums[evenPoint] % 2 == 1){ //奇数满足 + oddPoint += 2; + }else{ + oddPoint += 2; + evenPoint += 2; + } + } + return nums; + } +} +``` + ### Python3 ```python diff --git a/problems/面试题02.07.链表相交.md b/problems/面试题02.07.链表相交.md index abf7a2f8..6d6810a3 100644 --- a/problems/面试题02.07.链表相交.md +++ b/problems/面试题02.07.链表相交.md @@ -5,7 +5,9 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-# 面试题 02.07. 链表相交 +# 面试题 02.07. 链表相交 + +同:160.链表相交 [力扣题目链接](https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/) diff --git a/添加0222.完全二叉树的节点个数Go版本.md b/添加0222.完全二叉树的节点个数Go版本.md new file mode 100644 index 00000000..6001e7b7 --- /dev/null +++ b/添加0222.完全二叉树的节点个数Go版本.md @@ -0,0 +1,25 @@ +```go +func countNodes(root *TreeNode) int { + if root == nil { + return 0 + } + q := list.New() + q.PushBack(root) + res := 0 + for q.Len() > 0 { + n := q.Len() + for i := 0; i < n; i++ { + node := q.Remove(q.Front()).(*TreeNode) + if node.Left != nil { + q.PushBack(node.Left) + } + if node.Right != nil { + q.PushBack(node.Right) + } + res++ + } + } + return res +} +``` + diff --git a/添加559.n叉树的最大深度Go版本.md b/添加559.n叉树的最大深度Go版本.md new file mode 100644 index 00000000..3172837d --- /dev/null +++ b/添加559.n叉树的最大深度Go版本.md @@ -0,0 +1,22 @@ +```go +func maxDepth(root *Node) int { + if root == nil { + return 0 + } + q := list.New() + q.PushBack(root) + depth := 0 + for q.Len() > 0 { + n := q.Len() + for i := 0; i < n; i++ { + node := q.Remove(q.Front()).(*Node) + for j := range node.Children { + q.PushBack(node.Children[j]) + } + } + depth++ + } + return depth +} +``` +