给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
wp:
class Solution { public int[] twoSum(int[] nums, int target) { Mapmap = new HashMap<>(); for(int i = 0; i< nums.length; i++) { if(map.containsKey(target - nums[i])) { return new int[] {map.get(target-nums[i]),i}; } map.put(nums[i], i); } throw new IllegalArgumentException("No two sum solution"); } }
这里主要是用了map映射,
遍历数组 nums,i 为当前下标,每个值都判断map中是否存在 target-nums[i] 的 key 值 如果存在则找到了两个值,如果不存在则将当前的 (nums[i],i) 存入 map 中,继续遍历直到找到为止
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
wp:
字母相同,但排列不同的字符串,排序后都一定是相同的。因为每种字母的个数都是相同的,那么排序后的字符串就一定是相同的。
所以我们直接进行排序
使用stream流来做
class Solution2 { public List> getAnagrams(String[] strs) { return new ArrayList<>(Arrays.stream(strs).collect(Collectors.groupingBy(str -> Stream.of(str.split("")).sorted().collect(Collectors.joining()))).values()); } }
str -> split -> stream -> sort -> join
给定一个未排序的整数数组 nums
,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n)
的算法解决此问题。
wp:
首先本题是不能排序来做的,因为排序的时间复杂度为O(nlogn)
不符合题目的要求
对于 nums 中的元素 x,以 x 为起点,不断查找下一个数 x+1,x+2,⋯ 是否在 nums 中,并统计序列的长度。
class Solution3{ public int longestConsecutive(int[] nums){ int ans = 0; Setst = new HashSet<>(); for(int num:nums){ st.add(num); } for(int x:st){ if(st.contains(x-1)){ continue; } int y = x+1; while(st.contains(y)){ y++; } ans = Math.max(ans,y-x); } return ans; } }
主要使用了hashset来存储nums这个数组
主要的核心就是for循环
for(int x:st){ if(st.contains(x-1)){ continue; } int y = x+1; while(st.contains(y)){ y++; } ans = Math.max(ans,y-x); }
就是去试x周围的数据,x-1和x+1。最后取值y-x获得最大序列的长度
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
wp:
这里我们参考快速排序的方法,以0为基准元素
我们使用两个指针 i
和 j
,只要 nums[i]!=0
,我们就交换 nums[i]
和 nums[j]
在
这样我们很快就能完成排序
时间复杂度:O(n)
class Solution4 { public void moveZeroes(int[] nums){ if (nums == null){ return; } int j =0; for (int i=0;i5.11. 盛最多水的容器
给定一个长度为
n
的整数数组height
。有n
条垂线,第i
条线的两个端点是(i, 0)
和(i, height[i])
。找出其中的两条线,使得它们与
x
轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。
说明:你不能倾斜容器。
用一句话概括双指针解法的要点:指针每一次移动,都意味着排除掉了一个柱子。
如果固定左边的柱子,移动右边的柱子,那么水的高度一定不会增加,且宽度一定减少,所以水的面积一定减少。这个时候,左边的柱子和任意一个其他柱子的组合,其实都可以排除了。也就是我们可以排除掉左边的柱子了。
class Solution5{ public int maxArea(int[] height){ int res = 0; int i = 0; int j = height.length-1; while (i6.三数之和
给你一个整数数组
nums
,判断是否存在三元组[nums[i], nums[j], nums[k]]
满足i != j
、i != k
且j != k
,同时还满足nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为0
且不重复的三元组。注意:答案中不可以包含重复的三元组。
首先对数组进行排序
固定一个数i
L是i后面一个,R是len的最后一个
如果nums[i]大于0的话,sum必然大于0
如果num[i]==nums[i-1]的话,说明数字重复了,需要跳过
sum==0的时候,nums[L]==nums[L+1]重复舍去
nums[R] = nums[R-1]重负舍去
其中L是++的,R是——的
然后开始编码
当sum>0的时候说明R太大了,R--
Sum<0的时候说明L太小了,L++
wp:
class Solution6{ public static List> threeSum(int[] nums){ List
> ans = new ArrayList<>(); int len = nums.length; if (nums==null||len<3) return ans; Arrays.sort(nums); for(int i =0;i
0) break; if (i>0&&nums[i]==nums[i-1]) continue; int L = i+1; int R = len - 1; while (L 0) R--; } } return ans; } } 滑动窗口
6.无重复字符的最长子串
给定一个字符串
s
,请你找出其中不含有重复字符的 最长 子串 的长度。wp:
hashmap
dic
统计:指针j遍历字符s,哈希表统计s[j]最后一次出现的索引根据上轮i与dis[s[j]]更新左边界i,保证[i+1,j]内无重复字符且最大
i= max(dic[s[j]],i)
更新res就是[i+1,j]的len即j-1的最大值
class Solution7{ public int lenOfLongesSubstring(String s){ Mapdic = new HashMap<>(); int i=-1,res = 0,len =s.length(); for(int j=0;i 链表
7.相交链表
给你两个单链表的头节点
headA
和headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回null
。class Solution8 { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { if (headA == null || headB == null) return null; ListNode A = headA, B = headB; while (A != B) { A = (A != null) ? A.next : headB; B = (B != null) ? B.next : headA; } return A; } } class ListNode { int val; ListNode next; ListNode(int x) { val = x; next = null; } } 先处理一下A与B不为空的情况
最重要的是
while (A != B) { A = (A != null) ? A.next : headB; B = (B != null) ? B.next : headA; }
两个具有相同结尾的链表拼接,无论哪一个在前,哪一个在后,这两种拼接方式,他们总能保持最后的一段相同的节点是不变的。
8.反转链表
给你单链表的头节点
head
,请你反转链表,并返回反转后的链表。直接通过双指针反转
class Solution { public ListNode reverseList(ListNode head) { ListNode cur = head, pre = null; while(cur != null) { ListNode tmp = cur.next; // 暂存后继节点 cur.next cur.next = pre; // 修改 next 引用指向 pre = cur; // pre 暂存 cur cur = tmp; // cur 访问下一节点 } return pre; } }9.回文链表
给你一个单链表的头节点
head
,请你判断该链表是否为。如果是,返回true
;否则,返回false
。这个题是在上面的基础上进行更改
class Solution9{ public ListNode reverList(ListNode head){ ListNode cur = head,pre = null; while (cur!=null){ ListNode tmp = cur.next; cur.next = pre; pre = cur; cur = tmp; } return pre; } public boolean isPalindrome(ListNode head){ ListNode mid =middleNode(head); ListNode head2 = reverList(mid); while (head2!=null){ if (head.val!=head2.val){ return false; } head = head.next; head2 = head2.next; } return true; } private ListNode middleNode(ListNode head){ ListNode slow = head; ListNode fast = head; while (fast!=null&&fast.next!=null){ slow = slow.next; fast = fast.next.next; }return slow; } }先找到中间的节点,也就是middleNode函数
slow比慢等到fast.next到终点的时候.slow就是中间的节点
然后反正mid那一部分,如果mid与反转后的相同的话,那么就是回文串
返回ture反之则false
9.环形链表1
给你一个链表的头节点
head
,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪
next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。如果链表中存在环 ,则返回
true
。 否则,返回false
。如果一个链表存在环,那么快慢指针必然会相遇。实现代码如下
所以我们直接编写代码
class Solution10{ public boolean hasCycle(ListNode head){ ListNode slow = head,fast = head; while (fast!=null&&fast.next!=null){ slow = slow.next; fast = fast.next.next; if (fast==slow){ return true; } } return false; } }10.环形链表2
给定一个链表的头节点
head
,返回链表开始入环的第一个节点。 如果链表无环,则返回null
。如果链表中有某个节点,可以通过连续跟踪
next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果pos
是-1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改 链表。
根据分析,有环的标识是fast==slow
然后fast和slow第二次相遇的node就是环的节点
所以很简单的就可以分析出
class Solution11{ public ListNode detectCycle(ListNode head){ ListNode fast = head,slwo = head; while (fast!=null&&fast.next!=null){ fast = fast.next.next; slwo = slwo.next; if (fast == slwo) { fast = head; while (slwo!=fast){ slwo = slwo.next; fast = fast.next; } return fast; } } return null; } }直接使用两次相遇,相遇的fast就是环的起点;
11.合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
我们使用递归算法
当两个链表都为空的时候,说明已经合并完毕了
l1和l2哪个同头节点更小,较小节点的Next指针就指向其余节点的合并结果
class Solution12{ public ListNode mergeTowLists(ListNode l1,ListNode l2){ if (l1==null){ return l2; } else if (l2==null){ return l1; } else if (l1.val12.两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。‘
计算每一位的时候要考虑上一位的进位问题,计算结束后同样要跟更新进位
如果两个链表全部遍历完毕后,进位值为 1,则在新链表最前方添加节点 1
class Solution13 { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { ListNode pre = new ListNode(0); ListNode cur = pre; int carry = 0; while (l1 != null || l2 != null) { int x = l1 == null ? 0 : l1.val; int y = l2 == null ? 0 : l2.val; int sum = x + y + carry; carry = sum / 10; sum = sum % 10; cur.next = new ListNode(sum); cur = cur.next; if (l1 != null) { l1 = l1.next; } if (l2 != null) { l2 = l2.next; } } if (carry == 1) { cur.next = new ListNode(carry); } return pre.next; } }13.删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第
n
个结点,并且返回链表的头结点。设预先指针 pre 的下一个节点指向 head,设前指针为 start,后指针为 end,二者都等于 pre start 先向前移动n步 之后 start 和 end 共同向前移动,此时二者的距离为 n,当 start 到尾部时,end 的位置恰好为倒数第 n 个节点
因为要删除该节点,所以要移动到该节点的前一个才能删除,所以循环结束条件为
start.next != null
删除后返回
pre.next
,为什么不直接返回head
呢,因为head
有可能是被删掉的点class Solution14 { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode pre = new ListNode(0); pre.next = head; ListNode start = pre, end = pre; while (n != 0) { start = start.next; n--; }//start先提前移动 while (start.next != null) { start = start.next; end = end.next; }//一块移动 end.next = end.next.next;//删除某节点 return pre.next; } }14.两两交换链表中的节点
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
调用单元:设需要交换的两个点为 head 和 next,head 连接后面交换完成的子链表,next 连接 head,完成交换 终止条件:head 为空指针或者 next 为空指针,也就是当前无节点或者只有一个节点,无法进行交换
假设链表为
tmp -> (A) -> (B) -> (C) -> (D) -> null start -> (B) end -> (C) 使tmp.next = end;
链表现在变成了这样
(A) -> (C) -> (D) -> null (B) (断开) 再start.next = end.next;
(B)就指向了(D)
end.next = start;
然后(C)就指向了(B)
所以链表就变成了
(A) -> (C) -> (B) -> (D) -> null最后tmp = start;
再从B开始继续迭代循环
最终完成了链表节点的交换
完整代码:
class Solution15 { public ListNode swapPairs1(ListNode head) { if (head == null || head.next == null) { return head; }//没有节点,或者只剩一个的时候 ListNode next = head.next; head.next = swapPairs1(next.next);// next.next = head;//后节点等于头节点 return next; } public ListNode swapPairs2(ListNode head){ ListNode pre = new ListNode(0); pre.next = head; ListNode tmp = pre; while (tmp.next!=null&&tmp.next.next!=null){ ListNode start = tmp.next; ListNode end = tmp.next.next; tmp.next = end;//head start.next = end.next; end.next = start; tmp = start; } return pre.next; } }15. 随机链表的复制
给你一个长度为
n
的链表,每个节点包含一个额外增加的随机指针random
,该指针可以指向链表中的任何节点或空节点。构造这个链表的 深拷贝。 深拷贝应该正好由
n
个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的next
指针和random
指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。例如,如果原链表中有
X
和Y
两个节点,其中X.random --> Y
。那么在复制链表中对应的两个节点x
和y
,同样有x.random --> y
。返回复制链表的头节点。
用一个由
n
个节点组成的链表来表示输入/输出中的链表。每个节点用一个[val, random_index]
表示:
val
:一个表示Node.val
的整数。
random_index
:随机指针指向的节点索引(范围从0
到n-1
);如果不指向任何节点,则为null
。你的代码 只 接受原链表的头节点
head
作为传入参数。class Solution16{ public Node copyRandomList(Node head){ if (head==null) return null; Node cur =head; Mapmap = new HashMap<>(); while (cur!=null){ map.put(cur,new Node(cur.val)); cur = cur.next; } cur = head; while (cur!=null){ map.get(cur).next = map.get(cur.next); map.get(cur).random = map.get(cur.random); cur = cur.next; } return map.get(head); } } while (cur!=null){ map.put(cur,new Node(cur.val)); cur = cur.next; }
先把链表复制一份
构建新节点的
next
和random
引用指向。都是随机的然后迭代下一个节点
最后返回head节点
16.排序链表
给你链表的头结点
head
,请将其按 升序 排列并返回 排序后的链表 。我们使用新建链表的方式
把val值排序后再组成一个新链表
class Solution17{ public ListNode sortList(ListNode head){ if (head==null){ return null; } ListNode cur = head; int n = 0; while (cur!=null){ n++; cur = cur.next; } int[] arr = new int [n]; cur = head; for(int i=0;i17.LRU 缓存
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现
LRUCache
类:
LRUCache(int capacity)
以 正整数 作为容量capacity
初始化 LRU 缓存
int get(int key)
如果关键字key
存在于缓存中,则返回关键字的值,否则返回-1
。
void put(int key, int value)
如果关键字key
已经存在,则变更其数据值value
;如果不存在,则向缓存中插入该组key-value
。如果插入操作导致关键字数量超过capacity
,则应该 逐出 最久未使用的关键字。函数
get
和put
必须以O(1)
的平均时间复杂度运行。基于LinkedHashMap来实现
LinkedHashMap
是 有序哈希表,可以按插入顺序或访问顺序存储键值对。super(capacity, 0.75F, true);
capacity: 初始容量
0.75F: 负载因子(默认
0.75
)true: 启用 访问顺序,即 最近访问的元素会被移到链表尾部,最久未使用的元素会在链表头部。
size() > capacity
时,返回true
,LinkedHashMap
会自动删除 链表头部的最老元素(即最近最少使用的元素)。class LRUCache extends LinkedHashMap{ private int capacity; public LRUCache(int capacity) { super(capacity,0.75F,true); this.capacity = capacity; } public int get(int key) { return super.getOrDefault(key,-1); } public void put(int key, int value) { super.put(key,value); } @Override protected boolean removeEldestEntry(Map.Entry eldest){ return size() > capacity; } } 数组
18.最大子数组和
给你一个整数数组
nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组是数组中的一个连续部分。
class Solution18{ public int maxSubArray(int[] nums){ int ans = nums[0]; int sum = 0; for (int num:nums){ if (sum>0){ sum+=num; } else { sum = num; } ans=Math.max(ans,sum); } return ans; } }直接使用循环来完成这个目的