LeetCode 5.最长回文子串
官方题解:https://leetcode.cn/problems/longest-palindromic-substring/solution/zui-chang-hui-wen-zi-chuan-by-leetcode-solution/
题目描述:给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。
示例 2:输入:s = "cbbd" 输出:"bb"
一、我们可以从每一种边界情况开始【扩展】,边界情况:考虑到子串长度为奇或偶的情况,我们中心扩展每一种边界情况,直到无法扩展,返回len
二、注意for循环和奇偶的时候start和end的变化
start:i - (len - 1)/2
。奇数3 - 1=2,偶数4-1=3,/2之后都得1;正好奇数中间下标-1,偶数firstEnd下标也-1end:i + len/2
。奇数中间+3/2=+1;偶数firstEnd+4/2=+2- 时间复杂度:O(n^2),其中 n 是字符串的长度。长度为 奇 和 偶 的回文中心分别有 n 和 n−1 个,
每个回文中心最多会向外扩展 O(n) 次
。 - 空间复杂度:O(1)。
中心扩展算法完整代码:
public class 第5题最长回文子串 {
public static String longestPalindrome(String s) {
if (s == null || s.length() < 1) return ""; // 因为s是对象,所以可以是null值
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
int len1 = centerExpand(s, i, i); // 奇数拓展
int len2 = centerExpand(s, i, i + 1); // 偶数拓展,当i+1越界之后函数判断自动计算len=0【i从0-n-1】【因为奇数已经计算过了,,所以这里只计算偶数判0】
int len = Math.max(len1, len2); // len只是存一个最大值,len - 1才是最长回文子串的长度
if (len > end - start) {
start = i - (len - 1) / 2; // 奇数3 - 1=2,偶数4-1=3,/2之后都得1;正好奇数中间下标-1,偶数firstEnd下标也-1
end = i + len / 2; // 奇数中间+3/2=+1;偶数firstEnd+4/2=+2
}
}
return s.substring(start, end + 1);
}
private static int centerExpand(String s, int l, int r) {
while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) {
--l; // 向外拓展
++r;
}
return r - l - 1; // 因为临界条件后依然会计算一次left--和right++
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.next();
String ans = longestPalindrome(s);
for (char ch : ans.toCharArray()) {
System.out.print(ch);
}
}
}
LeetCode 146.LRU缓存机制(70行代码)
一、手写一个双向链表,手动连接head.next=tail;tail.prev=head,后使用Map,Integer存key,DLinkedNode存value。
使用DLinkedNode存储node节点,其中包含key和value;使用hashmap可以通过key找到对应的value
双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的。
这样以来,我们首先使用哈希表进行定位,找出缓存项在双向链表中的位置,随后将其移动到双向链表的头部,即可在 O(1)O(1) 的时间内完成 get 或者 put 操作。
可以使用一个伪头部和伪尾部标记界限,这样在添加结点和删除结点的时候就不需要检查相邻的节点是否存在。
添加元素的时候:
二、removeNode的时候要手动修改好前后的两个连接(两条线)
,但是addHead的时候node和head都要写好引用
removeNode:
private void removeNode(DLinkedNode node) { // 功能:根据node.prev和node.next删除单个节点node【两个节点之间只有prev和next连接】
node.prev.next = node.next;
node.next.prev = node.prev;
}
addToHead:
private void addToHead(DLinkedNode node) { // 添加到head的后面一个位置【将node插入到head与head.next之间,2个连接变4个】
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
双向链表+hashmap的完整代码:
public class 第146LRU缓存 {
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {}
public DLinkedNode(int key, int value) { this.key = key; this.value = value;}
}
private Map<Integer, DLinkedNode> cache = new HashMap<>(); // cache是一个map存储key和DLinkedNode
private int size;
private int capacity;
private DLinkedNode head, tail;
public 第146LRU缓存(int capacity) { // 构造器
this.size = 0;
this.capacity = capacity;
// 使用伪头部和伪头节点
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) return -1;
moveToHead(node); // 删除 + 添加到head与head.next之间
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) { // 如果key不存在
DLinkedNode newNode = new DLinkedNode(key, value);
cache.put(key, newNode);
addToHead(newNode);
++size;
if (size > capacity) {
DLinkedNode tail = removeTail();
cache.remove(tail.key);
--size;
}
} else { // 如果key存在,移动到head位置
node.value = value; // 修改value
moveToHead(node); // 将原node移除,并移动到head的后面一个位置
}
}
// 基础函数1
private void removeNode(DLinkedNode node) { // 功能:根据node.prev和node.next删除单个节点node【两个节点之间只有prev和next连接】
node.prev.next = node.next;
node.next.prev = node.prev;
}
// 基础函数2
private void addToHead(DLinkedNode node) { // 添加到head的后面一个位置【将node插入到head与head.next之间,2个连接变4个】
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private DLinkedNode removeTail() { // 删除尾部的节点(即tail的前一个节点)
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
}
LeetCode 148.排序链表
一、第一个for循环截止条件刚好到最大的sub超过length/2的时候(即sub的下一个就=2*pre_sub超过了length循环截止),刚好可以排序完
二、prev指向排序好链表的末尾节点,curr指向排序好链表的next节点。merge函数可以参考LeetCode 21.合并两个有序链表的做法
三、注意:每一个head都要在尾部截断,第二个for条件要避免出现null.next的情况,所以要curr!=null
ListNode head2 = curr.next; // 将curr.next存起来
curr.next = null; // 截断curr与后面的next
自底向上归并排序完整代码:
class Solution {
public ListNode sortList(ListNode head) {
if (head == null) return head;
int length = 0;
ListNode node = head;
while (node != null) {length++; node = node.next;}
ListNode dummyHead = new ListNode(0, head);
for (int sub = 1; sub < length; sub <<= 1) { // 位运算*2 cpu效率高
ListNode prev = dummyHead, curr = dummyHead.next;
while (curr != null) {
ListNode head1 = curr;
for (int i = 1; i < sub && curr.next != null; i++) {
curr = curr.next;
}
ListNode head2 = curr.next; // 截断上一个sub(分两步)
curr.next = null; // 截断上一个sub(分两步)
curr = head2; // 令curr = head2引用
for (int i = 1; i < sub && curr != null && curr.next != null; i++) { // 不能出现null.next
curr = curr.next;
}
ListNode next = null;
if (curr != null) {
next = curr.next; // next用于记录 拆分完两个链表的结束位置
curr.next = null; // 截断head2的末尾
}
ListNode merged = merge(head1, head2);
prev.next = merged; // prev.next 指向排好序链表的头,prev只在这里起作用了,所以下一步要把prev移动到head2的末尾
while (prev.next != null) {
prev = prev.next;
}
curr = next;
}
}
return dummyHead.next;
}
// public ListNode merge(ListNode head1, ListNode head2) {
// ListNode dummyHead = new ListNode(0);
// ListNode temp = dummyHead, temp1 = head1, temp2 = head2;
// while (temp1 != null && temp2 != null) {
// if (temp1.val <= temp2.val) {
// temp.next = temp1;
// temp1 = temp1.next;
// } else {
// temp.next = temp2;
// temp2 = temp2.next;
// }
// temp = temp.next;
// }
// if (temp1 != null) {
// temp.next = temp1;
// } else if (temp2 != null) {
// temp.next = temp2;
// }
// return dummyHead.next;
// }
public ListNode mrege(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode curr = dummy;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
curr.next = l1; l1 = l1.next;}
else {
curr.next = l2; l2 = l2.next;
}
curr = curr.next;
}
curr.next = l1 == null ? l2 : l1;
return dummy.next;
}
}
LeetCode 21合并两个有序链表
题目描述:
将两个升序链表
合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例2:
输入:l1 = [], l2 = []
输出:[]
示例3:
输入:l1 = [], l2 = [0]
输出:[0]
一、使用迭代的方法,时间复杂度为O(m+n),空间复杂度为O(1)
定义一个ans哑节点,还有一个迭代的引用prev
可通过完整代码:
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode ans = new ListNode(-1);
ListNode prev = ans;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next; // 移动引用
}
prev.next = l1 == null ? l2 : l1;
return ans.next;
}