neteast

LRU缓存请你设计并实现一个满足LRU (最近最少使用) 缓存约束的数据结构。
class LRUCache {
    class Node{
        int key;
        int value;
        Node pre;
        Node next;
        public Node(){}
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
    private int size = 0;
    private HashMap<Integer,Node> cache = new HashMap<>();
    private int capacity = 0;
    private Node head,tail;
    public LRUCache(int capacity) {
        this.capacity = capacity;
        head = new Node();
        tail = new Node();
        head.next = tail;
        tail.pre = head;
    }
    
    public int get(int key) {
        Node node = cache.get(key);
        if (node == null) {
            return -1;
        }
        moveToHead(node);
        return node.value;
    }
    private void moveToHead(Node node) {
        removeNode(node);
        addToHead(node);
    }
    private void removeNode(Node node) {
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }
    private void addToHead (Node node) {
        node.pre = head;
        node.next = head.next;
        head.next.pre = node;
        head.next = node;
    }
    public void put(int key, int value) {
        Node node = cache.get(key);
        if (node == null) {
            Node newNode = new Node(key,value);
            cache.put(key,newNode);
            addToHead(newNode);
            size++;
            if (size > capacity) {
                Node tail = removeTail();
                cache.remove(tail.key);
            }
        } else {
            node.value = value;
            moveToHead(node);
        }
    }
    private Node removeTail() {
        Node res = tail.pre;
        removeNode(res);
        return res;//返回要在cache中删除掉node
    }
}
  • 时间复杂度:对于 put 和 get 都是O(1)。
  • 空间复杂度:O(capacity),因为哈希表和双向链表最多存储capacity+1个元素。

在圆内随机生成点

给定圆的半径和圆心的位置,实现函数 randPoint ,在圆中产生均匀随机点。

class Solution {
    double rad, xc, yc;
    public Solution(double radius, double x_center, double y_center) {
        rad = radius;
        xc = x_center;
        yc = y_center;
    }

    public double[] randPoint() {
        double x0 = xc - rad;
        double y0 = yc - rad;

        while(true) {
            double xg = x0 + Math.random() * rad * 2;
            double yg = y0 + Math.random() * rad * 2;
            if (Math.sqrt(Math.pow((xg - xc) , 2) + Math.pow((yg - yc), 2)) <= rad)
                return new double[]{xg, yg};
        }
    }
}	
  • 时间复杂度:期望时间复杂度为O(1),但最坏情况下会达到 O(∞)(一直被拒绝)。
  • 空间复杂度:O(1)。

缺失的第一个正数

给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。

class Solution {
    public int firstMissingPositive(int[] nums) {
        int n = nums.length;
        int res = 0;
        //把所有负数设为n+1,和[1,n]区分开
        for (int i = 0; i < n; i++) {
            if (nums[i] <= 0) {
                nums[i] = n + 1;
            }
        }
      //将存在的整数i用复数打标记
        for (int i = 0; i < n; i++) {
            int cur = Math.abs(nums[i]);
            if (cur <= n) {
                nums[cur-1] = -Math.abs(nums[cur-1]);
            }
        }
      //找到第一个没被负数标记的索引,i+1则是最小正整数
        for (int i = 0; i < n; i++) {
            if (nums[i] > 0)
                return i + 1;
        }
        return n+1;
    }
}
  • 时间复杂度:O(N),其中 N是数组的长度。
  • 空间复杂度:O(1)。

多数元素

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

核心就是对拼消耗。玩一个诸侯争霸的游戏,假设你方人口超过总人口一半以上,并且能保证每个人口出去干仗都能一对一同归于尽。最后还有人活下来的国家就是胜利。

class Solution {
    public int majorityElement(int[] nums) {
        int candidate = 0;
        int count = 0;
        for (int n : nums) {
            if (count == 0)
                candidate = n;
            if (candidate == n) {
                count++;
            } else {
                count--;
            }
        }
        return candidate;
    }
}

时间复杂度:O(n)。Boyer-Moore 算法只对数组进行了一次遍历。

空间复杂度:O(1)。只需要常数级别的额外空间。

IP 到 CIDR

给你一个起始IP地址 ip 和我们需要覆盖的IP地址数量 n 。你的目标是使用 尽可能少的CIDR块 来 覆盖范围 [ip, ip + n - 1] 内的所有IP地址 。不应该覆盖范围之外的其他IP地址。

class Solution {
    public List<String> ipToCIDR(String ip, int n) {
        long start = ipToLong(ip);
        List<String> ans = new ArrayList();
        while (n > 0) {
            int mask = Math.max(33 - bitLength(Long.lowestOneBit(start)),
                                33 - bitLength(n));
            ans.add(longToIP(start) + "/" + mask);
            start += 1 << (32 - mask);
            n -= 1 << (32 - mask);
        }
        return ans;
    }
    private long ipToLong(String ip) {
        long ans = 0;
        for (String x: ip.split("\\.")) {
            ans = 256 * ans + Integer.valueOf(x);
        }
        return ans;
    }
    private String longToIP(long x) {
        return String.format("%s.%s.%s.%s",
            x >> 24, (x >> 16) % 256, (x >> 8) % 256, x % 256);
    }
    private int bitLength(long x) {
        if (x == 0) return 1;
        int ans = 0;
        while (x > 0) {
            x >>= 1;
            ans++;
        }
        return ans;
    }
}
  • 时间复杂度:O(N)。其中 N 表示的是 nums 的长度
  • 空间复杂度:O(1)。

至少有 K 个重复字符的最长子串

class Solution {
    public int longestSubstring(String s, int k) {
        int ret = 0;
        int n = s.length();
        for (int t = 1; t <= 26; t++) {
            int l = 0, r = 0;
            int[] cnt = new int[26];
            int tot = 0;
            int less = 0;
            while (r < n) {
                cnt[s.charAt(r) - 'a']++;
                if (cnt[s.charAt(r) - 'a'] == 1) {
                    tot++;
                    less++;
                }
                if (cnt[s.charAt(r) - 'a'] == k) {
                    less--;
                }

                while (tot > t) {
                    cnt[s.charAt(l) - 'a']--;
                    if (cnt[s.charAt(l) - 'a'] == k - 1) {
                        less++;
                    }
                    if (cnt[s.charAt(l) - 'a'] == 0) {
                        tot--;
                        less--;
                    }
                    l++;
                }
                if (less == 0) {
                    ret = Math.max(ret, r - l + 1);
                }
                r++;
            }
        }
        return ret;
    }
}
  • 时间复杂度:O(N⋅∣Σ∣+∣Σ∣ *∣Σ∣ ),其中 N 为字符串的长度,Σ 为字符集,本题中字符串仅包含小写字母,因此 ∣Σ∣=26。我们需要遍历所有可能的 t,共 ∣Σ∣ 种可能性;内层循环中滑动窗口的复杂度为 O(N),且初始时需要 O(∣Σ∣) 的时间初始化 cnt 数组。
  • 空间复杂度:O(∣Σ∣)。

堆排序 Heapsort

class Solution {
    public void heapSort(int[] nums){
        if(nums==null||nums.length<2){
            return;
        }
        for(int i=0;i<nums.length;i++){
            heapInsert(nums,i);
        }
        int heapSize = nums.length;
        swap(nums,0,--heapSize);
        while(heapSize>0){
            heapify(nums,0,heapSize);
            swap(nums,0,--heapSize);
        }
    }
    //某个数处在index,往上继续移动
    private void heapInsert(int[] nums,int index){
        while(nums[(index-1)/2]<nums[index]){
            swap(nums,(index-1)/2,index);
            index = (index-1)/2;
        }
    }
    private void swap(int[] nums,int left,int right){
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
    }
  	//和左、右节点比较,小于则往下换
    private void heapify(int[] nums,int index,int heapSize){
        int left = index*2+1;//从上往下移动,左孩子下标
        while(left<heapSize){
            int largest=left+1<heapSize&&nums[left+1]>nums[left]?left+1:left;
            largest=nums[largest]>nums[index]?largest:index;
            if(largest==index){
                break;
            }
            swap(nums,largest,index);
            index = largest;
            left=index*2+1;
        }
    }
}

数组中的第K个最大元素 快排 quicksort

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

class Solution {
    public int findKthLargest(int[] nums, int k) {
        quickSort(nums,0,nums.length-1);
        return nums[nums.length-k];
    }
    private void quickSort(int[] nums,int left,int right){
        if(left<right){
            swap(nums,(int)(left+Math.random()*(right-left+1)),right);
            int[] res = partition(nums,left,right);
            quickSort(nums,left,res[0]-1);
            quickSort(nums,res[1]+1,right);
        }
    }
    private void swap(int[] nums,int left,int right){
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
    }
    private int[] partition(int[] nums,int left,int right){
        int less = left-1; //小于区域
        int more = right;  //大于区域
        while(left<more){
            if(nums[left]<nums[right]){
                swap(nums,++less,left++);
            }else if(nums[left]>nums[right]){
                swap(nums,--more,left);
            }else{
                left++;
            }
        }
        swap(nums,more,right);
        return new int[]{less+1,more};//返回相等区域
    }
}