哈希表part02
day6-1 ● 454.四数相加II
整体思路
四个数组
相加为0
求元组数
map解题过程
一存取
四分两两
注意事项
四分一三效率低
map中的的key:a+b的值 value:值出现的次数
代码实现
class Solution {
/**
* 时间复杂度: O(n^2)
* 空间复杂度: O(n^2),最坏情况下A和B的值各不相同,相加产生的数字个数为 n^2
*/
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
int res = 0;
// key:值, value:出现的次数
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for(int i : nums1){
for(int j : nums2){
int sum = i + j;
map.put(sum, map.getOrDefault(sum, 0) + 1);
}
}
for(int k : nums3){
for (int l : nums4){
res += map.getOrDefault(0 - k -l, 0);
}
}
return res;
}
}
总结
查找元素是否存在用哈希法
day6-2 ● 383. 赎金信
代码实现
class Solution {
/**
* 时间复杂度: O(n)
* 空间复杂度: O(1)
*/
public boolean canConstruct(String ransomNote, String magazine) {
if(ransomNote.length() > magazine.length()){
return false;
}
// 定义一个hash映射数组
int[] record = new int[26];
for(int c : magazine.toCharArray()){
record[c- 'a'] += 1;
}
for(int c: ransomNote.toCharArray()){
record[c - 'a'] -= 1;
}
// 存在负数,说明ransomNote字符中存在magazine中没有的字符
for(int i : record){
if(i < 0){
return false;
}
}
return true;
}
}
day6-2 ● 384. 三数之和
整体思路
- 复杂点在去重
- hash法去重麻烦容易超时
- 双指针思路简单去重是关键
- 三元组里面的元素是可以重复的
- a+b+c 中
nums[i]==nums[i+1]
与nums[i]==nums[i-1]
对a去重的结果完全不同nums[i]==nums[i+1]
就把 三元组中出现重复元素的情况直接pass掉了, 例如{-1, -1 ,2}, 不能有重复的三元组,但三元组内的元素是可以重复的
指针放置情况
代码实现
class Solution {
/**
* 时间复杂度:O(n^2)
* 空间复杂度:O(1)
*/
public List<List<Integer>> threeSum(int[] nums) {
// 找a + b + c = 0
// 用来存a b C
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for(int i = 0; i < nums.length; i++){
// 开局就结束战斗
if(nums[i] > 0){
return result;
}
// 对a去重
if(i > 0 && nums[i - 1] == nums[i]){
continue;
}
int left = i + 1;
int right = nums.length - 1;
while(left < right){
int sum = nums[i] + nums[left] + nums[right];
if(sum > 0){
right--;
}
if(sum < 0){
left++;
}
if(sum == 0){
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 对b c去重 放在三元组之后避免00000这种情况取不到正确结果
while(left < right && nums[right] == nums[right - 1])right--;
while(left < right && nums[left] == nums[left + 1])left++;
left++;
right--;
}
}
}
return result;
}
}
去重逻辑回顾
对a去重
对b和c去重的逻辑放在收获逻辑里面至少收获一次
总结
双指针思路简单但是去重是关键
day6-3 ● 385. 四数之和
整体思路
剪枝操作
两层剪枝
代码实现
class Solution {
/**
* 时间复杂度: O(n^3)
* 空间复杂度: O(1)
*/
public List<List<Integer>> fourSum(int[] nums, int target) {
// 求 nums[k] + nums[i] + nums[left] + nums[right] == target
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for(int k = 0; k < nums.length; k++){
// 开局杀死比赛
// 限定nums[k] > 0是因为两个负数相加回更小
if(nums[k] > 0 && nums[k] > target){
return result;
}
// 对k去重
if(k > 0 && nums[k] == nums[k - 1]){
continue;
}
for(int i = k + 1; i < nums.length; i++){
if(i > k + 1 && nums[i] == nums[i - 1]){
continue;
}
int left = i + 1;
int right = nums.length - 1;
while(left < right){
long sum = (long)nums[k] + (long)nums[i] + nums[left] + nums[right];
if(sum > target){
right--;
}
if(sum < target){
left++;
}
if(sum == target){
result.add(Arrays.asList(nums[k], nums[i], nums[left], nums[right]));
while(left < right && nums[left] == nums[left+1])left++;
while(left < right && nums[right] == nums[right-1])right--;
// 找到答案,双指针同时收缩
left++;
right--;
}
}
}
}
return result;
}
}
总结
三数之后的思路上再套一层for循环
双指针
双指针优化时间复杂度
27.移除元素
class Solution {
// 双指针
// public int removeElement(int[] nums, int val) {
// int n = nums.length;
// int slow = 0;
// for(int fast = 0; fast < n; fast++){
// if(nums[fast] != val){
// nums[slow] = nums[fast];
// slow++;
// }
// }
// return slow;
// }
// 双指针优化版本
public int removeElement(int[] nums, int val) {
int left = 0;
int right = nums.length;
while(left < right){
if(nums[left] == val){
nums[left] = nums[right - 1];
right--;
}else{
left++;
}
}
return right;
}
}
15.三数之和
class Solution {
/**
* 时间复杂度:O(n^2)
* 空间复杂度:O(1)
*/
public List<List<Integer>> threeSum(int[] nums) {
// 找a + b + c = 0
// 用来存a b C
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for(int i = 0; i < nums.length; i++){
// 开局就结束战斗
if(nums[i] > 0){
return result;
}
// 对a去重
if(i > 0 && nums[i - 1] == nums[i]){
continue;
}
int left = i + 1;
int right = nums.length - 1;
while(left < right){
int sum = nums[i] + nums[left] + nums[right];
if(sum > 0){
right--;
}
if(sum < 0){
left++;
}
if(sum == 0){
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 对b c去重 放在三元组之后避免00000这种情况取不到正确结果
while(left < right && nums[right] == nums[right - 1])right--;
while(left < right && nums[left] == nums[left + 1])left++;
left++;
right--;
}
}
}
return result;
}
}
18.四数之和
```java
class Solution {
/**
* 时间复杂度: O(n^3)
* 空间复杂度: O(1)
*/
public List<List<Integer>> fourSum(int[] nums, int target) {
// 求 nums[k] + nums[i] + nums[left] + nums[right] == target
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for(int k = 0; k < nums.length; k++){
// 开局杀死比赛
// 限定nums[k] > 0是因为两个负数相加回更小
if(nums[k] > 0 && nums[k] > target){
return result;
}
// 对k去重
if(k > 0 && nums[k] == nums[k - 1]){
continue;
}
for(int i = k + 1; i < nums.length; i++){
if(i > k + 1 && nums[i] == nums[i - 1]){
continue;
}
int left = i + 1;
int right = nums.length - 1;
while(left < right){
long sum = (long)nums[k] + (long)nums[i] + nums[left] + nums[right];
if(sum > target){
right--;
}
if(sum < target){
left++;
}
if(sum == target){
result.add(Arrays.asList(nums[k], nums[i], nums[left], nums[right]));
while(left < right && nums[left] == nums[left+1])left++;
while(left < right && nums[right] == nums[right-1])right--;
// 找到答案,双指针同时收缩
left++;
right--;
}
}
}
}
return result;
}
}
链表相关的双指针
206.反转链表
/**
* 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 temp = null;
ListNode pre = null;
ListNode cur = head;
while(cur != null){
temp = cur.next;// 保存下一个节点
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
}
// 递归
class Solution {
public ListNode reverseList(ListNode head) {
return reverse(null, head);
}
private ListNode reverse(ListNode prev, ListNode cur) {
if (cur == null) {
return prev;
}
ListNode temp = null;
temp = cur.next;// 先保存下一个节点
cur.next = prev;// 反转
// 更新prev、cur位置
// prev = cur;
// cur = temp;
return reverse(cur, temp);
}
}
// 从后向前递归
class Solution {
ListNode reverseList(ListNode head) {
// 边缘条件判断
if(head == null) return null;
if (head.next == null) return head;
// 递归调用,翻转第二个节点开始往后的链表
ListNode last = reverseList(head.next);
// 翻转头节点与第二个节点的指向
head.next.next = head;
// 此时的 head 节点为尾节点,next 需要指向 NULL
head.next = null;
return last;
}
}
19.删除链表的倒数第N个节点
/**
* 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 removeNthFromEnd(ListNode head, int n) {
// 创建虚拟头节点指向head
ListNode dummyNode = new ListNode();
dummyNode.next = head;
// 快慢指针
ListNode fastIndex = dummyNode;
ListNode slowIndex = dummyNode;
// 快指针移动 n + 1个位置
// 因为只有这样同时移动的时候slow才能指向删除节点的上一个节点
for(int i = 0; i <= n; i++){
fastIndex = fastIndex.next;
}
while(fastIndex != null){
fastIndex = fastIndex.next;
slowIndex = slowIndex.next;
}
// 判空避免空指针异常
if(slowIndex!=null){
slowIndex.next = slowIndex.next.next;
}
return dummyNode.next;
}
}
面试题 02.07. 链表相加
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
/**
*时间复杂度:O(n + m)
* 空间复杂度:O(1)
*/
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
int lenA = 0, lenB = 0;
// 求长度
while(curA != null){
lenA++;
curA = curA.next;
}
while(curB != null){
lenB++;
curB = curB.next;
}
// A为较长链表
curA = headA;
curB = headB;
if(lenB > lenA){
int temLen = lenA;
lenA = lenB;
lenB = temLen;
ListNode temNode = curA;
curA = curB;
curB = temNode;
}
// 末尾对齐
int gap = lenA - lenB;
while(gap-->0){
curA = curA.next;
}
// 求交点
while(curA != null){
if(curA == curB){
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
}
141题.环形链表
/**
* 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 slowIndex = head;
ListNode fastIndex = head;
while(fastIndex != null && fastIndex.next != null){
slowIndex = slowIndex.next;
fastIndex = fastIndex.next.next;
if(fastIndex == slowIndex){
return true;
}
}
return false;
}
}
142题.环形链表||
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slowIndex = head;
ListNode fastIndex = head;
while(fastIndex != null && fastIndex.next != null){
slowIndex = slowIndex.next;
fastIndex = fastIndex.next.next;
if(fastIndex == slowIndex){// 有环
ListNode index1 = fastIndex;
ListNode index2 = head;
while(index1 != index2){
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}
day6-4 ● 总结
哈希表理论基础
哈希表是用来快速判断一个元素是否出现在集合中
哈希表经典题目
数组作为函数表
数组高效
383.赎金信
242.有效的字母异位词
set作为哈希表
题目没有限制大小,就无法使用数组作为哈希表
349.两个数组的交集
202.快乐数
map作为哈希函数
key-value结构
1.两数之和 (返回的是下标)
454.四数相加
总结
虽然map是万能的,但是也要考虑什么时候用数组,什么时候用set