1 82. 删除排序链表中的重复元素 II
错误原因:不确定要设定几个指针,以及指针的初始化
分析:
1.因为是重复元素都要删除(不是还要留一个)因此第一个元素也可能会删除,所以引入dummyNode和连接指针con
2.因为需要比较一个节点是否与他前面的节点相同,因此需要引入cur 和 curPre。
3.第一个节点就需要进行判读是否删除,因此cur = head, curPre = dummyNode, con = dummyNode.
4.最后con.next = null?怎么理解?把后面没连接的断掉。
2 49. 字母异位词分组
错误原因:思路错了,用的是朴素的O(N2)
分析:内容相同的string,他们的hash值是一样的,因此先对string数组里面的字符排序,然后放入一个
Map<String, List<String>> hm中
最后返回的new ArrayList<List>(hm.values());
Map<String, List<String>> hm = new HashMap<>();
for(String str : strs){
char[] temp = str.toCharArray();
Arrays.sort(temp);
String key = temp.toString();
List<String> list = hm.getOrDefault(key, new ArrayList<String>());
list.add(key);
hm.put(key, list);
}
return new ArrayList<List<String>>(hm.values());
3 15. 三数之和
错误原因: 每轮重置之后,没有重置j为nums.length-1 该死!
4 239. 滑动窗口最大值
错误原因:
for(int i = k; i<nums.length; i++){
//移除左边的一位 //错误1,这里的i-k我写成了i;
if(nums[i-k] == q.peekFirst()){
q.pollFirst();
}
//增加右边的一位
if(q.size() == 0){
q.offerLast(nums[i]);
}else if(nums[i] <= q.peekLast()){
q.offerLast(nums[i]);
}else if(nums[i] > q.peekLast()){
while(true){
q.pollLast(); //错误1,这里的>=我写成了>;
if(q.size() == 0 || q.peekLast() >= nums[i]){
break;
}
}
q.offerLast(nums[i]);
}
res[i-k+1] = q.peekFirst();
}
5 347. 前 K 个高频元素
错误原因
1.不记得for(int key : hm.keySet()) hm.keySet() 和hm.values() 分别对应key的set和value的集合。
因此keySet可以用for each来遍历。
2.for(int i : nums) 不规范,foreach容易把i当成是下标
这里要声明,之后使用一定要是for(int num : nums),i专属于下标!!!
6 144. 二叉树的前序遍历
错误原因:
while(!stack.isEmpty() || nd != root)
应该写成
while(!stack.isEmpty() || nd != null)
7 117. 填充每个节点的下一个右侧节点指针 II
错误原因:遍历的顺序错了,要先递归root.right,再递归root.left
否则你会发现root.next还没有连接。。。
8 111. 二叉树的最小深度
错误原因:搞清楚定义
9 101. 对称二叉树
错误原因:比较两个节点相等是比较.val而不是节点本身!!
10 257. 二叉树的所有路径
错误原因:二叉树的回溯跟 排列组合的回溯是有区别的, 以后就像下面这么写吧,是一个比较接近的形式
public void dfs(TreeNode root){
if(root == null) return;
sb.append(root.val);
if(root.left == null && root.right == null){
res.add(sb.toString());
return;
}
if(root.left != null){
dfs(root.left);
sb.deleteCharAt(sb.length()-1);
}
if(root.right != null){
dfs(root.right);
sb.deleteCharAt(sb.length()-1);
}
}
11 617. 合并二叉树
错误原因:一直以root1为视角,root1为null的时候,必须return root2,否则就一直是在root1上做连接。那些本来差很远的root2的节点也连到了root1的子树上。
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
/*正确
if(root1 == null) return root2;
if(root2 == null) return root1;
*/
/*错误
if(root1 == null && root2 == null) return null;
if(root1 == null){
root1 = new TreeNode(root2.val);
return root1;
}else if(root2 == null){
return root1;
}
*/
root1.val = root1.val + root2.val;
root1.left = mergeTrees(root1.left, root2.left);
root1.right = mergeTrees(root1.right, root2.right);
return root1;
}
12 501. 二叉搜索树中的众数
错误原因:细节,需要自己把握。
13 236. 二叉树的最近公共祖先
错误原因:
1.因为是从最底层往上return,应该用后序遍历
2.细节上,root.left == q && root.right == q 这样的错误。。。
14 450. 删除二叉搜索树中的节点
错误原因:没思路
如果删除的节点存在左右节点,假设root = 被删除节点,这里有两种写法
/*******************第一种写法************************/
cur = root.right;
然后cur像左遍历找到那个最小的值
巧妙的来了,交换root和cur的值,然后root.right = dfs(root.right, root.val); 真聪明啊
/*******************第二种写法************************/
cur找到那个最小值后
cur.left = root.left;
return root.right;
相当于是把root的right给return上去了呢,,妙啊
==15 491. 递增子序列==
有重复元素的全排列问题,和有重复元素的递增子序列问题,使用used数组都是为了防止在同一层pick相等的元素。
但是:
- 全排列是有序的,重复的元素会相邻出现,我们只需要判断nums[i]和nums[i-1] 并判断used[i-1] == 0就知道是同一层使用了重复值
而递增子序列是无序的,因此我们必须把元素的值nums[i]而不是下标i 当成下标,准备一个足够大的used[]数组。
-
我们又要保证,这个元素只是在同一层无法再使用,怎么办呢?
我们知道for循环代表这一层,因此在for() 之前使得used 置为零,在for循环体最后才使得used[nums[i]] = 1;
这样就会使得return之后,重新回到for循环时,这个新加的值变成了使用过的值。
for()循环跑完后,返回到上一层时,我们的used又是新的全部是0的值,完美。
used = new int[201]; //******在for循环整体跑完后,return到上一层,used[]又新为0 for(int i = start; i<nums.length; i++){ if(list.size()>0 && nums[i] < list.get(list.size()-1)) continue; if(used[nums[i] + 100] == 1) continue; list.add(nums[i]); dfs(nums, used, i+1); list.remove(list.size()-1); used[nums[i] + 100] = 1; //*******这个点return之后,前往同一层的下一个节点,此时将used[]置为1 }
16 746. 使用最小花费爬楼梯
错误原因:注意这里台阶数是要比cost数多1的哦,因为最后一级台阶不需要花费。
//dp[i] 表示登上第i级台阶的最小花费。
public int minCostClimbingStairs(int[] cost) {
int n = cost.length;
int[] dp = new int[n + 1];
dp[0] = dp[1] = 0;
for (int i = 2; i <= n; i++) {
dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
}
return dp[n];
}
17 63. 不同路径 II
错误原因:dp[0] [0] 并不是直接等于1,要看00这个点有没有障碍物,初始值错了。。
18 343. 整数拆分
错误原因:dp[i]表示i这个数至少切成两段的最大值,因此有切两段,切n段。。取最大值,这个切n段用 j*dp[i-j]来表示。
for(int i = 3; i<=n; i++){
for(int j = 1; j < i; j++){ //j代表第一刀切的位置
dp[i] = Math.max(dp[i], Math.max(j*(i-j), j*dp[i-j]));
} //切两刀 切三刀
}
19 96. 不同的二叉搜索树
其实这道题我做对了,但是跟官方思路不太一样
我的思路:新增的节点最大,他要么做前面结构的右子树,要么前面结构做他的左子树,这就是dp[i] = dp[i-1] + dp[i-1];
另外,这个最大的节点还可以做中间节点,其他节点分别在他上面和下面,于是temp += dp[j]dp[i-1-j];
最终得到 dp[i] = 2*dp[i-1] + temp;
官方思路:每个节点分别做根节点,他的左右子树的类是dp[j]*dp[i-1-j] ;j从1到i,即从第一个节点开始到最新节点分别做根节点。
20 01背包问题
https://programmercarl.com/%E8%83%8C%E5%8C%85%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%8001%E8%83%8C%E5%8C%85-1.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qlXxnql8-1681708954746)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211224142459150.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9FLTkbc0-1681708954747)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211224142536975.png)]
①二维数组的模式
- dp[i] [j]表示从0-i物品中任意取,背包容量为j的背包最多能装下的物品价值。
- 物品i和容量j的顺序可以互换。
for(int i = 1; i<x; i++){
for(int j = 1; j<=y; j++){
if(j < weight[i]){
dp[i][j] = dp[i-1][j];
}else{
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]);
}
}
}
②一维数组的模式
- dp[i] [j]表示从0-i物品中任意取,背包容量为j的背包最多能装下的物品价值。
- 必须先物品后容量,且容量是倒序,否则可能同一个物品被拿多次。dp[2] = dp[2-1] + value[0]; 此时dp[1]表示拿了物品0,现在又拿一次。而用dp[2] = dp[2-1](值为0) + value[0],则是这个背包第一次拿物品0.
for(int i = 0; i<x; i++){
for(int j = y; j>=0; j--){
if(j < weight[i]){
dp[j] = dp[j];
}else{
dp[j] = Math.max(dp[j], dp[j-weight[i]] + value[i]);
}
}
}
//可以再修饰下,让j的容量始终大于weight[i],就不会有if else
for(int i = 0; i<x; i++){
for(int j = y; j>=weight[i]; j--){
dp[j] = Math.max(dp[j], dp[j-weight[i]] + value[i]);
}
}
}
21 1049. 最后一块石头的重量 II
错误原因: return (sum - dp[sum/2]) - dp[sum/2];
22 494. 目标和
错误原因:题目问的是有几种方法,因此dp[j] 也应表示为方法数。
解题思路: 凡是问几种方法的,都是dp[j] += dp[j-nums[i]];
dp[0] = 1;
dp[j] += dp[j-nums[i]];
//而且size可能是负数!
23 474. 一和零
错误原因:没思路
解题思路:翻译下,装0的背包容量m, 装1的背包n,请问最多能装的物品数量。(没有价值呢还)
24 完全背包
- 由于物品可以拿多次,因此j的顺序是正的。
- 而且物品和背包的顺序可以颠倒。
for(int i = 0; i<x; i++){
for(int j = weight[i]; j<= y; j++){
dp[j] = Math.max(dp[j], dp[j-weight[i]] + value[i]);
}
}
}
25 377. 组合总和 Ⅳ
错误原因:没有搞清楚是先循环物品还是先循环背包
由于{1,2}和{2,1}都有效,因此先循环背包再循环物品,否则2永远在1后面,不会有{2,1}的情况。
for(int j = 1; j<=target; j++){
for(int i = 0; i<nums.length; i++){
if(j < nums[i]){
dp[j] = dp[j];
}else if(j >= nums[i]){
dp[j] += dp[j-nums[i]];
}
}
}
26 322. 零钱兑换
错误原因:
1.初始化 dp[0] = 0,而其他值都应该初始化为一个大于最大值的值amount + 1,因为我们将使用Math.min(xx);
2.递推关系 dp[j] = Math.min(dp[j], dp[j-coins[i]] + 1);
27 121. 买卖股票的最佳时机
错误原因:dp[i-1] [0] 不等于0!
//dp[i][0] 代表不持有
//dp[i][1] 代表持有
dp[0][0] = 0;
dp[0][1] = -prices[0];
for(int i = 1; i<prices.length; i++){
//今天没有,代表可能是之前就没有,或者之前有,今天卖了。
//之前没有,可能是已经交易了一次,因此dp[i-1][0]不能写成0
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);
//今天持有,代表可能是之前就持有,或者之前没有,今天买了。
//之前没有,因为今天要买,所以之前没有代表没有交易,dp[i-1][0] = 0;
dp[i][1] = Math.max(dp[i-1][1], - prices[i]);
}
28 123. 买卖股票的最佳时机 III
错误原因: 初始化不完全,所有dp[0] [j] [1] 都要初始化为-prices[0]才行。
dp[0][0][1] = -prices[0];
dp[0][1][1] = -prices[0];
dp[0][2][1] = -prices[0];
29 300. 最长递增子序列
错误原因: 没思路
总结:
- 像最长递增子序列,最长递增的连续子序列,最大子序列和。属于单条序列。
设置dp数组长度为dp[len];
dp[i]表示以nums[i]为结尾。
- 像最长公共序列,编辑距离,最长回文子序列,最长回文连续子序列,最长公共连续序列。这样是有两条序列的:
- 设置dp数组长度为dp[len+1](除了回文系列是dp[len],因为回文dp[i] [j] = dp[i+1] [j-1] xx 与dp[i-1]无关)
- 连续子序列dp[i]仍然表示以nums[i]为结尾。
- 非连续子序列dp[i]表示前i个数据。
for(int i = 1; i<nums.length; i++){
for(int j = 0; j<i; j++){ //对于num[i]来说,前面nums[0]-nums[i-1]都是可以利用的对象。
if(nums[i] > nums[j]){
dp[i] = Math.max(dp[i], dp[j]+1);
}else{
dp[i] = Math.max(dp[i], 1);
}
}
}
30 647. 回文子串的个数
dp[i] [j]表示以nums[i]和nums[j]结尾的子串是否是回文。
5. 最长回文子串
可以把序列翻转,然后求两条序列的最长公共连续序列(最长公共子串)??
不一定可以哦
https://leetcode-cn.com/problems/longest-palindromic-substring/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-bao-gu/
516. 最长回文子序列
可以把序列翻转,然后求两条序列的最长公共序列。
31 将字符串里面的字符按照出现的频率降序排列
由于要比较每个字符的频率
建立一个字符串数组,分隔开各个字符串,然后按照字符串的长度进行排序。
Arrays.sort(strs, (o1,o2)->o2.length()-o1.length());
32 用rand(7)实现rand(10)
(rand(7)-1)*7 + rand(7) 会等概率生成1-49的数
33 402. 移掉 K 位数字
错误原因:没考虑到好多细节:
class Solution {
public String removeKdigits(String num, int k) {
//贪心算法,让数保持递增他就是最小的
//删除递减序列的第一个数字
if(k >= num.length()) return "0";
StringBuilder sb = new StringBuilder(num);
int count = 0;
for(int i = 0; i<sb.length()-1 && count < k; i++){
if(sb.charAt(i) > sb.charAt(i+1)){
sb.deleteCharAt(i);
if(i == 0){
i--;
}else{
i = i-2; //i应该-2看i的左边是否跟他也形成了降序!
}
count++;
}
}
//如果最后没有用完,那么说明是一个递增的数(至少不递减),只需要舍弃末尾的数就行了。
System.out.println(count);
if(count != k){
for(int i = 0; i<k-count; i++){
sb.deleteCharAt(sb.length()-1);
}
}
//最后得到的数可能以0为开头,也可能只剩下0
while(sb.charAt(0) == '0' && sb.length() > 1){
sb.deleteCharAt(0);
}
return sb.toString();
}
}
33 最大正方形
https://leetcode-cn.com/problems/maximal-square/solution/zui-da-zheng-fang-xing-by-leetcode-solution/
错误原因:没思路
dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
dp[i] 表示以i为右下角的正方形的最大边长。
class Solution {
public int maximalSquare(char[][] matrix) {
int maxSide = 0;
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return maxSide;
}
int rows = matrix.length, columns = matrix[0].length;
int[][] dp = new int[rows][columns];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
if (matrix[i][j] == '1') {
if (i == 0 || j == 0) {
dp[i][j] = 1;
} else {
dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
}
maxSide = Math.max(maxSide, dp[i][j]);
}
}
}
int maxSquare = maxSide * maxSide;
return maxSquare;
}
}
34 126. 单词接龙 II
35 97. 交错字符串
翻译成:s3的前i+j个字符能否由s1的前i个字符和s2的前j个字符组成?
class Solution {
public boolean isInterleave(String s1, String s2, String s3) {
if(s3.length() != s1.length() + s2.length()){
return false;
}
boolean[][] dp = new boolean[s1.length()+1][s2.length()+1];
dp[0][0] = true;
for(int i = 1; i<=s1.length(); i++){
dp[i][0] = dp[i-1][0] && (s3.charAt(i-1) == s1.charAt(i-1));
}
for(int i = 1; i<=s2.length(); i++){
dp[0][i] = dp[0][i-1] && (s3.charAt(i-1) == s2.charAt(i-1));
}
for(int i = 1; i<=s1.length(); i++){
for(int j = 1; j<=s2.length(); j++){
dp[i][j] = ((dp[i-1][j] && s3.charAt(i+j-1) == s1.charAt(i-1)) || (dp[i][j-1] && s3.charAt(i+j-1) == s2.charAt(j-1)));
}
}
return dp[s1.length()][s2.length()];
}
}
36.124. 二叉树中的最大路径和
对于树的题目,有统一的思考方法,那就是站在树(子树)的顶端根节点root思考。
那么对于此题,我们思考如下问题:如果当前处在root节点,左右节点应该告诉我们什么信息才能得到答案?
根据题中对路径的定义,对于此题我们来回答以上问题。当我们遍历到树中某个节点时,我希望左子节点告诉我,在左子树中,以左子节点为开始(端点)的路径和最大为多少,同理我也希望右子节点告诉我类似的信息。
如果有了以上信息,再来思考最后一个问题:有了这个信息如何得到答案?
显然,对于当前节点有四个选择:
我自己就是一条路径
只跟左子节点合并成一条路径
只跟右子节点合并成一条路径
以自己为桥梁,跟左、右子节点合并成一条路径
需要注意的是,我们在递归求解的时候,第四种情况是不能作为递归的返回值的,因为它不符合我们对递归所期望返回值的定义(因为此时该子节点并不是拥有最大路径和路径的起点(端点)),但它也是一个可能的解,所以我们用一个全局变量记录上面四种值的最大值,递归结束后,该变量就是答案。
class Solution {
int pathSum = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
dfs(root);
return pathSum;
}
// dfs 返回以该节点为端点的最大路径和
public int dfs(TreeNode node) {
if (node == null) return 0;
int left = dfs(node.left);
int right = dfs(node.right);
// 当前节点有四个选择:
// 1)独立成线,直接返回自己的值
// 2)跟左子节点合成一条路径
// 3)跟右子节点合成一条路径
int ret = Math.max(node.val, node.val + Math.max(left, right));
// 4)以自己为桥梁,跟左、右子节点合并成一条路径
pathSum = Math.max(pathSum, Math.max(ret, node.val + left + right));
return ret;
}
}
37 295. 数据流的中位数
错误原因:最大堆和最小堆的size()没有控制好。
38 剑指 Offer 39. 数组中出现次数超过一半的数字
int candidate = nums[0];
int vote = 0;
for(int i = 0; i<nums.length; i++){
if(vote <= 0){ //这个判断一定要放到最前面,不然会出错。
candidate = nums[i];
}
if(nums[i] != candidate){
vote--;
}else{
vote++;
}
}