1. 反转链表
代码实现
非递归
思路:声明一个pre和cur变量,因为要反转,所以要让cur.next域指向pre,然后进行下一步迭代,因为下一步迭代前,cur断了,所以在迭代之前要用一个next变量来存储cur的下一个结点,直到迭代到cur为空.
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while(cur != null){
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
递归
思路:其实跟上面非递归差不多
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null) return null;
//初始时pre为null, cur为head上面的题也是一样
return reverse(null, head);
}
public ListNode reverse(ListNode pre, ListNode cur){
ListNode next = cur.next;
cur.next = pre;
if(next == null) return cur;
return reverse(cur, next);
}
}
2. 无重复字符的最长子串
思路:一眼丁真,鉴定为滑动窗口,为什么是滑动窗口嘞,因为要找最长无重复字符子串,让窗口内的字符子串不重复,重复了就收缩,而这个窗口也就是子串,用个变量存储结果,每次向右扩张完就判断是否最长。思路是先让滑动窗口向右扩张,如果遇到重复了就收缩到窗口内那个重复字符的右边第一个。
代码实现
class Solution {
public int lengthOfLongestSubstring(String s) {
int len = s.length();
if(len <= 1) return len;
Map<Character, Integer> map = new HashMap<>();
int left = 0;
int res = 1;
for(int i = 0; i < s.length(); i++){
if(map.containsKey(s.charAt(i))){
left = Math.max(map.get(s.charAt(i)) + 1, left);
}
map.put(s.charAt(i), i);
if(i - left + 1 > res) res = i - left + 1;
}
return res;
}
}
3. LRU 缓存
做这道题前先来了解一下LRU。
LRU全称Least Recently Used,翻译过来也就是最近最少使用,是一种页面置换算法(操作系统的),是选择最近最久未使用的页面予以淘汰。拿个具体的例子,我们应该用过手机的任务管理器吧,每次点开新的程序或者一个正在运行中的程序,打开管理器时,这个总会放到第一位,而假设一个任务管理器你只能放有限个并且当前任务的槽位满了,那么我们就需要把最末端(也就是最近最久未使用)的给淘汰掉。
思路:从get和put来看,不难看出来要用一个哈希表存储吧,而当容量满了,插入的新的key-value要将旧的淘汰,说明有序,而哈希表并不是有序的,所以我们应该用一个有序的数据结构,链表或数组(建议不使用数组,因为在删除的时候需要很多次移动,需要O(n)的时间复杂度,而哈希表也能通过key快速定位到这个key-value,算是可随机访问到key-value,从而可以将这个key-value删除掉,所以没必要用数组)来存储这个序列,哈希表则负责将链表的值存储到哈希表的value里。其实在Java中也提供了一个LinkedListHashMap,可以通过重写里面的方法,来实现RLU(读源码,里面有一个方法有说)
代码实现
class LRUCache {
Node head;
Node tail;
Map<Integer, Node> map = new HashMap<>();
int capacity;
int count;
public LRUCache(int capacity) {
head = new Node();
tail = new Node();
head.next = tail;
tail.pre = head;
this.capacity = capacity;
int count = 0;
}
public int get(int key) {
if(!map.containsKey(key)){
return -1;
}
Node node = map.get(key);
deleteNode(node);
addHead(node);
return node.value;
}
public void put(int key, int value) {
//如果存在key,则先删掉
if(map.containsKey(key)){
Node node = map.get(key);
deleteNode(node);
}
if(count >= capacity){
//淘汰末尾
deleteTail();
}
addHead(new Node(key, value));
}
private void addHead(Node node){
Node next = head.next;
head.next = node;
node.pre = head;
node.next = next;
next.pre = node;
map.put(node.key, node);
count++;
}
private void deleteNode(Node node){
Node pre = node.pre, next = node.next;
//删除
pre.next = next;
next.pre = pre;
node.pre = null;
node.next = null;
map.remove(node.key);
count--;
}
private void deleteTail(){
deleteNode(tail.pre);
}
}
class Node{
Node pre;
Node next;
int key;
int value;
public Node(){
}
public Node(int key,int value){
this.key = key;
this.value = value;
}
}
4. 三数之和
思路:排序,三指针,去重
代码实现:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
int len = nums.length;
for(int i = 0; i < len; i++){
//排序过的,只要大于0后面怎么加都是大于0
if(nums[i] > 0) break;
//去重
while(i - 1 >= 0 && i < len && nums[i - 1] == nums[i]) i++;
int left = i + 1, right = len - 1;
while(left < right){
if(nums[i] + nums[left] + nums[right] < 0) left++;
else if(nums[i] + nums[left] + nums[right] > 0) right--;
else {
res.add(Arrays.asList(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 res;
}
}
5. 排序数组(后续补上其他算法)
快速排序做法(要求手撕):
思路:确定一个基准值,声明双指针,把一个区间划分为两个区间,左边都小于基准值,右边大于等于基准值,然后分而治之。
代码实现:
class Solution {
public int[] sortArray(int[] nums) {
quickSort(nums, 0, nums.length - 1);
return nums;
}
private void quickSort(int[] nums, int left, int right){
if(left < right){
int i = left, j = right, x = nums[left];
while(i < j){
while(i < j && nums[j] >= x){
j--;
}
nums[i] = nums[j];
while(i < j && nums[i] < x){
i++;
}
nums[j] = nums[i];
}
nums[i] = x;
quickSort(nums, left, i - 1);
quickSort(nums, i + 1, right);
}
}
}
6. 数组中的第K个最大元素
思路1,快排。进行正序的快速排序,然后取倒数第k个,时间复杂度O(nlogn),空间复杂度是调用栈的高度,即O(logn)
思路2,优先队列。因为要取最大的,所以建立一个大小为k的小根堆,遍历整个数组,当堆没满时,将元素入堆;当遇到比堆顶大的数(堆顶也就是此时小根堆里最小的数),将堆顶退出堆,然后将这个数入堆中,形成新的小根堆。最后,遍历完数组时,小根堆中的元素都是最大的k个,而堆顶(因为是小根堆,所以堆顶是堆里最小的)就是要找的第k大的元素。
代码实现:
class Solution {
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> minHeap = new PriorityQueue<>(k, Comparator.comparingInt(a -> a));
for(int i = 0; i < k; i++){
minHeap.offer(nums[i]);
}
for(int i = k; i < nums.length; i++){
//堆顶
int peek = minHeap.peek();
if(nums[i] > peek){
//退出堆顶,nums[i]入堆
minHeap.poll();
minHeap.offer(nums[i]);
}
}
return minHeap.peek();
}
}