02高频题目

文章目录

合并两个有序链表

剑指 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

设计一个支持 pushpoptop 操作,并能在常数时间内检索到最小元素的栈。

  • 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)缓存算法设计并实现数据结构。它应该支持以下操作:getput

  • get(key) - 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1。
  • put(key, value) - 如果键已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量时,则应该在插入新项之前,使最不经常使用的项无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除最久未使用的键。

「项的使用次数」就是自插入该项以来对其调用 getput 函数的次数之和。使用次数会在对应项被移除后置为 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 的正序(从小到大)数组 nums1nums2

请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

你可以假设 nums1nums2 不会同时为空。

示例 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

给你两个有序整数数组 nums1nums2,请你将 nums2 合并到 nums1 中*,*使 nums1 成为一个有序数组。

说明:

  • 初始化 nums1nums2 的元素数量分别为 mn
  • 你可以假设 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--;
                }
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值