本文旨在记录做hot100时遇到的问题及易错点
一、
1.两数之和
定义数组
int[] arr = new int[5];
int[] arr = {1, 2, 3, 4, 5};
int[] arr = new int[]{1, 2, 3, 4, 5};
前面的是类型[]在前面,后面的是new 类型[]在后面
49字母移位词
HashMap<String,List<String>>map =new HashMap();
HashMap<String, String> map = new HashMap<>();
这篇得全篇背诵好多经典句子
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
HashMap<String,List<String>>map =new HashMap();
for(int i=0;i<strs.length;i++)
{
char[] charArray = strs[i].toCharArray();
Arrays.sort(charArray);
String sortedStr = new String(charArray);
if(map.containsKey(sortedStr))
{
List list= map.get(sortedStr);
list.add(strs[i]);
map.put(sortedStr,list);
}
else
{
List<String>list =new ArrayList<>();
list.add(strs[i]);
map.put(sortedStr,list);
}
}
return new ArrayList<List<String>>(map.values());
}
}
1.list操作数是add
list.add(strs[i]);
2.map操作数是put
map.put(sortedStr,list);
3.创建对象记得要new,并且创建的是ArrayList
return new ArrayList<List<String>>(map.values());
128.最长连续序列
class Solution {
public int longestConsecutive(int[] nums) {
HashSet<Integer>set =new HashSet();
for(int num:nums)
{
set.add(num);
}
int max_len=0;
for(int num:set)
{
if(!set.contains(num-1))
{//是起始位置
int len =1;
while(set.contains(++num))
{//有连续的序列
len++;
}
max_len=Math.max(len,max_len);
}
}
return max_len;
}
}
易错点:
1.遍历的是set集合
2.set.contains(++num)
查看num的后一个元素是否存在,所以首先num要+1,并且,存在还要继续判断num+1的值
283.移动零
思路就是,遇到非零元素,往前放置,最后的全部补0就行,使用快慢指针来实现
class Solution {
public void moveZeroes(int[] nums) {
//一个定位,一个用来遍历
int fast=0;
int slow=0;
while(fast<nums.length)
{
if(nums[fast]!=0)
{
nums[slow]=nums[fast];
slow++;
fast++;
}
else
{
fast++;
}
}
for(int i=slow;i<nums.length;i++)
{
nums[i]=0;
}
}
}
11.承最多水的容器
class Solution {
public int maxArea(int[] height) {
int left =0;
int right =height.length-1;
int max_area=(right-left)*Math.min(height[left],height[right]);
int area=max_area;
while(left<right)
{
if(height[left]<height[right])
{
left++;
}
else
{
right--;
}
area =(right-left)*Math.min(height[left],height[right]);
max_area=Math.max(area,max_area);
}
return max_area;
}
}
1.易错点:
不能写成
while(left<height.length&&right>=0;left<right)
这种情况没有固定住一边,两边都在移动。必须得固定住最大边
15.三数之和
两数之和会做,三数之和就是两数之和的拓展
思路:
首先对数组进行排序,排序后固定一个数 nums[i],再使用左右指针指向 nums[i]后面的两端,数字分别为 nums[L] 和 nums[R],计算三个数的和 sum 判断是否满足为 0
(1)如果 nums[i]大于 0,则三数之和必然无法等于 0,结束循环sum
(2)sum=0,满足则添加进结果集
(3)sum>0,right–
(4)sum<0,left++;
2.重复:
(1)如果 nums[i] == nums[i−1],则说明该数字重复,会导致结果重复,所以应该跳过
(2)当 sum == 0 时,nums[L] == nums[L+1] 则会导致结果重复,应该跳过,L++
(3)当 sum == 0 时,nums[R] == nums[R−1] 则会导致结果重复,应该跳过,R−−
注意:这道题如果没有去掉重复的测试的时候也会超出内存限制
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> list = new ArrayList<>();
Arrays.sort(nums); // 排序数组
for (int i = 0; i < nums.length; i++) {
// 跳过重复的元素,避免重复三元组
if (nums[i]>0)
{
continue;
}
if (nums[i] > 0) {
break; // 如果当前值大于 0,后面的和一定大于 0,直接结束
}
int left = i + 1, right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
if(i>0&& nums[i] == nums[i - 1])
{
break;
}
List<Integer> triplet = new ArrayList<>();
triplet.add(nums[i]);
triplet.add(nums[left]);
triplet.add(nums[right]);
list.add(triplet);
// 跳过重复的元素
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
// 移动指针
left++;
right--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return list;
}
}
42.盛水最多的容器
灵神讲的非常好
每个位置都当作一个水桶来进行操作。
class Solution {
public int trap(int[] height) {
int []pre=new int [height.length];
int []after=new int [height.length];
int max_pre=0;
int max_after=0;
for(int i=0;i<height.length;i++)
{
max_pre=Math.max(max_pre,height[i]);
pre[i]=max_pre;
}
for(int i=height.length-1;i>=0;i--)
{
max_after=Math.max(max_after,height[i]);
after[i]=max_after;
}
int sum =0;
for(int i=0;i<height.length;i++)
{
int v=Math.min(after[i],pre[i])-height[i];
if(v>0)
{
sum+=v;
}
}
return sum;
}
}
易错点:
1.int v=Math.min(after[i],pre[i]),不是after[i]-pre[i],水桶能装的水取决于最短的边
class Solution {
public int trap(int[] height) {
int max_pre = 0; // 左边的最大高度
int max_after = 0; // 右边的最大高度
int sum = 0; // 存储雨水总量
int left = 0; // 左指针
int right = height.length - 1; // 右指针
while (left < right) {
// 更新左侧最大值max_pre 和 右侧最大值max_after
max_pre = Math.max(max_pre, height[left]);
max_after = Math.max(max_after, height[right]);
// 如果左边的最大值大于右边的最大值,相当于我们把右边的作为容器,右边的已经计算完了,自然要左移
if (max_pre > max_after) {
// 计算当前右侧水量
int v = max_after - height[right];
if (v > 0) {
sum += v; // 累加水量
}
right--; // 右指针左移
} else {
// 计算当前左侧水量
int v = max_pre - height[left];
if (v > 0) {
sum += v; // 累加水量
}
left++; // 左指针右移
}
}
return sum; // 返回总水量
}
}
1.取决于水桶最短的边
2. 如果左边的最大值大于右边的最大值,相当于我们把右边的作为容器,右边的已经计算完了,自然要左移(这个左右移动的逻辑千万不能错误)
3.无重复字符的最长字串
思路:
求以nums[i]为最后一个元素的滑动窗口,每个元素都这样求。所有滑动窗口的最大值就是最长的字串
class Solution {
public int lengthOfLongestSubstring(String s) {
HashSet<Character> set = new HashSet<>();
int left = 0;
int right = 0;
int s_len = s.length();
int length = 0;
int max_length = 0;
while (right < s_len) {
char a = s.charAt(right);
if (!set.contains(a)) {
set.add(a);
length++;
right++;
} else {
// 内层循环,移除左边字符直到不包含重复字符
char b = s.charAt(left);
set.remove(b);
left++;
length--;
}
max_length = Math.max(length, max_length);
}
return max_length;
}
}
二、
438.找到字符串中所有字母异位词
给定两个字符串 s 和 p,找到 s 中所有 p 的
异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
思路分析:统计p中每个字符出现的次数,最多26个字符。
如果s的子窗口中字符出现的次数和p中一样,那就是一个字母移位词
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> list =new ArrayList<>();
int s_len =s.length();
int p_len =p.length();
int[] p_count =new int[26];
int[] windows =new int[26];
if (s_len < p_len) {
return list;
}
for(int i =0;i<p_len;i++)
{
p_count[p.charAt(i)-'a']++;
}
int left =0;int right= 0;
while (right < s_len) {
// 更新右边字符
windows[s.charAt(right) - 'a']++;
// 窗口大小达到 p_len 时
if (right - left + 1 == p_len) {
if (matches(windows, p_count)) {
list.add(left);
}
// 移除左边字符,窗口滑动
windows[s.charAt(left) - 'a']--;
left++;
right++;
}
else if(right - left + 1 < p_len)
{
right++;
}
}
return list;
}
public boolean matches(int []a,int[]b)
{
for(int i=0;i<26;i++)
{
if(a[i]!=b[i])
{
return false;
}
}
return true;
}
}
别看感觉简单,但是实际很多小细节,需要自己仔细做
1.right - left + 1 == p_len时,就需要缩减窗口了,不可能出现right - left + 1 > p_len的情况
长时间的解法:
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> list =new ArrayList<>();
int s_len =s.length();
int p_len =p.length();
int[] p_count =new int[26];
int[] windows =new int[26];
if (s_len < p_len) {
return list;
}
for(int i =0;i<p_len;i++)
{
p_count[p.charAt(i)-'a']++;
}
int left =0;int right= 0;
while (right < s_len) {
// 更新右边字符
windows[s.charAt(right) - 'a']++;
// 窗口大小达到 p_len 时
if (right - left + 1 == p_len) {
if (matches(windows, p_count)) {
list.add(left);
}
// 移除左边字符,窗口滑动
windows[s.charAt(left) - 'a']--;
left++;
}
// 移动右边界
right++;
}
return list;
}
public boolean matches(int []a,int[]b)
{
for(int i=0;i<26;i++)
{
if(a[i]!=b[i])
{
return false;
}
}
return true;
}
每次都要更新右边的字符,只有当等号成立的时候才需要考虑其他需要修改的情况,这个思维模式得学会
560.和为k的子数组
class Solution {
public int subarraySum(int[] nums, int k) {
int count =0;
for(int i =0;i<nums.length;i++)//i代表子数组的末尾
{
int sum =0;
for(int j=i;j>=0;j--)//j代表子数组的开头
{
sum =sum+nums[j];
if(sum==k)
{
count++;
}
}
}
return count;
}
}
思想还是滑动窗口的思想。
239.滑动窗口最大值
import java.util.Deque;
import java.util.LinkedList;
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0) return new int[0];
Deque<Integer> deque = new LinkedList<>();
int[] output = new int[nums.length - k + 1];
int outputIndex = 0;
int left = 0, right = 0;
while (right < nums.length) {
// 保持 deque 递减
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[right]) {
deque.removeLast();
}
// 加入当前元素的索引
deque.addLast(right);
// 窗口大小达到 k 后,记录最大值
if (right - left + 1 == k) {
output[outputIndex++] = nums[deque.peekFirst()]; // 队首是最大值
// 如果队首元素已不在窗口范围内,移除队首
if (deque.peekFirst() == left) {
deque.removeFirst();
}
left++; // 滑动窗口左边界
}
right++; // 滑动窗口右边界
}
return output;
}
}
易错点,这里队列里存储的是 数值的索引,而不是具体的值,原因是这样可以更好的根据left判断出来是否超出滑动窗口的范围。
如果deque存储的是具体的值
deque.peekFirst() == nums[left]
只是判断了这俩数值是否相等,不能说明已经超出了滑动窗口的界限,所以这个逻辑是不正确的。
53.最大子数组和
以第i个元素结尾的最大子数组和dp[i]
dp[i]=max((dp[i-1]+nums[i]),dp[i])理解为:
要么加上这个元素,要么从头开始作为字串。
class Solution {
public int maxSubArray(int[] nums) {
int []dp=new int[nums.length];
dp[0]=nums[0];
int max=dp[0];
for(int i=1;i<nums.length;i++)
{
dp[i]=Math.max((dp[i-1]+nums[i]),nums[i]);
max=Math.max(max,dp[i]);
}
return max;
}
}
三、
76.最小覆盖字串(未)
题目:给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
示例 1:
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
解释:最小覆盖子串 “BANC” 包含来自字符串 t 的 ‘A’、‘B’ 和 ‘C’。
思路:先找全,再缩小左边界
56.合并区间
1.首先要对区间进行排序,可以按照左边界排序,或者右边界排序
2.两个区间是否重合(重合的条件是:下一个区间的左边界<上一个区间的右边界)
需要注意的点:
1.Arrays.sort(intervals,(a.b)->a[0]-b[0]);//这是升序排序
2.list.get(list.size()-1)//时间复杂度是O(1)
list.get()时间复杂度是O(1)
list.size()时间复杂度也是O(1)
3. List<int[]>list =new ArrayList<>();//链表中每个元素都是int[]整型数组
class Solution {
public int[][] merge(int[][] intervals) {
List<int[]>list =new ArrayList<>();
Arrays.sort(intervals,(a,b)->a[0]-b[0]);
list.add(new int[]{intervals[0][0], intervals[0][1]});
for(int i=1;i<intervals.length;i++)
{
if(intervals[i][0]<=list.get(list.size()-1)[1])
{
list.get(list.size()-1)[1]=Math.max(intervals[i][1],list.get(list.size()-1)[1]);
}
else
{
list.add(new int[]{intervals[i][0], intervals[i][1]});
}
}
return list.toArray(new int[list.size()][]);
}
}
189.轮转数组
第二次写竟然思路要比用官方的方法写思路简单!!!
//k代表移动的次数,n代表数组的长度
//思路是:移动后k次后,实际相当于移动为k%n。
//前k%n的元素变为后n-k元素,后n-k变为前k%n的元素
//原来的nums[0],变为answer[k%n]
class Solution {
public void rotate(int[] nums, int k) {
int []answer =new int[nums.length];
//原来nums第一个元素的索引从k开始
int nums_index=0;
for(int i=k%nums.length;i<answer.length;i++)
{
answer[i]=nums[nums_index++];
}
//nums_index继续用着就行
for(int i=0;i<k%nums.length;i++)
{
answer[i]=nums[nums_index++];
}
for(int i=0;i<nums.length;i++)
{
nums[i]=answer[i];
}
}
}
238.除自身以外数组的乘积
思路:用前后缀来解题
class Solution {
public int[] productExceptSelf(int[] nums) {
//算出一个数的前缀和后缀的乘积
int []pre=new int[nums.length];
int []after =new int[nums.length];
pre[0]=1;after[nums.length-1]=1;
for(int i=1;i<nums.length;i++)
{
pre[i]=pre[i-1]*nums[i-1];
}
for(int i=nums.length-2;i>=0;i--)
{
after[i]=after[i+1]*nums[i+1];
}
for(int i=0;i<nums.length;i++)
{
nums[i]=pre[i]*after[i];
}
return nums;
}
}
after[i]=after[i+1]*nums[i+1];
pre[i]=pre[i-1]*nums[i-1];
//i要用after和pre的下角标来,不然容易出错。
41.缺失的第一个正数
1.需要交换和不需要交换的元素进行分类
class Solution {
public int firstMissingPositive(int[] nums) {
int index = 0;
while (index < nums.length) {
// 1.需要交换的元素 必须>0 2.需要交换的元素必须<=数组长度 3.需要交换的两个元素不相等
if (nums[index] > 0 && nums[index] <= nums.length && nums[nums[index] - 1] != nums[index]) {
// 交换 nums[index] 和 nums[nums[index] - 1]
int tmp = nums[nums[index] - 1];
nums[nums[index] - 1] = nums[index];
nums[index] = tmp;
} else {
// 如果当前元素不需要交换,直接移动到下一个
index++;
}
}
// 查找第一个不符合条件的位置
for (int i = 0; i < nums.length; i++) {
if (nums[i] != i + 1) {
return i + 1;
}
}
// 如果所有位置都符合条件,那么缺失的最小正整数就是 nums.length + 1
return nums.length + 1;
}
}
73.矩阵置零
1.标记出来哪里有0就OK啦
class Solution {
public void setZeroes(int[][] matrix) {
int []row=new int[matrix.length];//m
int []col =new int [matrix[0].length];//n
for(int i=0;i<row.length;i++)
{
for(int j=0;j<col.length;j++)
{
if(matrix[i][j]==0)
{
row[i]=1;
col[j]=1;
}
}
}
for(int i=0;i<row.length;i++)
{
if(row[i]==1)
{
for(int j=0;j<col.length;j++)
{
matrix[i][j]=0;
}
}
}
for(int j=0;j<col.length;j++ )
{
if(col[j]==1)
{
for(int i=0;i<row.length;i++)
{
matrix[i][j]=0;
}
}
}
}
}
54.螺旋矩阵
1.每次处理的规则必须一致,是[)或者[]必须一致。(代码随想录的语言)
2.判断是否还要从右往左,以及判断是否从下往上,需要判断先决条件
(因为写在一个for循环里了。必须要多判断一次)
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer>list =new ArrayList<Integer>();
int left=0;
int right =matrix[0].length-1;
int top=0;
int bottom =matrix.length-1;
while(left<=right&&top<=bottom)
{
//从左到右
for(int i=left;i<=right;i++ )
{
list.add(matrix[top][i]);
}
top++;
//从上到下
for(int i=top;i<=bottom;i++)
{
list.add(matrix[i][right]);
}
right--;
//从右到左
if(top<=bottom)
{
for(int i=right;i>=left;i--)
{
list.add(matrix[bottom][i]);
}
bottom--;
}
//从下到上
if(left<=right)
{
for(int i =bottom;i>=top;i--)
{
list.add(matrix[i][left]);
}
left++;
}
}
return list;
}
}
四、
48.旋转图像
解题思路:观察图片发现 1,2,3 和123,很像是沿着副对角线翻转得来的,但是是逆序,所以还需要列和列之间进行交换。
易错点:沿着副对角线翻转,只需要翻转上半个三角,不能翻转下半三角
class Solution {
public void rotate(int[][] matrix) {
//首先将最后面的列放到第一列,列之间两两交换
//在沿着副对角线交换元素
int left=0;int right=matrix[0].length-1;
while(left<right)
{
for(int i=0;i<matrix.length;i++)
{
swap_left_right(matrix,i,left,right);
}
left++;
right--;
}
for(int i=0;i<matrix.length;i++)//行
{
for(int j=0;j<matrix.length-i;j++)//列
{
swap_i_j(matrix,i,j);
}
}
}
public void swap_left_right(int[][]matrix,int i,int left,int right)
{
int tmp=matrix[i][right];
matrix[i][right]=matrix[i][left];
matrix[i][left]=tmp;
}
public void swap_i_j(int[][]matrix,int i,int j)
{
int tmp=matrix[i][j];
matrix[i][j]=matrix[matrix.length-1-j][matrix.length-1-i];
matrix[matrix.length-1-j][matrix.length-1-i]=tmp;
}
}
240.搜索二维矩阵II
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int bottom=matrix.length-1;//0
int right=matrix[0].length-1;//0
int row_index=0;
int col_index=0;
while(row_index<=bottom&&col_index<=right)
{
while(right>=0&&target<=matrix[row_index][right])
{
if(target==matrix[row_index][right])return true;
right--;
}
row_index++;
//target>matrix[row_index][right] 5>matrix[0][1]
if(row_index<=bottom&&col_index<=right)
{
while(row_index<=bottom&&target>=matrix[row_index][right])
{
if(target==matrix[row_index][right])return true;
row_index++;
}
right--;
//target<matrix[row_index][right]
}
}
return false;
}
}
1.虽然这次也很慢,但是这次起码答题思路正确
2.while(right>=0&&target<=matrix[row_index][right])
{
if(target==matrix[row_index][right])return true;
right--;
}
//这里right>=0必须得有,因为right很可能<0了,但是matrix[row_index][right]还得计算
五、
160.相交链表
1.易错点:while循环写的时候容易不更新条件,导致索引越界
2.从第一个元素开始比对,所以不是p.next
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA==null||headB==null)return null;
int count_A=0;int count_B=0;
ListNode p= headA;ListNode q= headB;
while(p!=null)
{
count_A++;
p=p.next;
}
while(q!=null)
{
count_B++;
q=q.next;
}
if( count_A>count_B)//A比较长点
{
int count=(count_A-count_B);
while(count>0)
{
headA=headA.next;
count--;
}
}
else
{
int count=(count_B-count_A);
while(count>0)
{
headB=headB.next;
count--;
}
}
p=headA;q=headB;
while(p!=null&&q!=null&&p!=q)
{
p=p.next;
q=q.next;
}
return p;
}
}
206. 反转链表
//思路:使用头插法即可解决
//但是有一个问题,p=head,
//移动p的时候,如果改变了p.next,那么head也会变,下面的并没有改变原来节点间的关系
while(p!=null)
{
count_A++;
p=p.next;
}
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
//设置一个头节点
ListNode pre =new ListNode(0);
pre.next=null;
while(head!=null)
{
ListNode p=head;
head=head.next;//head 先保存好下一个节点,这样才能继续遍历
p.next=pre.next;
pre.next=p;
}
return pre.next;
}
}