文章目录
- **合并两个有序链表**
- [23. 合并K个升序链表](https://leetcode-cn.com/problems/merge-k-sorted-lists/)
- [剑指 Offer 63. 股票的最大利润](https://leetcode-cn.com/problems/gu-piao-de-zui-da-li-run-lcof/)
- [123. 买卖股票的最佳时机 III](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/)
- [剑指 Offer 42. 连续子数组的最大和](https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/)
- [155. 最小栈](https://leetcode-cn.com/problems/min-stack/)
- [146. LRU缓存机制](https://leetcode-cn.com/problems/lru-cache/)
- [460. LFU缓存](https://leetcode-cn.com/problems/lfu-cache/)
- [剑指 Offer 41. 数据流中的中位数](https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/)
- [4. 寻找两个正序数组的中位数](https://leetcode-cn.com/problems/median-of-two-sorted-arrays/)
- [5. 最长回文子串](https://leetcode-cn.com/problems/longest-palindromic-substring/)
- [88. 合并两个有序数组](https://leetcode-cn.com/problems/merge-sorted-array/)
合并两个有序链表
剑指 Offer 25. 合并两个排序的链表
难度简单45
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
限制:
0 <= 链表长度 <= 1000
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode vNode = new ListNode(0);//虚拟节点
ListNode cur = vNode;//当前节点
while(l1!=null && l2!=null){
if(l1.val < l2.val){
cur.next = l1;
l1 = l1.next;
}else{
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
if(l1!=null) cur.next = l1;
if(l2!=null) cur.next = l2;
return vNode.next;
}
}
23. 合并K个升序链表
难度困难864
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:
输入:lists = []
输出:[]
示例 3:
输入:lists = [[]]
输出:[]
提示:
k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i]
按 升序 排列lists[i].length
的总和不超过10^4
使用合并有序列表函数
/**
* 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 mergeTwoLists(ListNode l1, ListNode l2) {
ListNode vNode = new ListNode(0);//虚拟节点
ListNode cur = vNode;//当前节点
while(l1!=null && l2!=null){
if(l1.val < l2.val){
cur.next = l1;
l1 = l1.next;
}else{
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
if(l1!=null) cur.next = l1;
if(l2!=null) cur.next = l2;
return vNode.next;
}
public ListNode mergeKLists(ListNode[] lists) {
ListNode mergeNode = null;
for(ListNode node : lists){
mergeNode = mergeTwoLists(mergeNode,node);
}
return mergeNode;
}
}
利用分治思想
/**
* 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 mergeTwoLists(ListNode l1, ListNode l2) {
ListNode vNode = new ListNode(0);//虚拟节点
ListNode cur = vNode;//当前节点
while(l1!=null && l2!=null){
if(l1.val < l2.val){
cur.next = l1;
l1 = l1.next;
}else{
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
if(l1!=null) cur.next = l1;
if(l2!=null) cur.next = l2;
return vNode.next;
}
//使用归并排序思想
public ListNode merge(ListNode[] lists,int l,int r){
if(r == l) return lists[l];
if(l > r) return null;//不存在
int mid = (r-l)/2+l;
//归并合并
return mergeTwoLists(merge(lists,l,mid),merge(lists,mid+1,r));
}
public ListNode mergeKLists(ListNode[] lists) {
return merge(lists,0,lists.length-1);
}
}
使用优先队列存储节点
/**
* 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 mergeKLists(ListNode[] lists) {
//使用优先队列
Queue<ListNode> queue = new PriorityQueue<>((n1,n2)->{return n1.val-n2.val;});
for(ListNode node : lists){
if(node !=null)
queue.offer(node);
}
//保存一个虚拟节点
ListNode vNode = new ListNode(0);
ListNode cur = vNode;//当前节点
while(!queue.isEmpty()){
ListNode min = queue.poll();
cur.next = min;
cur = cur.next;
if(min.next != null)
queue.offer(min.next);
}
return vNode.next;
}
}
剑指 Offer 63. 股票的最大利润
难度中等49
假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
限制:
0 <= 数组长度 <= 10^5
class Solution {
public int maxProfit(int[] prices) {
//每天转的钱,是当前-前一天的
if(prices == null || prices.length <= 1) return 0;//判空
int ans = 0;
//前一天的最大利润
int maxPro = 0;
int minIn = prices[0];
for(int i=1;i<prices.length;i++){
int cur = prices[i];
if(cur > minIn)
maxPro = Math.max(maxPro,cur-minIn);//先更新最大收益
minIn = Math.min(minIn,cur);//后更新最低行情
}
return maxPro;
}
}
class Solution {
public int maxProfit(int[] prices) {
//找出波峰与波谷
int i = 0;
int maxPro = 0;
int feng = prices[0];
int gu = prices[0];
while(i < prices.length-1){
while(i < prices.length-1 && prices[i] >= prices[i+1])
i++;//寻找波谷
gu = prices[i];
while(i < prices.length-1 && prices[i] <= prices[i+1])
i++;//寻找波峰
feng = prices[i];
maxPro += feng - gu;
}
return maxPro;
}
}
class Solution {
public int maxProfit(int[] prices) {
//因为连续增加的情况最终由:n-1 = n-(n-1)+... + 2-1
if(prices == null || prices.length < 2) return 0;
int ans = 0;
for(int i=1;i<prices.length;i++){
if(prices[i] > prices[i-1])
ans += prices[i]-prices[i-1];
}
return ans;
}
}
123. 买卖股票的最佳时机 III
难度困难484
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [3,3,5,0,0,3,1,4]
输出: 6
解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这个情况下, 没有交易完成, 所以最大利润为 0。
class Solution {
public int maxProfit(int[] prices) {
if(prices==null || prices.length < 2) {
return 0;
}
int n = prices.length;
//定义5种状态,并初始化第一天的状态
int dp1 = -prices[0];
int dp2 = 0;
int dp3 = -prices[0];
int dp4 = 0;
for(int p : prices) {
//这里省略dp0,因为dp0每次都是从上一个dp0来的相当于每次都是0
//处理第一次买入、第一次卖出
dp1 = Math.max(dp1,0-p);
dp2 = Math.max(dp2,dp1+p);
//处理第二次买入、第二次卖出
dp3 = Math.max(dp3,dp2-p);
dp4 = Math.max(dp4,dp3+p);
}
//返回最大值
return Math.max(dp4,dp2);
}
}
剑指 Offer 42. 连续子数组的最大和
难度简单108
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
提示:
1 <= arr.length <= 10^5
-100 <= arr[i] <= 100
class Solution {
public int maxSubArray(int[] nums) {
//DP
if(nums == null || nums.length == 0) return 0;
int maxSub = nums[0];
int preSub = maxSub;//记录前一个的值
for(int i=1;i<nums.length;i++){
//最大值转移:前一个和小于等于0时,对当前无收益
if(preSub <= 0){
maxSub = Math.max(nums[i],maxSub);
preSub = nums[i];
}
else{
maxSub = Math.max(maxSub,preSub+nums[i]);
preSub = nums[i]+preSub;
}
}
return maxSub;
}
}
155. 最小栈
难度简单645
设计一个支持 push
,pop
,top
操作,并能在常数时间内检索到最小元素的栈。
push(x)
—— 将元素 x 推入栈中。pop()
—— 删除栈顶的元素。top()
—— 获取栈顶元素。getMin()
—— 检索栈中的最小元素。
示例:
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
class MinStack {
private Stack<Integer> stack;
private Stack<Integer> min_stack;
public MinStack() {
stack = new Stack<>();//初始化两个栈
min_stack = new Stack<>();
}
public void push(int x) {
stack.push(x);//压入大栈
if(min_stack.isEmpty() || x <= min_stack.peek())//压入最小值
min_stack.push(x);
}
public void pop() {
if(stack.pop().equals(min_stack.peek()))//弹出最小值
min_stack.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return min_stack.peek();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(x);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
146. LRU缓存机制
难度中等829
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get
和 写入数据 put
。
获取数据 get(key)
- 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
写入数据 put(key, value)
- 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?
示例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得关键字 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得关键字 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
继承LinkedHashMap
Java提供的LinkedHashMap是有序集合,可以通过重载removeEldestEntry方法实现LRU的一定容量要求
class LRUCache extends LinkedHashMap<Integer,Integer>{
//使用默认的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);//不存在返回-1
}
public void put(int key, int value) {
super.put(key,value);//存入
}
@Override
protected boolean removeEldestEntry(Map.Entry<Integer,Integer> eldest){
return size() >capacity;//当超过容量时删除多余的
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/
借助Map,自建双向链表
- 容量与当前size
- 虚拟头尾节点
- 哈希表查找
- 添加节点、删除节点、移动头节点进行辅助
class LRUCache {
class DLinkedNode{
//自建双向链表
int key;//键
int val;//值
DLinkedNode prev;//前序指针
DLinkedNode next;//后续指针
public DLinkedNode(){}
public DLinkedNode(int key,int val){
this.key = key;
this.val = val;
}
}
private Map<Integer,DLinkedNode> map = new HashMap<>();//用于快速查找节点
private int size;//当前大小
private int capacity;//缓存容量
private DLinkedNode head;//虚拟头节点
private DLinkedNode tail;//虚拟尾节点
public LRUCache(int capacity) {
this.capacity = capacity;
this.size = 0;//初始化
//使用虚拟节点
head = new DLinkedNode();
tail = new DLinkedNode();//相互连接
head.next = tail;
tail.prev = head;
}
public int get(int key) {
//先判断哈希表中是否存在
DLinkedNode node = map.get(key);
if(node == null)
return -1;//不存在
//如果存在,则需要先把此访问的节点移动到头部,再返回数据
moveToHead(node);
return node.val;
}
public void put(int key, int value) {
//1、查找是否存在,存在则移动到头部
DLinkedNode node = map.get(key);
if(node != null){
//移动
node.val = value;
moveToHead(node);
}else{//2、不存在则先根据容量需不需要删除尾部
//常见节点
DLinkedNode newNode = new DLinkedNode(key,value);
//添加到哈希表
map.put(key,newNode);
//添加双向链表头部
addToHead(newNode);//
//判断容量
size++;
if(size > capacity){
//先删除尾部
DLinkedNode t = removeTail();
//删除哈希表中数据
map.remove(t.key);
--size;
}
}
}
//辅助函数:添加一个新节点到头部,同时控制缓存容量
private void addToHead(DLinkedNode node){
//连接头节点
node.prev = head;
node.next = head.next;
//1->2 + 3
//1<-3 3->2
//3<-2 1->3
head.next.prev = node;
head.next = node;
}
//删除节点
private void removeNode(DLinkedNode node){
node.prev.next = node.next;
node.next.prev = node.prev;
}
//将一个节点移动至头节点
private void moveToHead(DLinkedNode node){
//先删除节点进行,再连接到头部
removeNode(node);
addToHead(node);
}
//删除尾部节点
private DLinkedNode removeTail(){
DLinkedNode res = tail.prev;//尾部节点
removeNode(res);//删除
return res;//返回
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/
460. LFU缓存
难度困难259
请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。它应该支持以下操作:get
和 put
。
get(key)
- 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1。put(key, value)
- 如果键已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量时,则应该在插入新项之前,使最不经常使用的项无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除最久未使用的键。
「项的使用次数」就是自插入该项以来对其调用 get
和 put
函数的次数之和。使用次数会在对应项被移除后置为 0 。
进阶:
你是否可以在 O(1) 时间复杂度内执行两项操作?
示例:
LFUCache cache = new LFUCache( 2 /* capacity (缓存容量) */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 去除 key 2
cache.get(2); // 返回 -1 (未找到key 2)
cache.get(3); // 返回 3
cache.put(4, 4); // 去除 key 1
cache.get(1); // 返回 -1 (未找到 key 1)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
解答
使用双向链表存储每个频次的节点
- put操作时需要更新频次(以前存在则直接更新频次,不存在则需要先判断是否缓存已满,已满则清除,之后再根据尾部链表表示的频次判断是否需要新建链表
- get操作需要更新使用频次
class LFUCache {
//自定义双向链表记录频次
Map<Integer,Node> cache;//存储缓存内容
DLinkedList firstLinkedList;
DLinkedList lasstLinkedList;
int size;
int capacity;
class DLinkedList{
//频次信息
int nums;//该双向链表表示的频次信息
DLinkedList prev ;
DLinkedList next;
Node head;//虚拟头节点
Node tail;//虚拟尾部节点
public DLinkedList(){
head = new Node();
tail = new Node();
head.next = tail;
tail.prev = head;
}
public DLinkedList(int nums){
head = new Node();
tail = new Node();
head.next = tail;
tail.prev = head;
this.nums = nums;
}
//移除节点
void removeNode(Node node){
node.prev.next = node.next;
node.next.prev = node.prev;
}
//添加节点
void addNode(Node node){
//添加到头部
node.next = head.next;
head.next.prev = node;
head.next = node;
node.prev = head;
node.dLinkedList = this;//关联链表
}
}
class Node{
//节点信息
int key;
int val;
int nums = 1;//使用频次初始为1
Node prev;
Node next;//所在频次前后节点
DLinkedList dLinkedList;//节点所在频次的双向链表
public Node(){}
public Node(int key,int val){
this.key = key;
this.val = val;
}
}
public LFUCache(int capacity) {
cache = new HashMap<>(capacity);//只能存储指定数目个
firstLinkedList = new DLinkedList();
lasstLinkedList = new DLinkedList();
firstLinkedList.next = lasstLinkedList;//first后面元素为使用频次最高的
lasstLinkedList.prev = firstLinkedList;
this.capacity = capacity;
this.size = 0;
}
public int get(int key) {
//获取节点
Node node = cache.get(key);
if(node == null){
//不存在
return -1;
}
//频次加一
numInc(node);
return node.val;
}
public void put(int key, int value) {
if(capacity == 0)
return ;//不能添加
Node node= cache.get(key);
//之前存在加一不存在新建
if(node != null){
node.val = value;
numInc(node);//添加频次,修改位置
}else{
//不存在
//缓存满了
if(size == capacity){
//缓存满了,需要删除最后链表中的节点
cache.remove(lasstLinkedList.prev.tail.prev.key);
lasstLinkedList.removeNode(lasstLinkedList.prev.tail.prev);
size--;
if(lasstLinkedList.prev.head.next == lasstLinkedList.prev.tail && lasstLinkedList.prev.nums != 1){//为空且频次不为1,就删除,频次为1还可以利用
//此链表为空
removeDLinkeList(lasstLinkedList.prev);
}
}
//添加节点
Node newNode = new Node(key,value);
cache.put(key,newNode);
if(lasstLinkedList.prev.nums != 1){
//需要新建链表
DLinkedList newDlinkedList = new DLinkedList(1);
addDlinkedList(newDlinkedList,lasstLinkedList.prev);
newDlinkedList.addNode(newNode);//添加节点
}
else{
//直接添加
lasstLinkedList.prev.addNode(newNode);
}
size++;
}
}
//辅助函数:增加node访问次数
void numInc(Node node){
//步骤:获取原链表
DLinkedList linkedList = node.dLinkedList;
DLinkedList preLinkedList = linkedList.prev;
linkedList.removeNode(node);//删除此节点
if(linkedList.head.next == linkedList.tail)
removeDLinkeList(linkedList);//当前频次无节点,移除
//将node次数添加并加入到prelinked中
node.nums ++;
if(preLinkedList.nums != node.nums){
//不存在此频次
DLinkedList newDlinkedList = new DLinkedList(node.nums);
addDlinkedList(newDlinkedList,preLinkedList);//添加新的频次链表
newDlinkedList.addNode(node);//添加节点
}else{
preLinkedList.addNode(node);
}
}
//辅助函数:增加某一个频次的链表
void addDlinkedList(DLinkedList newDlinkedList,DLinkedList preLinkedList){
newDlinkedList.next = preLinkedList.next;
preLinkedList.next.prev = newDlinkedList;
preLinkedList.next = newDlinkedList;
newDlinkedList.prev = preLinkedList;
}
//删除某一频次的链表
void removeDLinkeList(DLinkedList linkedList){
linkedList.prev.next = linkedList.next;
linkedList.next.prev = linkedList.prev;
}
}
/**
* Your LFUCache object will be instantiated and called as such:
* LFUCache obj = new LFUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/
剑指 Offer 41. 数据流中的中位数
难度困难53
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
- void addNum(int num) - 从数据流中添加一个整数到数据结构中。
- double findMedian() - 返回目前所有元素的中位数。
示例 1:
输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]
示例 2:
输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]
限制:
- 最多会对
addNum、findMedia
进行50000
次调用。
class MedianFinder {
/** initialize your data structure here. */
Queue<Integer> A,B;
public MedianFinder() {
//构造两个堆
this.A = new PriorityQueue<>();//小顶堆
this.B = new PriorityQueue<>((x,y)->(y-x));//大顶堆
}
public void addNum(int num) {
if(A.size() != B.size()){
A.add(num);
B.add(A.poll());//排序后的数字
}else{
B.add(num);//计数时,A中元素更多
A.add(B.poll());
}
}
public double findMedian() {
return A.size() != B.size() ? A.peek() : (A.peek() + B.peek())/2.0;
}
}
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder obj = new MedianFinder();
* obj.addNum(num);
* double param_2 = obj.findMedian();
*/
4. 寻找两个正序数组的中位数
难度困难3088
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1
和 nums2
。
请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1
和 nums2
不会同时为空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
顺序遍历,找到中位数应该对应的索引
class Solution {
public double findMedianSortedArrays(int[] A, int[] B) {
int m = A.length;
int n = B.length;
int len = m + n;
int left = -1, right = -1;
int aStart = 0, bStart = 0;
for (int i = 0; i <= len / 2; i++) {
left = right;
if (aStart < m && (bStart >= n || A[aStart] < B[bStart])) {
right = A[aStart++];
} else {
right = B[bStart++];
}
}
if ((len & 1) == 0)
return (left + right) / 2.0;
else
return right;
}
}
二分思想,删除一半的数字
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int len1 = nums1.length;
int len2 = nums2.length;
int total = len1+len2;//总长度
int left = (total + 1) /2;//这里需要考虑奇偶的问题
int right = (total + 2) /2;
if((total & 1) == 0){//偶数个,为中间两个数字的平均数
return (findK(nums1,0,nums2,0,left)+findK(nums1,0,nums2,0,right))/2.0;
}else{//奇数个
//直接求中间的个数即可
return findK(nums1,0,nums2,0,left);
}
}
//此为找两个数组中第K小的数据
public int findK(int[] nums1,int i,int[] nums2,int j,int k){
//分别为两个数组,两个数组被删后开始索引,此时需要寻找的第K小数字
if(i >= nums1.length)
return nums2[j+k-1];//此时只剩下一个有序数组
if(j >= nums2.length)
return nums1[i+k-1];
if(k == 1)
return Math.min(nums1[i],nums2[j]);//此时只需要找到最小的数字
//计算出那边的K/2处更小,进行删除
int mid1 = (i+k/2-1) < nums1.length ? nums1[i+k/2-1] : Integer.MAX_VALUE;//删除更小的
int mid2 = (j+k/2-1) < nums2.length ? nums2[j+k/2-1] : Integer.MAX_VALUE;
if(mid1 < mid2)//继续遍历
return findK(nums1,i+k/2,nums2,j,k-k/2);
return findK(nums1,i,nums2,j+k/2,k-k/2);//K需要取整
}
}
5. 最长回文子串
难度中等2599
给定一个字符串 s
,找到 s
中最长的回文子串。你可以假设 s
的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
中心扩散法
每次取i位置以及i&i+1向外扩散,取最大长度,如果大于之前的最大长度,则替换更新。
class Solution {
public String longestPalindrome(String s) {
//中心扩散法:中间一个或者两个字符向外扩散
int len = s.length();
if(s.length() < 2) return s;
int maxLen = 1;
String res = s.substring(0,1);
for(int i=0;i<len-1;i++){
String rsl = centerSpread(s,i,i);//中间一位扩散
String rsr = centerSpread(s,i,i+1);//偶数个
String tmp = rsl.length() > rsr.length() ? rsl : rsr;
if(tmp.length() > maxLen){
maxLen = tmp.length();
res = tmp;
}
}
return res;
}
public String centerSpread(String s,int left,int right){
int len = s.length();
int i = left;
int j= right;
while(i >=0 && j < len){
if(s.charAt(i) == s.charAt(j)){
//扩散
i--;
j++;
}else{
//不符合要求,返回
break;
}
}
return s.substring(i+1,j);//每次扩散后i-1,j+1了,所以i最小值可能为-1,-1位置未扩散,左开又开区间
}
}
Manacher 算法(不用掌握,面试的时候绝大多数情况下不会要求写这个算法,了解思想即可)
88. 合并两个有序数组
难度简单596
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中*,*使 nums1 成为一个有序数组。
说明:
- 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
- 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
使用插入排序思想O(MN)
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
//使用插入排序
for(int i=0;i<n;i++){//需要插入n个数字
int j = m-1;//插入后扩充位置
while(j >= 0){
if(nums1[j] <= nums2[i])
break;//此时直接可以插入
nums1[j+1] = nums1[j];
j--;
}
nums1[j+1] = nums2[i];
m++;
}
}
}
每次往后填充最大的数字
每次往后填充最大的数字,使用nums1使用两个指针控制边界,左指针小于0时说明nums1中原有元素填充完毕,直接把nums2剩余元素复制过去即可,当nums1中没有复制完,则使用nums2最大元素与左指针处比较并将较大的填入右指针处,同时右指针减一,左指针根据填充的数组判断需不需要左移。
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
//思想:先探测最大的,然后天道最后
int right = m+n-1;//最大的数字需要填入的地方
int left = m-1;//开始有空缺的位置
int num2R = n-1;//需要操作的num2的索引
for(int i=n-1;i>=0;i--){
//i:num2需要操作的索引
while(left >= 0){
if(nums1[left] <= nums2[i]){
//此时只需要把num2[i]放入right中right--
nums1[right] = nums2[i];
right--;
break;
}else{
nums1[right] = nums1[left];//把最大的数字移过去
right--;
left--;
}
}
//不是从nums[left] <= num2[i]过来的需要把nums2填入
if(left < 0){
//填充:nums1已经全部移动过去了,现在只需要移动nums2即可
for(;i>=0;i--){
nums1[right] = nums2[i];
right--;
}
}
}
}
}
}
### 每次往后填充最大的数字
每次往后填充最大的数字,使用nums1使用两个指针控制边界,左指针小于0时说明nums1中原有元素填充完毕,直接把nums2剩余元素复制过去即可,当nums1中没有复制完,则使用nums2最大元素与左指针处比较并将较大的填入右指针处,同时右指针减一,左指针根据填充的数组判断需不需要左移。
```java
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
//思想:先探测最大的,然后天道最后
int right = m+n-1;//最大的数字需要填入的地方
int left = m-1;//开始有空缺的位置
int num2R = n-1;//需要操作的num2的索引
for(int i=n-1;i>=0;i--){
//i:num2需要操作的索引
while(left >= 0){
if(nums1[left] <= nums2[i]){
//此时只需要把num2[i]放入right中right--
nums1[right] = nums2[i];
right--;
break;
}else{
nums1[right] = nums1[left];//把最大的数字移过去
right--;
left--;
}
}
//不是从nums[left] <= num2[i]过来的需要把nums2填入
if(left < 0){
//填充:nums1已经全部移动过去了,现在只需要移动nums2即可
for(;i>=0;i--){
nums1[right] = nums2[i];
right--;
}
}
}
}
}