哈希
两数之和
题目: 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
解析: 最简单的方式是通过嵌套循环的方式将这两个数找出来,但这样会带来一个问题,数组特别大的时候,时间复杂度较高,会造成内存占用过高,资源耗尽等问题,所以这不是一个很好的方案。
这里使用哈希表的方式来解决这个问题,解决思路是创建一个哈希表,判断哈希表中是否有 target - num 的值,有则将两个数的索引返回,没有则将这个数及其对应的索引值放入哈希表中。代码如下:
public int[] twoSum(int[] nums, int target) {
// 使用哈希表存储数组元素及其索引
HashMap<Integer, Integer> hashMap = new HashMap<>();
// 遍历数组
for (int i = 0; i < nums.length; i++) {
// 检查哈希表中是否存在 target - nums[i]
if (hashMap.containsKey(target - nums[i])){
// 如果存在,返回对应的索引
return new int[]{hashMap.get(target - nums[i]),i};
}// 将当前元素及其索引存入哈希表
hashMap.put(nums[i],i);
}
// 如果没有找到,返回空数组
return new int[0];
}
字母异位词
题目: 给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
解析: 通过创建一个哈希表,以字母位键,将不同组合的单词存储到list中,作为哈希表的值。代码如下:
public List<List<String>> groupAnagrams(String[] strs) {
HashMap<String, List<String>> map = new HashMap<>();
for (String str : strs) {
char[] array = str.toCharArray();
Arrays.sort(array);
String key = new String(array);
List<String> list = map.getOrDefault(key, new ArrayList<String>());
list.add(str);
map.put(key,list);
}
return new ArrayList<List<String>>(map.values());
}
最长连续序列
题目: 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
解析: 通过将数组中的数保存在hashset中,遍历nums数组,查看是否有比当前数字小1的数,有则说明这是一个序列的起点,目前这个序列长度为2。将当前序列长度设置为1,遍历是否有比当前数组大1的数,有则数字自增1,当前序列长度自增1。直到没有为止,然后保存最长的序列数。直到遍历完数组,将最长的序列返回。
public int longestConsecutive(int[] nums) {
Set<Integer> num_set = new HashSet<Integer>(); // 使用 HashSet 存储数组中的数字
for (int num : nums) {
num_set.add(num); // 将所有数字加入 HashSet
}
int longestStreak = 0; // 用于记录最长连续子序列的长度
for (int num : num_set) {
if (!num_set.contains(num - 1)) { // 如果当前数字是连续序列的起点(即 num - 1 不在集合中)
int currentNum = num; // 当前数字
int currentStreak = 1; // 当前连续子序列的长度初始化为 1
while (num_set.contains(currentNum + 1)) { // 检查当前数字的后继是否在集合中
currentNum += 1; // 更新当前数字
currentStreak += 1; // 更新当前连续子序列的长度
}
longestStreak = Math.max(longestStreak, currentStreak); // 更新最长连续子序列的长度
}
}
return longestStreak; // 返回最长连续子序列的长度
}
双指针
移动零
题目: 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意: 必须在不复制数组的情况下原地对数组进行操作。
解析: 定义2个指针,遍历数组,一个指针用于指向非零数字,另一个指针遍历数组,遇到0,就将两个指针指向的数字做交换。代码如下:
public void moveZeroes(int[] nums) {
int n = nums.length, left = 0, right = 0;
while (right < n) {
if (nums[right] != 0) {
swap(nums, left, right);
left++;
}
right++;
}
}
public void swap(int[] nums, int left, int right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
盛水容器
题目: 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
解析: 定义两个指针,分别从左右两头交替往中间遍历,哪个数字小下次就往中间移动,记录每次移动后形成的当前面积大小,与最大面积进行比较并保存下来。代码如下:
public int maxArea(int[] height) {
int first = 0;
int last = height.length - 1;
int maxArea = 0;
while (first < last) {
int currentArea = height[first] > height[last] ? (last - first) * height[last] : (last - first) * height[first];
maxArea = maxArea > currentArea ? maxArea : currentArea;
if (height[first] > height[last]) {
last--;
} else {
first++;
}
}
return maxArea;
}
三数之和
题目: 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。注意: 答案中不可以包含重复的三元组。
解析: 先对数组进行从小到大排序,定义三个指针,在一个指针不动的前提下,用剩余两个指针遍历数组,找到三个数相加等于零的集合。代码如下:
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
// 首先对数组进行排序
Arrays.sort(nums);
// 遍历第一个数
for (int first = 0; first < nums.length - 2; first++) {
// 第一个数大于0,后面不可能有解
if (nums[first] > 0) break;
// 跳过重复的first
if (first > 0 && nums[first] == nums[first - 1]) continue;
int second = first + 1;
int third = nums.length - 1;
while (second < third) {
int sum = nums[first] + nums[second] + nums[third];
if (sum < 0) {
second++;
} else if (sum > 0) {
third--;
} else {
result.add(Arrays.asList(nums[first], nums[second], nums[third]));
// 跳过重复的second和third元素
while (second < third && nums[second] == nums[second + 1]) second++;
while (second < third && nums[third] == nums[third - 1]) third--;
second++;
third--;
}
}
}
return result;
}
接雨水
题目: 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
解析: 代码如下:
public int trap(int[] height) {
int ans = 0;
int left = 0, right = height.length - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = Math.max(leftMax, height[left]);
rightMax = Math.max(rightMax, height[right]);
if (height[left] < height[right]) {
ans += leftMax - height[left];
++left;
} else {
ans += rightMax - height[right];
--right;
}
}
return ans;
}
链表
相交链表
题目: 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
解析: 遍历headA将其所有节点放入hashset中,通过遍历headB,查看其节点是否存在于hashset中,存在则返回该节点。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
HashSet<ListNode> listNodes = new HashSet<>();
ListNode temp = headA;
while (temp!=null){
listNodes.add(temp);
temp = temp.next;
}
temp = headB;
while (temp != null){
if (listNodes.contains(temp)){
return temp;
}
temp = temp.next;
}
return null;
}
环形链表
题目: 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
解析: 思路如相交链表。
public ListNode detectCycle(ListNode head) {
ListNode pos = head;
HashSet<ListNode> listNodes = new HashSet<>();
while (pos!=null){
if (listNodes.contains(pos)){
return pos;
}else {
listNodes.add(pos);
}
pos = pos.next;
}
return null;
}
删除链表的倒数第 N 个结点
题目: 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
解析: 这里提供两个思路:1.先循环遍历获取链表的长度,再根据链表长度和删除的节点,将当前指针指向删除节点的前一个节点(比如:长度为5,删除倒数第2个节点,则当前指针指向第三个节点),最后将当前节点的下一个节点指向下一个节点的下一个节点,返回链表,代码如下1 。2.使用快慢指针的方式,先将快指针移动 n次,再快慢指针同时遍历链表,直到快指针指向null,将慢指针的下一个节点指向下一个节点的下一个节点,代码如下2 。
// 方式1
public ListNode removeNthFromEnd(ListNode head, int n) {
//新建一个带头节点的链表
ListNode dummy = new ListNode(0,head);
//记录链表的长度
int length = 0;
// 当前指针指向待头节点的链表
ListNode cur = dummy;
while (head != null){
length++;
head = head.next;
}
for (int i = 1; i <= length -n; i++) {
cur = cur.next;
}
cur.next = cur.next.next;
ListNode ans = dummy.next;
return ans;
}
// 方式2
public ListNode removeNthFromEnd2(ListNode head, int n) {
//新建一个带头节点的链表
ListNode dummy = new ListNode(0, head);
ListNode fast = dummy;
ListNode slow = dummy;
while (n>0 && fast != null) {
fast = fast.next;
n--;
}
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
ListNode ans = dummy.next;
return ans;
}