文章目录
- 链表部分
-
- 相交链表
- 环形链表
- 回文链表
- 合并两个有序链表
- 两数相加
- 总结:两个链表操作类型题目
- [K 个一组翻转链表](#K 个一组翻转链表)
- 随机链表的复制
- 排序链表(对递归的理解)
- [合并 K 个升序链表](#合并 K 个升序链表)
- [LRU 缓存](#LRU 缓存)
- 哈希部分
- 双指针部分
- 滑动窗口部分
- 字串部分
-
- [和为 K 的子数组](#和为 K 的子数组)
- 最小覆盖子串(滑动窗口)
- 数组部分
链表部分
相交链表
题目链接:160. 相交链表
解题思路:
先将A链表遍历一遍,将节点放入到set中,然后遍历B链表,每走一步就看set中是否存在,如果存在直接返回表示第一个相交节点。如果遍历完都没有,直接返回null。
解题代码:
java
/**
* 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) {
ListNode pointerA = headA;
ListNode pointerB = headB;
Set<ListNode> path = new HashSet<>();
while(pointerA != null) {
path.add(pointerA);
pointerA = pointerA.next;
}
while(pointerB != null) {
if(path.contains(pointerB)) return pointerB;
pointerB = pointerB.next;
}
return null;
}
}
环形链表
解题链接:141. 环形链表
解题思路:
和相交链表的思路一样,使用set记录走过的节点
解题代码:
java
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode pointer = head;
Set<ListNode> set = new HashSet<>();
while(pointer != null) {
if(set.contains(pointer)) return true;
set.add(pointer);
pointer = pointer.next;
}
return false;
}
}
回文链表
题目链接:234. 回文链表
解题逻辑:
基本逻辑就是:
- 先求出链表长度
- 折半之后,将一边的元素添加到栈中,同时移动指针
- 然后再将栈中元素弹出与指针元素比对即可
解题代码:
java
class Solution {
public boolean isPalindrome(ListNode head) {
int len = 0;
ListNode pointer = head;
while(pointer != null) {
len++;
pointer = pointer.next;
}
int begin = len / 2;
pointer = head;
Deque<Integer> stack = new ArrayDeque<>();
while(begin-- > 0) {
stack.push(pointer.val);
pointer = pointer.next;
}
if(len % 2 == 1) pointer = pointer.next;
while(!stack.isEmpty()) {
if(stack.pop() != pointer.val) return false;
pointer = pointer.next;
}
return true;
}
}
合并两个有序链表
题目链接:21. 合并两个有序链表
本题比较基础分三种情况讨论即可
解题代码:
java
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode result = new ListNode();
ListNode pointer1 = list1;
ListNode pointer2 = list2;
ListNode pointer3 = result;
while(pointer1 != null && pointer2 != null) {
if(pointer1.val <= pointer2.val) {
pointer3.next = new ListNode(pointer1.val);
pointer1 = pointer1.next;
pointer3 = pointer3.next;
}else {
pointer3.next = new ListNode(pointer2.val);
pointer2 = pointer2.next;
pointer3 = pointer3.next;
}
}
while(pointer1 != null) {
pointer3.next = new ListNode(pointer1.val);
pointer1 = pointer1.next;
pointer3 = pointer3.next;
}
while(pointer2 != null) {
pointer3.next = new ListNode(pointer2.val);
pointer2 = pointer2.next;
pointer3 = pointer3.next;
}
return result.next;
}
}
两数相加
题目链接:2. 两数相加
和前面一样分三种情况讨论即可,然后可以使用一个flag表示进位符号。
java
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode pointer1 = l1;
ListNode pointer2 = l2;
ListNode result = new ListNode();
ListNode pointer3 = result;
boolean flag = false;
while(pointer1 != null && pointer2 != null) {
int num;
if(flag) num = pointer1.val + pointer2.val + 1;
else num = pointer1.val + pointer2.val;
flag = num >= 10 ? true : false;
pointer3.next = new ListNode(num % 10);
pointer1 = pointer1.next;
pointer2 = pointer2.next;
pointer3 = pointer3.next;
}
while(pointer2 != null) {
int num;
if(flag) num = pointer2.val + 1;
else num = pointer2.val;
flag = num >= 10 ? true : false;
pointer3.next = new ListNode(num % 10);
pointer2 = pointer2.next;
pointer3 = pointer3.next;
}
while(pointer1 != null) {
int num;
if(flag) num = pointer1.val + 1;
else num = pointer1.val;
flag = num >= 10 ? true : false;
pointer3.next = new ListNode(num % 10);
pointer1 = pointer1.next;
pointer3 = pointer3.next;
}
if(flag) {
pointer3.next = new ListNode(1);
}
return result.next;
}
}
总结:两个链表操作类型题目
此种题目一般会创建三个指针:
- 链表1的操作指针pointer1
- 链表2的操作指针pointer2
- 结果链表的操作指针pointer3
分三种情况讨论:
- pointer1 和 pointer2都不为null
- pointer1不为null的情况
- pointer2不为null的情况
K 个一组翻转链表
题目链接:25. K 个一组翻转链表
解题思路:
没有技巧,主要考察对代码的掌控能力,边界情况的处理
解题代码:
java
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
int len = 0;
ListNode pointer = head;
while(pointer != null) {
len++;
pointer = pointer.next;
}
ListNode result = head;
for(int i = 0;i < len;i+=k) {
if(len - i < k) break;
result = reverseList(result,i,i + k - 1);
}
return result;
}
public ListNode reverseList(ListNode head,int start,int end){
Deque<Integer> stack = new ArrayDeque<>();
ListNode pointer = head;
int cur = 0;
ListNode left = null;
ListNode right = null;
while(cur <= end) {
if(cur == start - 1) left = pointer;
if(cur == end) right = pointer.next;
if(cur >= start) stack.push(pointer.val);
cur++;
pointer = pointer.next;
}
if(start == 0 && !stack.isEmpty()) left = new ListNode(stack.pop());
ListNode result = left;
while(!stack.isEmpty()) {
result.next = new ListNode(stack.pop());
result = result.next;
}
result.next = right;
if(start == 0) return left;
else return head;
}
}
随机链表的复制
题目链接:138. 随机链表的复制
解题思路:
解题代码:
排序链表(对递归的理解)
题目链接:148. 排序链表
本题如果使用选择排序的思想,时间复杂度达到了n方,最后会超时:
java
class Solution {
public ListNode sortList(ListNode head) {
ListNode change = head;
while(change != null) {
ListNode search = change;
int min = search.val;
ListNode minNode = search;
while(search != null) {
if(search.val < min) {
minNode = search;
min = search.val;
}
search = search.next;
}
//交换元素
int temp = change.val;
change.val = min;
minNode.val = temp;
change = change.next;
}
return head;
}
}
所以需要改进为归并排序,将时间复杂度降低到nlogn!
关于归并算法的逻辑,可以参考:十大经典排序算法-归并排序算法详解
解题代码:
java
class Solution {
public ListNode sortList(ListNode head) {
int len = 0;
ListNode pointer = head;
while(pointer != null) {
pointer = pointer.next;
len++;
}
if(len <= 1) return head;
int devide = len / 2;
ListNode devideNode = head;
int count = 1;
while(count++ < devide) devideNode = devideNode.next;
ListNode right = sortList(devideNode.next);
devideNode.next = null;
ListNode left = sortList(head);
ListNode leftPointer = left;
ListNode rightPointer = right;
ListNode merge = new ListNode();
ListNode mergePointer = merge;
while(leftPointer != null && rightPointer != null) {
if(leftPointer.val <= rightPointer.val) {
mergePointer.next = new ListNode(leftPointer.val);
leftPointer = leftPointer.next;
}else {
mergePointer.next = new ListNode(rightPointer.val);
rightPointer = rightPointer.next;
}
mergePointer = mergePointer.next;
}
while(leftPointer != null) {
mergePointer.next = new ListNode(leftPointer.val);
leftPointer = leftPointer.next;
mergePointer = mergePointer.next;
}
while(rightPointer != null) {
mergePointer.next = new ListNode(rightPointer.val);
rightPointer = rightPointer.next;
mergePointer = mergePointer.next;
}
return merge.next;
}
}
对递归的深度理解:

递归以及回溯算法其重中之重就是画出树形图,能画出树形图其实题目已经做出了80%。
- 递归就是分叉向下的箭头
- 回溯就是箭头由下向上的返回
- 而递归逻辑就需要你站在那个节点中去思考
- 最后注意递归的出口
注意这四点递归相关的题目基本都能应付!
合并 K 个升序链表
题目链接:23. 合并 K 个升序链表
解题思想:
利用的和上一题一样的思想,也就是使用分治法,只不过上一题每个元素是一个数,本题每个元素是一个链表。
解题代码:
java
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if(lists.length == 0) return null;
if(lists.length == 1) return lists[0];
return merge(lists,0,lists.length - 1);
}
public ListNode merge(ListNode[] lists,int start,int end) {
if(end < start || start < 0 || end >= lists.length) return null;
if(start == end) return lists[start];
int devide = (start + end) / 2;
ListNode left = merge(lists,start,devide);
ListNode right = merge(lists,devide + 1,end);
ListNode leftPointer = left;
ListNode rightPointer = right;
ListNode result = new ListNode();
ListNode resultPointer = result;
//单层递归逻辑
while(leftPointer != null && rightPointer != null) {
if(leftPointer.val <= rightPointer.val) {
resultPointer.next = new ListNode(leftPointer.val);
leftPointer = leftPointer.next;
}else {
resultPointer.next = new ListNode(rightPointer.val);
rightPointer = rightPointer.next;
}
resultPointer = resultPointer.next;
}
while(rightPointer != null) {
resultPointer.next = new ListNode(rightPointer.val);
rightPointer = rightPointer.next;
resultPointer = resultPointer.next;
}
while(leftPointer != null) {
resultPointer.next = new ListNode(leftPointer.val);
leftPointer = leftPointer.next;
resultPointer = resultPointer.next;
}
return result.next;
}
}
LRU 缓存
题目链接:146. LRU 缓存
解题逻辑:
LinkedHashMap与LinkedSet在构造器中都可以指定访问顺序模式:
- accessOrder = false(默认):按插入顺序迭代(元素添加顺序)。
- accessOrder = true:按访问顺序迭代(最近访问的元素在末尾)
java
public LinkedHashSet(int initialCapacity, float loadFactor, boolean accessOrder)
// 参数说明:初始容量、加载因子、accessOrder(true表示访问顺序)
LinkedHashMap<K, V> map = new LinkedHashMap<>(initialCapacity, loadFactor, true);
我们只需要自己维护一个缓存长度即可,然后超出长度使用迭代器进行删除即可。
注意点:
- 游标最先开始在一个元素前面
- 先调用next才能调用remove
- Map与Set获得迭代器的方法不一样


解题代码:
sql
class LRUCache {
Map<Integer,Integer> cache = new LinkedHashMap<>(16, 0.75f, true);
int len;
public LRUCache(int capacity) {
len = capacity;
}
public int get(int key) {
return cache.get(key) == null ? -1 : cache.get(key);
}
public void put(int key, int value) {
cache.put(key,value);
if(cache.size() > len) {
Iterator<Map.Entry<Integer, Integer>> entryIterator = cache.entrySet().iterator();
entryIterator.next();
entryIterator.remove();
}
}
}
哈希部分
字母异位词分组
题目链接:49. 字母异位词分组
解题逻辑:
- 对每个字符串排序
- 如果排序之后字符串一样,那么就放到map的同一个key下
- 最后返回map的value集合
解题代码:
java
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> record = new HashMap<>();
for(String str : strs) {
char[] temp = str.toCharArray();
Arrays.sort(temp);
String key = new String(temp);
List<String> list = record.getOrDefault(key,new ArrayList<>());
list.add(str);
record.put(key,list);
}
return new ArrayList<List<String>>(record.values());
}
}
最长连续序列
题目链接:128. 最长连续序列
解题逻辑:
核心在于:找开头
- 先将元素全部添加到hash表中
- 遍历hash表
- 如果存在前一个元素说明不是开头元素,直接跳过
- 如果不存在前一个元素说明是开头元素,依次向后探测是否存在元素,获取以当前元素为头节点的最长序列长度
- 返回最大值
解题代码:
java
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> set = new HashSet<>();
for(int num : nums) set.add(num);
int max = 0;
for(int num : set) {
if(set.contains(num - 1)) continue;
else {
int count = 1;
int find = num + 1;
while(set.contains(find)) {
count++;
find++;
}
if(count > max) max = count;
}
}
return max;
}
}
双指针部分
移动零
题目链接:283. 移动零
解题逻辑:
使用双指针法,一个指针遍历数组,一个指针用来填充元素,遇到非0元素,使用填充指针进行赋值,遍历完之后如果还有空位则使用0补齐。
解题代码:
java
class Solution {
public void moveZeroes(int[] nums) {
int pointer = 0;
for(int i = 0;i < nums.length;i++) if(nums[i] != 0) nums[pointer++] = nums[i];
while(pointer < nums.length) nums[pointer++] = 0;
}
}
盛最多水的容器
题目链接:11. 盛最多水的容器
解题思路:
面积公式:S(i,j)=min(h[i],h[j])×(j−i)
核心在于双指针怎么动:
- 如果将短板指针向内移动,min(h[i],h[j])可能会变大也可能会变小
- 如果将长版指针向内移动,min(h[i],h[j])可能不变也可能会变小
- 因为移动长板指针一定不会让S变大,所以我们最终只需要将短板指针向内移动即可
解题代码:
java
class Solution {
public int maxArea(int[] height) {
int left = 0;
int right = height.length - 1;
int max = 0;
while(left < right) {
int edge = Math.min(height[left],height[right]);
int cur = edge * (right - left);
if(cur > max) max = cur;
if(height[left] <= height[right]) left += 1;
else right -= 1;
}
return max;
}
}
接雨水
题目链接:42. 接雨水
解题逻辑:

双指针解法的重点就在于指针怎么移动:
- 找到数组的最大值,分为左右两个部分
- 首先看左部分
- 左指针找到一个非0的位置
- 右指针从左指针所处位置向右依次寻找,直到该位置上的值大于左指针对应的值就停止
- 我们将双指针所知区域进行面积累加
- 右部分同理做镜像操作即可
解题代码:
java
class Solution {
public int trap(int[] height) {
int result = 0;
int left = 0;
while(left < height.length && height[left] == 0) left++;
int devide = 0;
int max = 0;
for(int i = 0;i < height.length;i++) {
if(height[i] > max) {
devide = i;
max = height[i];
}
}
while(left < height.length && left < devide) {
int right = left + 1;
while(right < height.length) {
if(height[right] >= height[left]) {
int cur = getArea(Arrays.copyOfRange(height,left,right + 1));
result += cur;
left = right;
break;
}
right += 1;
}
}
int right = height.length - 1;
while(right > 0 && height[right] == 0) right--;
while(right > devide) {
left = right - 1;
while(left >= devide) {
if(height[left] >= height[right]) {
int cur = getArea(Arrays.copyOfRange(height,left,right + 1));
result += cur;
right = left;
break;
}
left -= 1;
}
}
return result;
}
public int getArea(int[] nums){
if(nums.length < 3) return 0;
int edge = Math.min(nums[0],nums[nums.length - 1]);
int area = edge * (nums.length - 2);
for(int i = 1;i < nums.length - 1;i++) area -= nums[i];
return area;
}
}
滑动窗口部分
找到字符串中所有字母异位词
解题链接:438. 找到字符串中所有字母异位词
暴力法:
sql
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> result = new ArrayList<>();
char[] ss = s.toCharArray();
char[] pp = p.toCharArray();
Arrays.sort(pp);
for(int i = 0;i + pp.length - 1 < ss.length;i++) {
char[] cur = Arrays.copyOfRange(ss,i,i + pp.length);
Arrays.sort(cur);
for(int j = 0;j < pp.length;j++) {
if(cur[j] != pp[j]) break;
if(cur[j] == pp[j] && j == pp.length - 1) result.add(i);
}
}
return result;
}
}
使用滑动窗口:
在滑动窗口维护一个连续区间,通过数组对该区间字符进行计数,移动窗口,看计数是否与要求的子串相同。
java
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> result = new ArrayList<>();
if(p.length() > s.length()) return result;
char[] ss = s.toCharArray();
char[] pp = p.toCharArray();
int[] record1 = new int[26];
int[] record2 = new int[26];
for(int i = 0;i < pp.length;i++) {
record1[ss[i] - 'a']++;
record2[pp[i] - 'a']++;
}
if(Arrays.equals(record1,record2)) result.add(0);
for(int i = 1;i + pp.length - 1 < ss.length;i++) {
record1[ss[i - 1] - 'a']--;
record1[ss[i + pp.length - 1] - 'a']++;
if(Arrays.equals(record1,record2)) result.add(i);
}
return result;
}
}
字串部分
和为 K 的子数组
题目链接:560. 和为 K 的子数组
解题思路:
这一题不能使用滑动窗口,因为数组元素可能为负数,那么扩张窗口(移动右指针)不一定使和变大,而收缩窗口(移动左指针)也不一定会使和变小!
所以这题我们首先想到可以通过前缀和 + 暴力枚举的形式,这样的话数组的和就转变成为了前缀和的差,然后双层for循环遍历所有子数组,对符合条件的和计数,代码如下:
java
class Solution {
public int subarraySum(int[] nums, int k) {
int[] preSum = new int[nums.length];
preSum[0] = nums[0];
for(int i = 1;i < nums.length;i++) preSum[i] = preSum[i - 1] + nums[i];
int result = 0;
for(int left = 0;left < nums.length;left++) {
for(int right = left;right < nums.length;right++) {
if(left - 1 < 0 && preSum[right] == k) result++;
else if(left - 1 >= 0 && preSum[right] - preSum[left - 1] == k) result++;
}
}
return result;
}
}
该解法可能会突破时间限制,可以通过前缀和 + Hash表 继续优化。
我们可以尝试将双层for循环优化为单层for循环:假设当前位置前缀和为 preSum,如果之前出现过前缀和为 preSum - k ,那么我们就找到了一个区间和为 k 的子数组。

而考虑到前缀和为 preSum - k可能不止一个,所以我们需要使用hash表来记录前缀和和次数,代码如下:
java
class Solution {
public int subarraySum(int[] nums, int k) {
Map<Integer,Integer> map = new HashMap<>();
map.put(0,1);
int result = 0;
int preSum = 0;
for(int i = 0;i < nums.length;i++) {
preSum += nums[i];
if(map.containsKey(preSum - k)) result += map.get(preSum - k);
map.put(preSum,map.getOrDefault(preSum,0) + 1);
}
return result;
}
}
最小覆盖子串(滑动窗口)
题目链接:76. 最小覆盖子串
解题思路:
滑动窗口的典型应用:
- 使用map维护窗口中的有效元素
- for循环控制right指针使窗口扩充,如果为有效元素添加到map中
- 同时根据left指针所指的元素进行窗口收缩
- 如果left所指元素非有效元素,收缩
- 如果left所指元素为有效元素,且超过了要求的个数,收缩
解题代码:
java
class Solution {
public String minWindow(String s, String t) {
if(t.length() > s.length()) return "";
char[] ss = s.toCharArray();
Map<Character,Integer> map1 = new HashMap<>();
Map<Character,Integer> map2 = new HashMap<>();
for(char c : t.toCharArray()) {
map1.put(c,0);
map2.put(c,map2.getOrDefault(c,0) + 1);
}
int left = 0;
int right;
int min = Integer.MAX_VALUE;
String result = "";
for(right = 0;right < ss.length;right++) {
if(map1.containsKey(ss[right])) map1.put(ss[right],map1.get(ss[right]) + 1);
else continue;
while(left < right && (!map1.containsKey(ss[left]) || map1.containsKey(ss[left]) && map1.get(ss[left]) > map2.get(ss[left]))) {
if(map1.containsKey(ss[left])) map1.put(ss[left],map1.get(ss[left]) - 1);
left++;
}
if(isGreaterOrEqual(map1,map2) && right - left < min) {
min = right - left;
result = s.substring(left,right + 1);
}
}
return result;
}
public boolean isGreaterOrEqual(Map<Character, Integer> map1, Map<Character, Integer> map2) {
//比较每个键对应的value:map1的value >= map2的value
for (Map.Entry<Character, Integer> entry : map2.entrySet()) {
Character key = entry.getKey();
Integer value2 = entry.getValue();
Integer value1 = map1.get(key);
if (value1 < value2) {
return false;
}
}
return true;
}
}
数组部分
合并区间
题目链接:56. 合并区间
解题思路:
一共6种情况,其中一个情况无需变化:

我们将intervals按照左区间进行排序之后,这样所有合并都只会发生在相邻的两个区间之间,如此我们只需要一次for循环就可以解决问题。
解题代码:
java
class Solution {
public int[][] merge(int[][] intervals) {
List<List<Integer>> result = new ArrayList<>();
//intervals转换为List<List<Integer>>方便排序
List<List<Integer>> lists = Arrays.stream(intervals)
.map(list -> Arrays.stream(list).boxed().collect(Collectors.toList()))
.sorted((list1,list2) -> list1.get(0) - list2.get(0))
.collect(Collectors.toList());
for(List<Integer> list : lists) {
if(result.isEmpty()) {
result.add(list);
continue;
}
List<Integer> cur = result.get(result.size() - 1);
if(list.get(1) < cur.get(0) || list.get(0) > cur.get(1)) result.add(new ArrayList<>(list));
else if(list.get(0) < cur.get(0) && list.get(1) <= cur.get(1)) cur.set(0,list.get(0));
else if(list.get(1) > cur.get(1) && list.get(0) >= cur.get(0)) cur.set(1,list.get(1));
else if(list.get(0) < cur.get(0) && list.get(1) > cur.get(1)) {
cur.set(0,list.get(0));
cur.set(1,list.get(1));
}
}
return result.stream().map(list -> list.stream().mapToInt(Integer::intValue).toArray()).toArray(int[][]::new);
}
}
轮转数组(数组翻转)
题目链接:189. 轮转数组
解题逻辑:
将数组的元素向后轮转k个位置,其实可以转化为将数组的最后k个元素,放到数组头部去,然后其他元素后移。那么要想达到这种效果,我们可以先翻转整个数组,先让这k个元素到头部去,只是顺序不对,那么我们再对这k个元素进行一次翻转就可以了。至于翻转的操作使用双指针法即可。

解题代码:
java
class Solution {
public void rotate(int[] nums, int k) {
if(k == nums.length) return;
//先翻转整个数组
reverse(nums,0,nums.length - 1);
//再对两个部分分别进行翻转
reverse(nums,0,(k - 1) % nums.length);
reverse(nums,k % nums.length,nums.length - 1);
}
public void reverse(int[] nums,int left,int right) {
while(left < right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
right--;
}
}
}
除自身以外数组的乘积(前缀表、后缀表)
题目链接:238. 除自身以外数组的乘积
解题思路:
这一题和前面560.和为 K 的子数组这一题很相似。都涉及到了使用数组的连续状态得出解,例如:
560.和为 K 的子数组:使用到了数组连续的和来求解238. 除自身以外数组的乘积:使用到了数组连续的乘积来求解
那么我们可以通过构建前缀或者后缀表,从而减少时间复杂度。
解题代码:
java
class Solution {
public int[] productExceptSelf(int[] nums) {
int[] prefix = new int[nums.length];
int[] suffix = new int[nums.length];
prefix[0] = nums[0];
for(int i = 1;i < nums.length;i++) prefix[i] = prefix[i - 1] * nums[i];
suffix[nums.length - 1] = nums[nums.length - 1];
for(int i = nums.length - 2;i >= 0;i--) suffix[i] = suffix[i + 1] * nums[i];
int[] result = new int[nums.length];
for(int i = 0;i < nums.length;i++) {
int pre = i - 1 < 0 ? 1 : prefix[i - 1];
int suf = i + 1 >= nums.length ? 1 : suffix[i + 1];
result[i] = pre * suf;
}
return result;
}
}
缺失的第一个正数(原地Hash)
题目链接:41. 缺失的第一个正数
解题逻辑:
方法一:
直接使用stream流来做:
- 去重
- 过滤负数、0
- 排序
得到的新数组,如果:
- 长度为0或者第一个元素大于1,则直接返回1
- 否则就依次 + 1匹配后一个元素
算法分析:
- 时间复杂度:O(n log n)(受排序操作主导)
- 空间复杂度:O(n)(受去重存储和新数组主导)
解题代码:
java
class Solution {
public int firstMissingPositive(int[] nums) {
nums = Arrays.stream(nums).distinct().filter(num -> num > 0).sorted().toArray();
if(nums.length == 0 || nums[0] > 1) return 1;
else for(int i = 1;i < nums.length;i++) if(nums[i] != nums[i - 1] + 1) return nums[i - 1] + 1;
return nums[nums.length - 1] + 1;
}
}
方法二:原地Hash
首先明确我们要找的数一定在【1,n + 1】之间,n + 1是当1 ~ n在数组中都存在的时候才返回。所以我们现在的目标就变为将数组中的元素原地hash,hash函数为hash(i) = i - 1,hash完之后遍历一遍看哪个位置上的元素不符合要求。
注意交换的前提:
- 在范围内【1,n】
- 当前位置上的元素不符合hash函数
- 要交换的位置上的元素也不符合hash函数(防止无限交换)
解题代码:
java
class Solution {
public int firstMissingPositive(int[] nums) {
for(int i = 0;i < nums.length;i++) {
while(nums[i] > 0 && nums[i] <= nums.length && nums[i] != i + 1 && nums[nums[i] - 1] != nums[i]) {
int exchangeIndex = nums[i] - 1;
int temp = nums[i];
nums[i] = nums[exchangeIndex];
nums[exchangeIndex] = temp;
}
}
for(int i = 0;i < nums.length;i++) if(nums[i] != i + 1) return i + 1;
return nums.length + 1;
}
}