【算法训练营 · 补充】LeetCode Hot100(中)
文章目录
- 链表部分
- 相交链表
- 环形链表
- 回文链表
- 合并两个有序链表
- 两数相加
- 总结:两个链表操作类型题目
- K 个一组翻转链表
- 随机链表的复制
- 排序链表(对递归的理解)
- 合并 K 个升序链表
- LRU 缓存
- 哈希部分
- 字母异位词分组
- 最长连续序列
- 双指针部分
- 移动零
- 盛最多水的容器
- 接雨水
- 滑动窗口部分
- 找到字符串中所有字母异位词
- 字串部分
- 和为 K 的子数组
- 最小覆盖子串(滑动窗口)
- 数组部分
- 合并区间
- 轮转数组(数组翻转)
- 除自身以外数组的乘积(前缀表、后缀表)
- 缺失的第一个正数(原地Hash)
链表部分
相交链表
题目链接:160. 相交链表
解题思路:
先将A链表遍历一遍,将节点放入到set中,然后遍历B链表,每走一步就看set中是否存在,如果存在直接返回表示第一个相交节点。如果遍历完都没有,直接返回null。
解题代码:
/*** 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记录走过的节点
解题代码:
/*** 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. 回文链表
解题逻辑:
基本逻辑就是:
- 先求出链表长度
- 折半之后,将一边的元素添加到栈中,同时移动指针
- 然后再将栈中元素弹出与指针元素比对即可
解题代码:
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. 合并两个有序链表
本题比较基础分三种情况讨论即可
解题代码:
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表示进位符号。
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 个一组翻转链表
解题思路:
没有技巧,主要考察对代码的掌控能力,边界情况的处理
解题代码:
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方,最后会超时:
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!
关于归并算法的逻辑,可以参考:十大经典排序算法-归并排序算法详解
解题代码:
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 个升序链表
解题思想:
利用的和上一题一样的思想,也就是使用分治法,只不过上一题每个元素是一个数,本题每个元素是一个链表。
解题代码:
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:按访问顺序迭代(最近访问的元素在末尾)
public LinkedHashSet(int initialCapacity, float loadFactor, boolean accessOrder)
// 参数说明:初始容量、加载因子、accessOrder(true表示访问顺序)
LinkedHashMap<K, V> map = new LinkedHashMap<>(initialCapacity, loadFactor, true);
我们只需要自己维护一个缓存长度即可,然后超出长度使用迭代器进行删除即可。
注意点:
- 游标最先开始在一个元素前面
- 先调用next才能调用remove
- Map与Set获得迭代器的方法不一样


解题代码:
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集合
解题代码:
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表
- 如果存在前一个元素说明不是开头元素,直接跳过
- 如果不存在前一个元素说明是开头元素,依次向后探测是否存在元素,获取以当前元素为头节点的最长序列长度
- 返回最大值
解题代码:
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补齐。
解题代码:
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变大,所以我们最终只需要将短板指针向内移动即可
解题代码:
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的位置
- 右指针从左指针所处位置向右依次寻找,直到该位置上的值大于左指针对应的值就停止
- 我们将双指针所知区域进行面积累加
- 右部分同理做镜像操作即可
解题代码:
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. 找到字符串中所有字母异位词
暴力法:
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;}
}
使用滑动窗口:
在滑动窗口维护一个连续区间,通过数组对该区间字符进行计数,移动窗口,看计数是否与要求的子串相同。
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循环遍历所有子数组,对符合条件的和计数,代码如下:
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表来记录前缀和和次数,代码如下:
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所指元素为有效元素,且超过了要求的个数,收缩
解题代码:
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的valuefor (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循环就可以解决问题。
解题代码:
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个元素进行一次翻转就可以了。至于翻转的操作使用双指针法即可。

解题代码:
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. 除自身以外数组的乘积:使用到了数组连续的乘积来求解
那么我们可以通过构建前缀或者后缀表,从而减少时间复杂度。
解题代码:
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)(受去重存储和新数组主导)
解题代码:
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函数(防止无限交换)
解题代码:
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;}
}
