目录
一、哈希
LeetCode1:两数之和
map存储没出现过的数,同时查找目标 target - nums[i]
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0;i<nums.length;i++){
int num = target - nums[i];
if(map.containsKey(num)){
res[0] = i;
res[1] = map.get(num);
break;
}else{
map.put(nums[i],i);
}
}
return res;
}
}
LeetCode49:字母异位词分组
既然要分组,map存储有序string(key)和stringList,最终将map的value都添加到res
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
List<List<String>> res = new ArrayList<>();
Map<String,List<String>> map = new HashMap<>();
for(String str:strs){
char[] chars = str.toCharArray();
Arrays.sort(chars);
String key = new String(chars);
if(map.containsKey(key)){
map.get(key).add(str);
}else{
List<String> stringList = new ArrayList<>();
stringList.add(str);
map.put(key,stringList);
}
}
for(String str:map.keySet()){
List<String> value = map.get(str);
res.add(value);
}
return res;
}
}
LeetCode128:最长连续序列
先使用集合存下所有元素(连续不重复),再类似滑动窗口方法,先定下左界x(集合包含x但不包含x+1),再定下右界y(从x开始++)
对于最长记得设置一个maxd
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> hash = new HashSet<Integer>();
for(int x : nums) hash.add(x); //放入hash表中
int res = 0;
for(int x : hash)
{
if(hash.contains(x) && !hash.contains(x-1))
{
int y = x; //以当前数x向后枚举
while(hash.contains(y + 1)) y++;
res = Math.max(res, y - x + 1); //更新答案
}
}
return res;
}
}
二、双指针
LeetCode283:移动零
这题一看就类似于移除元素,要用到快慢指针法,fast寻找新数组的元素,slow更新新数组的元素下标
相当于移除0,剩下的部分再置0
class Solution {
public void moveZeroes(int[] nums) {
int slow = 0;
for(int fast=0;fast<nums.length;fast++){
if(nums[fast]!=0){
nums[slow++] = nums[fast];
}
}
for(int i = slow;i<nums.length;i++){
nums[i] = 0;
}
}
}
LeetCode11:盛水最多的容器
决定容器容量的两个因素:一是两边较短的一个高度,二是两边之间的宽度,并且由于提到了最多,又得用到maxd
双指针法,定义start/end,他们对应的高度是height[start]和height[end]
class Solution {
//双指针 每次循环扔掉较小的
public static int maxArea(int[] height) {
int start = 0;
int end = height.length - 1;
int max = 0;
while (start < end) {
if (height[start] < height[end]) {
max = (max > height[start] * (end - start)) ? max : height[start] * (end - start);
start++;
} else {
max = (max > height[end] * (end - start)) ? max : height[end] * (end - start);
end--;
}
}
return max;
}
}
LeetCode15:三数之和
这一类型的题目概括在代码随想录哈希表第七天
区分于两数之和(查找元素序号-value):遍历元素a放入map,查找sum-a
区分四数相加(求满足四元组个数-value):遍历a+b放入map中,查找sum-(c+d)
三数之和、四数之和(要求返回所有的元组)都要求有剪枝、去重的操作
双指针法逻辑:首先排序-剪枝(减掉已经>0的值)-去掉重复的值-“int sum = nums[i] + nums[left] + nums[right];”-“while(left<right)-判断sum,移动left、right指针”
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
for(int i= 0; i<nums.length;i++){
if(nums[i]>0){ //剪枝
break;
}
if(i>0 && nums[i] == nums[i-1]){ //对nums[i]去重
continue;
}
int left = i+1;
int right = nums.length - 1;
int sum = nums[i] + nums[left] + nums[right]; //
while(left<right){
if(sum<0) left++;
else if(sum>0) right--;
else{
res.add(Arrays.asList(nums[i],nums[left],nums[right]));
//必须加入right>left的判断条件以免移动的过头
while(right > left && nums[left]==nums[left+1]) left++;
while(right > left && nums[right]==nums[right-1]) right--;
// 双指针本身还是要移动的
left++;
right--;
}
}
}
return res;
}
}
LeetCode42:接雨水
思路:遍历每一列,需要左右边最高列其中的较矮列>当前列才能储存水,且存水量等于二者之差
为此要先求出左右边最高列的高度
class Solution {
public int trap(int[] height) {
int left = 0,right = height.length-1;
int ret = 0;
int left_max = 0,right_max = 0;
while (left < right){
if (height[left] < height[right]){
if (height[left]>left_max){
left_max = height[left];
}else{
ret += left_max - height[left];
}
left++;
}else{
if(height[right] >= right_max){
right_max = height[right];
}else{
ret += right_max - height[right];
}
right--;
}
}
return ret;
}
}
三、滑动窗口
LeetCode3:无重复字符的最长子串
思路:记得存储的不是char是character;并且不管map是否包含key=c的元素,需要以新序号end(value)去覆盖它。
class Solution {
public int lengthOfLongestSubstring(String s) {
int len = s.length();
int maxLen = 0;
int start = 0;
Map<Character,Integer> map = new HashMap<>();
for (int end=0; end<len; end++){
char c = s.charAt(end);
if(map.containsKey(c)){
// +1表示从该位置之后开始寻找不重复子串
start = Math.max(start, map.get(c)+1);
}
maxLen = Math.max(maxLen, end-start+1);
map.put(c, end);
}
return maxLen;
}
}
LeetCode438:找到字符串中的所有字母异位词
思路:首先定义返回数组,然后用滑动窗口法解,定义两个哈希数组[26]用于存放sCount和pcount,首先初始化,如果两个数组相等则添加起始索引为0,然后开始移动滑动窗口
即先初始化ans,在开始滑动窗口,每滑动一次,有一个元素进,一个元素出
判断是否是字母异位词即通过类似于字母异位词、383.赎金信【大小为26的哈希数组的方法】
sCount[s.charAt(i)-'a']--;
sCount[s.charAt(i+pLen)-'a']++;
class Solution {
public List<Integer> findAnagrams(String s, String p) {
int sLen = s.length(),pLen = p.length();
int []sCount = new int[26];
int []pCount = new int[26];
List<Integer> ans = new ArrayList<>();
if(sLen<pLen){
return ans;
}
for(int i=0;i<pLen;i++){
sCount[s.charAt(i)-'a']++;
pCount[p.charAt(i)-'a']++;
}
if(Arrays.equals(sCount, pCount)){
ans.add(0);
}
for(int i=0;i<sLen-pLen;i++){
sCount[s.charAt(i)-'a']--;
sCount[s.charAt(i+pLen)-'a']++;
if(Arrays.equals(sCount, pCount)){
ans.add(i+1);
}
}
return ans;
}
}
四、子串
LeetCode560:和为K的子数组
思路:这个问题不同于几数之和,对子数组的元素个数没有限制,可以采用前缀和+哈希map的方法来解决,哈希map以前缀和pre为键,以pre出现次数为值,如果map中包含pre-k的子数组,则说明存在其他子数组使得和为k。
注意:子数组指的是连续非空的序列
public int subarraySum3(int[] nums, int k) {
int count = 0;// 记录合适的连续字符串数量
int pre = 0;// 记录前面数字相加之和
// map记录:以前缀和pre为键,出现次数为对应的值,记录pre出现的次数,
HashMap<Integer, Integer> map = new HashMap<>();
// 初始化
map.put(0, 1);
// 从左往右,边更新map,边计算答案
for (int i = 0; i < nums.length; i++) {
pre += nums[i];
// 如果前面数字之和加上这个数字正好等于K(存在一个数字加上nums[i]结果为K
// 说明找到了
if (map.containsKey(pre - k)) {
// 累计
count += map.get(pre - k);
}
// 计算新的和放入map
map.put(pre, map.getOrDefault(pre, 0) + 1);
}
return count;
}
LeetCode239:滑动窗口的最大值
思路:实际上用到了单调队列。维持一个单调递减队列,来获得滑动窗口的最大值。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
//这是一个存储下标的递减数组
ArrayDeque<Integer> deque = new ArrayDeque<>();
int n = nums.length;
int[] res = new int[n-k+1];
int idx = 0;
for(int i = 0;i < n;i++){
//区间在i-k+1到i,该情况通常出现在最大值(队列头结点)在i递增后不在区间内
//i<k-1时右侧为负数,显然也不可能成立
while(!deque.isEmpty() && deque.peek()<i-k+1){
deque.poll();
}
//比较新加入的元素,更新队列
while(!deque.isEmpty() && nums[deque.peekLast()]<nums[i]){
deque.pollLast();
}
deque.offer(i);
//当i=k-1时第一个窗口走完了,接下来i每增加一次就是一次窗口的滑动
if(i>=k-1){
res[idx++] = nums[deque.peek()]; //分清下标和值
}
}
return res;
}
}
LeetCode76:最小覆盖子串
思路:两个哈希map,用于放s,t的元素对应的个数;用滑动窗口法,每次滑动窗口,更新一下minLen。覆盖子串用cnt来判断,每次当窗口内的子串对应值没满足t的标准,right++;当left对于的元素不在t标准或者多于t标准,left++,将它移出滑动窗口。
易错:①getOrDefault(t.charAt(i),0)+1;②sMap.get(s.charAt(i))<=tMap.get(s.charAt(i));③ ans = s.substring(j,i+1);
class Solution {
public String minWindow(String s, String t) {
Map<Character,Integer> hs = new HashMap<Character,Integer>();
Map<Character,Integer> ht = new HashMap<Character,Integer>();
String ans = "";
int left = 0, right = 0;
int valid = 0;
int start = 0;
int minLen = Integer.MAX_VALUE;
if (s == null || s.isEmpty() || t == null || t.isEmpty() || s.length() < t.length()) return "";
for(int i=0;i<t.length();i++){
ht.put(t.charAt(i), ht.getOrDefault(t.charAt(i),0)+1);
}
while(right < s.length()){
char r = s.charAt(right);
right++;
if(ht.containsKey(r)){
hs.put(r, hs.getOrDefault(r,0)+1);
if(hs.get(r) == ht.get(r)) valid++;
}
while(valid == ht.size()){ //当所有字母满足个数条件时,收缩窗口左界j
// 更新最小窗口的起始位置和长度([left-2,right-1])
if (right-left< minLen) {
start = left;
minLen = right-left;
}
char l = s.charAt(left);
left++;
//当移动窗口到不符合valid条件时,跳出循环,不会更新minLen
if (ht.containsKey(l)){
hs.put(l, hs.getOrDefault(l,0)-1);
if(hs.get(l) < ht.get(l)) valid--;
}
}
}
return minLen != Integer.MAX_VALUE?s.substring(start, start+minLen):"";
}
}
五、普通数组
LeetCode53:最大子数组和
思路:动态规划法:sum = Math.max(sum + num, num);即最大值可能是当前元素num,也可能是当前元素与之相邻的最大子序和sum(取决于sum大于0还是小于0),不断维护当前元素之前的最大子序和sum。
class Solution {
public int maxSubArray(int[] nums) {
int max = nums[0];
int sum = 0;
for (int num : nums) {
sum = Math.max(sum + num, num); //维护当前元素之前的最大子序和
max = Math.max(max, sum); //获取最大值
}
return max;
}
}
class Solution {
public int maxSubArray(int[] nums) {
if(nums.length )
int[] dp = new int[nums.length]; //dp表示包括序号i的前子数组的最大子数组和
dp[0] = nums[0];
int maxd = Integer.MIN_VALUE;
for(int i=1;i<nums.length;i++){
dp[i] = Math.max(dp[i-1] + nums[i], nums[i]);
maxd = Math.max(maxd, dp[i]);
}
return maxd;
}
}
LeetCode56:合并区间
思路:先排序,再判断是否重叠,重叠则更新右边界。
区分数组排序和优先队列排序(升序)
Arrays.sort(intervals,(a,b)->Integer.compare(a[0],b[0]));
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
list——数组的方法:
return ans.toArray(new int[ans.size()][]);
//integer->int
return ans.stream().mapToInt.(Integer::intvalue).toArray()
//指定类型
return ans.stream().toArray(value -> new int[value])
代码:
class Solution {
public int[][] merge(int[][] intervals) {
//按起始位置进行升序排列
Arrays.sort(intervals,(a,b)->Integer.compare(a[0],b[0]));
ArrayList<int[]> ans = new ArrayList<>();
ans.add(new int[]{intervals[0][0],intervals[0][1]});
for (int i = 1; i < intervals.length;i++){
int left = intervals[i][0];
int right = intervals[i][1];
//线段加入的条件,当前线段和前面的线段没有交集
if (ans.get(ans.size() - 1)[1] < left){
ans.add(new int[]{left,right});
}else{
//有交集的情况,我们只需要更新右边界就行了
ans.get(ans.size() - 1)[1] = Math.max(ans.get(ans.size() - 1)[1],right);
}
}
return ans.toArray(new int[ans.size()][]);
}
}
class Solution {
public int[][] merge(int[][] intervals) {
List<int[]> res = new LinkedList<>();
Arrays.sort(intervals, (x,y)->Integer.compare(x[0],y[0]));
int left = intervals[0][0];
int right = intervals[0][1];
for(int i = 1; i<intervals.length; i++){
if(right >= intervals[i][0]){ //重叠,合并
right = Math.max(right, intervals[i][1]);
}else{ //不重叠,添加旧区间,更新left、right
res.add(new int[]{left, right});
left = intervals[i][0];
right = intervals[i][1];
}
}
res.add(new int[]{left, right});
return res.toArray(new int[res.size()][2]);
}
}
LeetCode189:轮转数组
思路:其实就是反转数组,先整体翻转,再对前k个和前n-k个局部翻转
class Solution {
public void rotate(int[] nums, int k) {
//记得对n取模
int len=nums.length;
int pos=k%(len);
reverse(nums,0,len-1);
reverse(nums,0,pos-1);
reverse(nums,pos,len-1);
}
public void reverse(int[] nums,int start,int end){
while(start<end){
int tmp = nums[end];
nums[end] = nums[start];
nums[start] = tmp;
start++;
end--;
}
}
}
LeetCode238:除自身之外数组的乘积
思路:
- 开辟两个和原数组长度相等的数组,分别用于记录原数组当前位置左侧元素的乘积和右侧元素的乘积
- 然后在再开辟一个等长大小answer数组,将左侧元素乘积数组和右侧乘积数组相同下标的元素相乘,然后将结果赋值给answer数组中对应下标的位置
- 最后返回answer数组
class Solution {
public int[] productExceptSelf(int[] nums) {
int length = nums.length;
// L 和 R 分别表示左右两侧的乘积列表
int[] L = new int[length];
int[] R = new int[length];
int[] answer = new int[length];
// L[i] 为索引 i 左侧所有元素的乘积
// 对于索引为 '0' 的元素,因为左侧没有元素,所以 L[0] = 1
L[0] = 1;
for (int i = 1; i < length; i++) {
L[i] = nums[i - 1] * L[i - 1];
}
// R[i] 为索引 i 右侧所有元素的乘积
// 对于索引为 'length-1' 的元素,因为右侧没有元素,所以 R[length-1] = 1
R[length - 1] = 1;
for (int i = length - 2; i >= 0; i--) {
R[i] = nums[i + 1] * R[i + 1];
}
// 对于索引 i,除 nums[i] 之外其余各元素的乘积就是左侧所有元素的乘积乘以右侧所有元素的乘积
for (int i = 0; i < length; i++) {
answer[i] = L[i] * R[i];
}
return answer;
}
}
LeetCode41:缺失的第一个正数
思路:
class Solution {
public int firstMissingPositive(int[] nums) {
Arrays.sort(nums);
int min = 1;
for (int i = 0; i < nums.length; i++){
if(nums[i] > min){
return min;
}
min = Math.max(nums[i] + 1, min);
}
return min;
}
}