手撕leeCode中等难度算法

385 阅读4分钟


1.滑动窗口

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串

示例:

输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"

    public void gethuaDong() {
        String s = "ADOBECODEBANC";
        String t = "ABC";

        Map<Character, Integer> needMap = new HashMap<>();
        Map<Character, Integer> windowMap = new HashMap<>();

        //初始needMap  符合条件为[left, right)里面的数据即是要的数据
        for (int i = 0; i < t.length(); i++) {
            Integer num = needMap.getOrDefault(t.charAt(i), 0);
            needMap.put(t.charAt(i), ++num);
        }

        int left = 0;
        int right = 0;
        int startLen = 0;//最短字符串的起始位置
        int minLen = 0;//最短字符串的长度
        int validNum = 0;//目前已经符合need中的元素个数

        //移动right,并更新windowMap数据
        while (right < s.length()) {
            Character srcStr = s.charAt(right);
            Integer targetNum = needMap.get(srcStr);


            //如果是符合needMap的才统计入windowMap窗口
            if (targetNum != null) {
                Integer srcNum = windowMap.getOrDefault(srcStr, 0);
                windowMap.put(srcStr, ++srcNum);
                if (srcNum.equals(targetNum)) {
                    validNum++;
                }
            }
            //设置好windowMap就移动right
            right++;

            //移动left,并更新数据(windowMap,最小字符串的位置)
            Boolean tempFlag = false;
            while (validNum == needMap.size()) {
                Character srcStr2 = s.charAt(left);
                Integer targetNum2 = needMap.get(srcStr2);

                if (targetNum2 != null) {
                    Integer srcNum2 = windowMap.get(srcStr2);
                    //如果srcNum2是0则不正常,要异常处理

                    srcNum2 -= 1;
                    windowMap.put(srcStr2, srcNum2);

                    if (srcNum2 < targetNum2) {
                        validNum--;
                    }
                }
                tempFlag = true;
                left++;
            }

            //更新最短数据
            if (tempFlag) {
                //因为是算长度,所以加一
                int len = right - left + 1;
                if (len > 0 && (len < minLen || minLen == 0)) {
                    minLen = len;
                    startLen = left - 1;
                }
            }

        }
        System.out.println(s.substring(startLen, startLen + minLen));

    }



2.LRU算法

运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。 获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。 写入数据 put(key, value) - 如果密钥已经存在,则变更其数据值;如果密钥不存在,则插入该组「密钥/数据值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间

方法一:LinkedList+Map 实现

class LRUCache {
    Map<Integer, Integer> lruMap =null;
    LinkedList<Integer> deque = null;
    int capacity ;
    public LRUCache(int capacity) {
    lruMap = new HashMap<>(capacity+1);
    deque = new LinkedList<>();
    this.capacity = capacity;
    }
    
    public int get(int key) {
        Integer integer = lruMap.get(key);

        if (integer == null) {
            return -1;
        } else {
            deque.removeFirstOccurrence(key);
            deque.addFirst(key);
            return integer;
        }
    }
    
    public void put(int key, int value) {
        Integer integer = lruMap.put(key, value);
        if (integer != null) {
            deque.removeFirstOccurrence(key);
        }
        deque.addFirst(key);

        //因为有可能put了两次1,实际size并没有增加,所以放后面判断
        if (this.lruMap.size() > this.capacity) {
            this.lruMap.remove(deque.removeLast());
        }
    }
}

方法二:自实现双向链表+Map 实现

这个方法查询性能比方法一好,因为不需要链表再去找某个节点然后删除
public class LRUCache {

    private int size;
    private HashMap<Integer, Node> map;
    private Node head;
    private Node tail;

    LRUCache(int size) {
        this.size = size;
        map = new HashMap<>();
    }

    /**
     * 添加元素
     * 1.元素存在,将元素移动到队尾
     * 2.不存在,判断链表是否满。
     * 如果满,则删除队首元素,放入队尾元素,删除更新哈希表
     * 如果不满,放入队尾元素,更新哈希表
     */
    public void put(int key, int value) {
        Node node = map.get(key);
        if (node != null) {
            //更新值
            node.v = value;
            moveNodeToTail(node);
        } else {
            Node newNode = new Node(key, value);
            //链表满,需要删除首节点
            if (map.size() == size) {
                Node delHead = removeHead();
                map.remove(delHead.k);
            }
            addLast(newNode);
            map.put(key, newNode);
        }
    }

    public int get(int key) {
        Node node = map.get(key);
        if (node != null) {
            moveNodeToTail(node);
            return node.v;
        }
        return -1;
    }

    public void addLast(Node newNode) {
        if (newNode == null) {
            return;
        }
        if (head == null) {
            head = newNode;
            tail = newNode;
        } else {
            //连接新节点
            tail.next = newNode;
            newNode.pre = tail;
            //更新尾节点指针为新节点
            tail = newNode;
        }
    }

    public void moveNodeToTail(Node node) {
        if (tail == node) {
            return;
        }
        if (head == node) {
            head = node.next;
            head.pre = null;
        } else {
            //调整双向链表指针
            node.pre.next = node.next;
            node.next.pre = node.pre;
        }
        node.pre = tail;
        node.next = null;
        tail.next = node;
        tail = node;
    }

    public Node removeHead() {
        if (head == null) {
            return null;
        }
        Node res = head;
        if (head == tail) {
            head = null;
            tail = null;
        } else {
            head = res.next;
            head.pre = null;
            res.next = null;
        }
        return res;
    }

    class Node {
        int k;
        int v;
        Node pre;
        Node next;

        Node(int k, int v) {
            this.k = k;
            this.v = v;
        }
    }

}

方法三 用LinkedHashMap

public class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> {

    private int capacity;

    LRULinkedHashMap(int capacity) {
        // 初始大小,0.75是装载因子,true是表示按照访问时间排序
        super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
        //传入指定的缓存最大容量
        this.capacity = capacity;
    }

    /**
     * 实现LRU的关键方法,如果map里面的元素个数大于了缓存最大容量,则删除链表的顶端元素
     
     removeEldestEntry默认是返回false,不删除元素的
     */
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }
}



3.二分结合抽屉

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

解法1:

    public static int demo() {

        int[] nums = new int[]{1, 6, 6, 4, 3, 5, 2};

        //数值的范围是只能是 [left, right]
        int left = 1;
        int right = nums.length - 1;

        while (left < right) {
            int countNum = 0;

            //>>>是为了防止负数的出现
            int halfNum = (left + right) >>> 1;
            for (int i : nums) {
                if (i <= halfNum) {
                    countNum++;
                }
            }

            //半分查找,这里已经有天然的自然排序就是: 抽屉[left, right]
            if (countNum <= halfNum) {
                left = halfNum + 1;
            } else {
                right = halfNum;
            }
        }

        return left;
    }