diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 6a3e68da..00000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -**/.DS_Store \ No newline at end of file diff --git a/problems/0027.移除元素.md b/problems/0027.移除元素.md index daf8dac3..cb342586 100644 --- a/problems/0027.移除元素.md +++ b/problems/0027.移除元素.md @@ -247,6 +247,24 @@ class Solution: ### Go: +```go +// 暴力法 +// 时间复杂度 O(n^2) +// 空间复杂度 O(1) +func removeElement(nums []int, val int) int { + size := len(nums) + for i := 0; i < size; i ++ { + if nums[i] == val { + for j := i + 1; j < size; j ++ { + nums[j - 1] = nums[j] + } + i -- + size -- + } + } + return size +} +``` ```go // 快慢指针法 // 时间复杂度 O(n) @@ -289,7 +307,6 @@ func removeElement(nums []int, val int) int { right-- } } - fmt.Println(nums) return left } ``` diff --git a/problems/0042.接雨水.md b/problems/0042.接雨水.md index 73d787b1..6d92d2b3 100644 --- a/problems/0042.接雨水.md +++ b/problems/0042.接雨水.md @@ -440,6 +440,33 @@ class Solution { } ``` +双指针优化 +```java +class Solution { + public int trap(int[] height) { + if (height.length <= 2) { + return 0; + } + // 从两边向中间寻找最值 + int maxLeft = height[0], maxRight = height[height.length - 1]; + int l = 1, r = height.length - 2; + int res = 0; + while (l <= r) { + // 不确定上一轮是左边移动还是右边移动,所以两边都需更新最值 + maxLeft = Math.max(maxLeft, height[l]); + maxRight = Math.max(maxRight, height[r]); + // 最值较小的一边所能装的水量已定,所以移动较小的一边。 + if (maxLeft < maxRight) { + res += maxLeft - height[l ++]; + } else { + res += maxRight - height[r --]; + } + } + return res; + } +} +``` + 单调栈法 ```java diff --git a/problems/0062.不同路径.md b/problems/0062.不同路径.md index 207a66ee..32c64a12 100644 --- a/problems/0062.不同路径.md +++ b/problems/0062.不同路径.md @@ -285,6 +285,24 @@ public: } ``` +状态压缩 +```java +class Solution { + public int uniquePaths(int m, int n) { + // 在二维dp数组中,当前值的计算只依赖正上方和正左方,因此可以压缩成一维数组。 + int[] dp = new int[n]; + // 初始化,第一行只能从正左方跳过来,所以只有一条路径。 + Arrays.fill(dp, 1); + for (int i = 1; i < m; i ++) { + // 第一列也只有一条路,不用迭代,所以从第二列开始 + for (int j = 1; j < n; j ++) { + dp[j] += dp[j - 1]; // dp[j] = dp[j] (正上方)+ dp[j - 1] (正左方) + } + } + return dp[n - 1]; + } +} +``` ### Python 递归 diff --git a/problems/0121.买卖股票的最佳时机.md b/problems/0121.买卖股票的最佳时机.md index fb548cbc..60fbc5cc 100644 --- a/problems/0121.买卖股票的最佳时机.md +++ b/problems/0121.买卖股票的最佳时机.md @@ -287,9 +287,6 @@ class Solution { return dp[1]; } } -``` -```Java - ``` ### Python: diff --git a/problems/0347.前K个高频元素.md b/problems/0347.前K个高频元素.md index 93d605f5..b340e185 100644 --- a/problems/0347.前K个高频元素.md +++ b/problems/0347.前K个高频元素.md @@ -218,7 +218,7 @@ class Solution { ``` ### Python: - +解法一: ```python #时间复杂度:O(nlogk) #空间复杂度:O(n) @@ -246,6 +246,31 @@ class Solution: result[i] = heapq.heappop(pri_que)[1] return result ``` +解法二: +```python +class Solution: + def topKFrequent(self, nums: List[int], k: int) -> List[int]: + # 使用字典统计数字出现次数 + time_dict = defaultdict(int) + for num in nums: + time_dict[num] += 1 + # 更改字典,key为出现次数,value为相应的数字的集合 + index_dict = defaultdict(list) + for key in time_dict: + index_dict[time_dict[key]].append(key) + # 排序 + key = list(index_dict.keys()) + key.sort() + result = [] + cnt = 0 + # 获取前k项 + while key and cnt != k: + result += index_dict[key[-1]] + cnt += len(index_dict[key[-1]]) + key.pop() + + return result[0: k] +``` ### Go: diff --git a/problems/0392.判断子序列.md b/problems/0392.判断子序列.md index ebd567cb..caca8cb8 100644 --- a/problems/0392.判断子序列.md +++ b/problems/0392.判断子序列.md @@ -173,6 +173,63 @@ class Solution { } } ``` +> 修改遍历顺序后,可以利用滚动数组,对dp数组进行压缩 +```java +class Solution { + public boolean isSubsequence(String s, String t) { + // 修改遍历顺序,外圈遍历t,内圈遍历s。使得dp的推算只依赖正上方和左上方,方便压缩。 + int[][] dp = new int[t.length() + 1][s.length() + 1]; + for (int i = 1; i < dp.length; i++) { // 遍历t字符串 + for (int j = 1; j < dp[i].length; j++) { // 遍历s字符串 + if (t.charAt(i - 1) == s.charAt(j - 1)) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } else { + dp[i][j] = dp[i - 1][j]; + } + } + System.out.println(Arrays.toString(dp[i])); + } + return dp[t.length()][s.length()] == s.length(); + } +} +``` +> 状态压缩 +```java +class Solution { + public boolean isSubsequence(String s, String t) { + int[] dp = new int[s.length() + 1]; + for (int i = 0; i < t.length(); i ++) { + // 需要使用上一轮的dp[j - 1],所以使用倒序遍历 + for (int j = dp.length - 1; j > 0; j --) { + // i遍历的是t字符串,j遍历的是dp数组,dp数组的长度比s的大1,因此需要减1。 + if (t.charAt(i) == s.charAt(j - 1)) { + dp[j] = dp[j - 1] + 1; + } + } + } + return dp[s.length()] == s.length(); + } +} +``` +> 将dp定义为boolean类型,dp[i]直接表示s.substring(0, i)是否为t的子序列 + +```java +class Solution { + public boolean isSubsequence(String s, String t) { + boolean[] dp = new boolean[s.length() + 1]; + // 表示 “” 是t的子序列 + dp[0] = true; + for (int i = 0; i < t.length(); i ++) { + for (int j = dp.length - 1; j > 0; j --) { + if (t.charAt(i) == s.charAt(j - 1)) { + dp[j] = dp[j - 1]; + } + } + } + return dp[dp.length - 1]; + } +} +``` ### Python: diff --git a/problems/0674.最长连续递增序列.md b/problems/0674.最长连续递增序列.md index ece62944..0ca8a4c9 100644 --- a/problems/0674.最长连续递增序列.md +++ b/problems/0674.最长连续递增序列.md @@ -186,7 +186,23 @@ public: return res; } ``` - +> 动态规划状态压缩 +```java +class Solution { + public int findLengthOfLCIS(int[] nums) { + // 记录以 前一个元素结尾的最长连续递增序列的长度 和 以当前 结尾的...... + int beforeOneMaxLen = 1, currentMaxLen = 0; + // res 赋最小值返回的最小值1 + int res = 1; + for (int i = 1; i < nums.length; i ++) { + currentMaxLen = nums[i] > nums[i - 1] ? beforeOneMaxLen + 1 : 1; + beforeOneMaxLen = currentMaxLen; + res = Math.max(res, currentMaxLen); + } + return res; + } +} +``` > 贪心法: ```Java diff --git a/problems/0746.使用最小花费爬楼梯.md b/problems/0746.使用最小花费爬楼梯.md index d13ff19f..6320ed89 100644 --- a/problems/0746.使用最小花费爬楼梯.md +++ b/problems/0746.使用最小花费爬楼梯.md @@ -244,6 +244,24 @@ class Solution { } ``` +```Java +// 状态压缩,使用三个变量来代替数组 +class Solution { + public int minCostClimbingStairs(int[] cost) { + // 以下三个变量分别表示前两个台阶的最少费用、前一个的、当前的。 + int beforeTwoCost = 0, beforeOneCost = 0, currentCost = 0; + // 前两个台阶不需要费用就能上到,因此从下标2开始;因为最后一个台阶需要跨越,所以需要遍历到cost.length + for (int i = 2; i <= cost.length; i ++) { + // 此处遍历的是cost[i - 1],不会越界 + currentCost = Math.min(beforeOneCost + cost[i - 1], beforeTwoCost + cost[i - 2]); + beforeTwoCost = beforeOneCost; + beforeOneCost = currentCost; + } + return currentCost; + } +} +``` + ### Python 动态规划(版本一) diff --git a/problems/kama54.替换数字.md b/problems/kama54.替换数字.md index 7dc22c0a..45a0aa54 100644 --- a/problems/kama54.替换数字.md +++ b/problems/kama54.替换数字.md @@ -146,17 +146,42 @@ for (int i = 0; i < a.size(); i++) { ```java import java.util.Scanner; -class Main { - public static void main(String[] args) { - Scanner in = new Scanner(System.in); - String s = in.nextLine(); - StringBuilder sb = new StringBuilder(); +public class Main { + + public static String replaceNumber(String s) { + int count = 0; // 统计数字的个数 + int sOldSize = s.length(); for (int i = 0; i < s.length(); i++) { - if (Character.isDigit(s.charAt(i))) { - sb.append("number"); - }else sb.append(s.charAt(i)); + if(Character.isDigit(s.charAt(i))){ + count++; + } } - System.out.println(sb); + // 扩充字符串s的大小,也就是每个空格替换成"number"之后的大小 + char[] newS = new char[s.length() + count * 5]; + int sNewSize = newS.length; + // 将旧字符串的内容填入新数组 + System.arraycopy(s.toCharArray(), 0, newS, 0, sOldSize); + // 从后先前将空格替换为"number" + for (int i = sNewSize - 1, j = sOldSize - 1; j < i; j--, i--) { + if (!Character.isDigit(newS[j])) { + newS[i] = newS[j]; + } else { + newS[i] = 'r'; + newS[i - 1] = 'e'; + newS[i - 2] = 'b'; + newS[i - 3] = 'm'; + newS[i - 4] = 'u'; + newS[i - 5] = 'n'; + i -= 5; + } + } + return new String(newS); + }; + public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + String s = scanner.next(); + System.out.println(replaceNumber(s)); + scanner.close(); } } ```