CodeTop-LeetCode-1-5

3. 无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。

思路:

  1. 使用滑动窗口表示子串区间,要保证窗口中没有重复元素
  2. 借助一个map记录已经出现过的元素和其位置。初始化窗口边界,右边界向后遍历并记录达到的最长窗口长度,当遇到重复元素,则把左边界移动到重复元素上一次出现位置之后(即跳过旧的重复元素)

代码:

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        Map<Character, Integer> map = new HashMap<>();
        for(int l = 0, r = 0; r < n; r++){
            char c = s.charAt(r);
            if(map.containsKey(c)){
                //因为l表示不重复的第一个元素,所以是当前重复元素加1
                //且,这里需要使得l为满足窗口内不重复的最右侧值
                //如果只取当前重复值的下一个位置
                //可能这个重复元素是很早(左侧)的元素
                //而现在的l已经因为其他元素重复在后边了
                //所以要取下标最大即最右侧的值
                l = Math.max(map.get(c) + 1, l);
            }
            ans = Math.max(ans, r - l + 1);
            map.put(c, r);
        }
        return ans;
    }
}

146. LRU缓存机制

请你设计并实现一个满足  LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

  • LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

思路:

  1. 当需要淘汰数据的时候,选择最久没有被使用的数据淘汰,刚刚使用的数据不要淘汰
  2. 要求get和put的复杂度为O(1)
  3. 通过map根据key定位,找到位置后,移动到双向链表的头部,就可以在O(1)时间完成
  4. 为什么用双向链表
    1. 因为将某个节点移动到链表头部或者将链表尾部节点删去,都要用到删除链表中某个节点这个操作。从map中得到节点后,如果要在链表里删除,如果是单链表,则需要知道链表的前一个节点(从头遍历需要O(n)),而双向链表可以快速删除元素。
  5. 注意添加虚拟节点辅助,哨兵节点
  6. 为什么要在链表节点里存储key和value,而不是仅仅只存储 value
    1. 因为删去最近最少使用的键值对时,要删除链表的尾节点并且要删除map中的key-value,而如果尾节点没有存储key,那么就找不到要在map中删除哪个key,因此节点必须存储key

代码:

class LRUCache {
    //定义节点类
    private static class Node{
        int key, value;
        Node pre, next;

        Node(int k, int v){
            key = k;
            value = v;
        }
    }

    //设定的缓存空间
    private final int capacity;
    //哨兵节点
    private final Node dummy = new Node(0, 0);
    //存放元素的map
    private final Map<Integer, Node> keyToNode = new HashMap<>();

    public LRUCache(int capacity) {
        this.capacity = capacity;
        dummy.pre = dummy;
        dummy.next = dummy;
    }
    
    public int get(int key) {
        //定义getNode方法,获取值并移动到链表头部
        Node node = getNode(key);
        //没找到返回-1
        return node != null ? node.value : -1;
    }
    
    public void put(int key, int value) {
        //
        Node node = getNode(key);
        //本来就有这个元素
        if(node != null){
            node.value = value; //更新值
            return ;
        }
        //新元素
        node = new Node(key, value);
        keyToNode.put(key, node);
        pushFront(node); //定义方法,放到最前
        if(keyToNode.size() > capacity){
            //找到尾部的元素
            Node backNode = dummy.pre;
            keyToNode.remove(backNode.key); //在map里删除
            remove(backNode); // 在链表里删除
        }
    }

    //获取对应的节点,同时把该节点放到链表头部
    private Node getNode(int key){
        //没有这个节点返回null
        if(!keyToNode.containsKey(key)){
            return null;
        }
        //找到节点
        Node node = keyToNode.get(key);
        remove(node);//从链表中移除
        pushFront(node); //重新添加到链表头部
        return node;
    }

    //从链表中移除节点
    private void remove(Node node){
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }

    //在链表头部添加一个节点
    private void pushFront(Node node){
        node.pre = dummy;
        node.next = dummy.next;
        dummy.next = node;
        //这里不要忘了原来的头节点指向新节点
        node.next.pre = node; 
    }
}

206. 反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

思路:

  1. 采用迭代的方法通过两个指针和一个临时指针操作节点

代码:

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre = null;  //记录已经反转的点
        ListNode cur = head;  //记录当前要反转的点
        ListNode nxt = null;  //记录下一次反转的点
        while(cur != null){
            nxt = cur.next;
            cur.next = pre;
            pre = cur;
            cur = nxt;
        }
        return pre;
    }
}

215. 数组中的第K个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 **k** 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

思路:

  1. 借助快排的思想,每次选择一个元素,根据大小划分区间(这里实际可以分为:小、相等、大,三个区间)。根据划分的区间的长度,我们可以知道第k大的元素会出现在哪个区间中。再对这个目标区间递归处理,当第K大的元素判断为再相等区间时,就找到他的值了

代码:

class Solution {
    public int findKthLargest(int[] nums, int k) {
        List<Integer> numList = new ArrayList<>();
        for(int num : nums){
            numList.add(num);
        }
        return quickSelect(numList, k);
    }

    //快速选择
    //使用两个列表版本
    private int quickSelect(List<Integer> nums, int k){
        //随机选择基准数
        Random rand = new Random();
        //Random.nextInt方法用于生成一个随机的整数值。
        //这个值位于0(包含)和指定值n(不包含)之间的范围内
        int pivot = nums.get(rand.nextInt(nums.size()));
        //两个列表存放划分的元素
        List<Integer> big = new ArrayList<>();
        List<Integer> small = new ArrayList<>();
        //每个元素和pivot的大小比较
        for(int num : nums){
            if(num > pivot) big.add(num);
            else if(num < pivot) small.add(num);
        }
        //第k大的元素在big中,对big列表划分
        if(k <= big.size())
            return quickSelect(big, k);
        //第k大的元素在small中,对small列表划分
        if(nums.size() - small.size() < k)
            return quickSelect(small, k - nums.size() + small.size());
        //第k大的元素在相等的元素中中,返回
        return pivot;
    }
}

15. 三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组

思路:

  1. 先固定一个值,另外两个值的和通过双指针处理
  2. 参考两数之和,把数组排序,然后使用相向双指针,逐渐缩小范围
  3. 注意需要对重复元素去重,保证三元组不重复。由于排序后的相同元素挨在一起,直接跳过相同元素就行
  4. 额外的,可以根据固定的值和最大两个值或最小两个值的和,提前判断循环结果进行优化

代码:

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        //数组有序,就可以使用相向双指针了
        //参考两数之和,固定一个值,根据最大值和最小值加起来的结果判断,需要往哪边找
        //排序
        Arrays.sort(nums);
        List<List<Integer>> res = new ArrayList<>();
        int n = nums.length;
        //由于是三数之和,第一个数只需要遍历到倒数第三个就可以,所以n-2
        for(int i = 0; i < n - 2; i++){
            int x = nums[i];
            //为了保证没有重复三元组,跳过重复的数字
            if(i > 0 && x == nums[i - 1]) continue;
            //如果最小的x和相邻两个最小的值的和已经超出0,则直接结束返回空
            if(x + nums[i + 1] + nums[i + 2] > 0) break;
            //如果最小的x和最后两个最大的值的和还不够0,则跳过这个x,检查下一个
            if(x + nums[n - 1] + nums[n - 2] < 0) continue;
            //更新另外两个相向指针
            int j = i + 1;
            int k = n - 1;
            while(j < k){
                int s = x + nums[j] + nums[k];
                if(s > 0){
                    k--;
                } else if (s < 0){
                    j++;
                } else{
                    //和为0,找到一个三元组
                    res.add(List.of(x, nums[j], nums[k]));
                    //找到后更新指针,避免有相同元素导致元组重复
                    //跳过重复元素
                    j++;
                    while(j < k && nums[j] == nums[j - 1])
                        j++;
                    k--;
                    while(k > j && nums[k] == nums[k + 1])
                        k--;
                }
            }
        }
        return res;
    }
}
posted @ 2025-06-03 23:09  xloading  阅读(9)  评论(0)    收藏  举报