LeetCode100

47 阅读7分钟

哈希

两数之和

题目: 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

解析: 最简单的方式是通过嵌套循环的方式将这两个数找出来,但这样会带来一个问题,数组特别大的时候,时间复杂度较高,会造成内存占用过高,资源耗尽等问题,所以这不是一个很好的方案。

这里使用哈希表的方式来解决这个问题,解决思路是创建一个哈希表,判断哈希表中是否有 target - num 的值,有则将两个数的索引返回,没有则将这个数及其对应的索引值放入哈希表中。代码如下:

public int[] twoSum(int[] nums, int target) {
    // 使用哈希表存储数组元素及其索引
    HashMap<Integer, Integer> hashMap = new HashMap<>();
    // 遍历数组
    for (int i = 0; i < nums.length; i++) {
        // 检查哈希表中是否存在 target - nums[i]
        if (hashMap.containsKey(target - nums[i])){
            // 如果存在,返回对应的索引
            return new int[]{hashMap.get(target - nums[i]),i};
        }// 将当前元素及其索引存入哈希表
        hashMap.put(nums[i],i);
    }
    // 如果没有找到,返回空数组
    return new int[0];
}

字母异位词

题目: 给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。

解析: 通过创建一个哈希表,以字母位键,将不同组合的单词存储到list中,作为哈希表的值。代码如下:

public List<List<String>> groupAnagrams(String[] strs) {
    HashMap<String, List<String>> map = new HashMap<>();
    for (String str : strs) {
        char[] array = str.toCharArray();
        Arrays.sort(array);
        String key = new String(array);
        List<String> list = map.getOrDefault(key, new ArrayList<String>());
        list.add(str);
        map.put(key,list);
    }
    return new ArrayList<List<String>>(map.values());
}

最长连续序列

题目: 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

解析: 通过将数组中的数保存在hashset中,遍历nums数组,查看是否有比当前数字小1的数,有则说明这是一个序列的起点,目前这个序列长度为2。将当前序列长度设置为1,遍历是否有比当前数组大1的数,有则数字自增1,当前序列长度自增1。直到没有为止,然后保存最长的序列数。直到遍历完数组,将最长的序列返回。

public int longestConsecutive(int[] nums) {
    Set<Integer> num_set = new HashSet<Integer>(); // 使用 HashSet 存储数组中的数字
    for (int num : nums) {
        num_set.add(num); // 将所有数字加入 HashSet
    }

    int longestStreak = 0; // 用于记录最长连续子序列的长度

    for (int num : num_set) {
        if (!num_set.contains(num - 1)) { // 如果当前数字是连续序列的起点(即 num - 1 不在集合中)
            int currentNum = num; // 当前数字
            int currentStreak = 1; // 当前连续子序列的长度初始化为 1

            while (num_set.contains(currentNum + 1)) { // 检查当前数字的后继是否在集合中
                currentNum += 1; // 更新当前数字
                currentStreak += 1; // 更新当前连续子序列的长度
            }

            longestStreak = Math.max(longestStreak, currentStreak); // 更新最长连续子序列的长度
        }
    }

    return longestStreak; // 返回最长连续子序列的长度
}

双指针

移动零

题目: 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意: 必须在不复制数组的情况下原地对数组进行操作。

解析: 定义2个指针,遍历数组,一个指针用于指向非零数字,另一个指针遍历数组,遇到0,就将两个指针指向的数字做交换。代码如下:

public void moveZeroes(int[] nums) {
    int n = nums.length, left = 0, right = 0;
    while (right < n) {
        if (nums[right] != 0) {
            swap(nums, left, right);
            left++;
        }
        right++;
    }
}

public void swap(int[] nums, int left, int right) {
    int temp = nums[left];
    nums[left] = nums[right];
    nums[right] = temp;
}

盛水容器

题目: 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。

解析: 定义两个指针,分别从左右两头交替往中间遍历,哪个数字小下次就往中间移动,记录每次移动后形成的当前面积大小,与最大面积进行比较并保存下来。代码如下:


public int maxArea(int[] height) {
    int first = 0;
    int last = height.length - 1;
    int maxArea = 0;
    while (first < last) {
        int currentArea = height[first] > height[last] ? (last - first) * height[last] : (last - first) * height[first];
        maxArea = maxArea > currentArea ? maxArea : currentArea;
        if (height[first] > height[last]) {
            last--;
        } else {
            first++;
        }
    }
    return maxArea;
}

三数之和

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

解析: 先对数组进行从小到大排序,定义三个指针,在一个指针不动的前提下,用剩余两个指针遍历数组,找到三个数相加等于零的集合。代码如下:

public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> result = new ArrayList<>();
    // 首先对数组进行排序
    Arrays.sort(nums);
    // 遍历第一个数
    for (int first = 0; first < nums.length - 2; first++) {
        // 第一个数大于0,后面不可能有解
        if (nums[first] > 0) break;
        // 跳过重复的first
        if (first > 0 && nums[first] == nums[first - 1]) continue;
        int second = first + 1;
        int third = nums.length - 1;
        while (second < third) {
            int sum = nums[first] + nums[second] + nums[third];
            if (sum < 0) {
                second++;
            } else if (sum > 0) {
                third--;
            } else {
                result.add(Arrays.asList(nums[first], nums[second], nums[third]));
                // 跳过重复的second和third元素
                while (second < third && nums[second] == nums[second + 1]) second++;
                while (second < third && nums[third] == nums[third - 1]) third--;
                second++;
                third--;
            }
        }
    }
    return result;
}

接雨水

题目: 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

解析: 代码如下:

public int trap(int[] height) {
    int ans = 0;
    int left = 0, right = height.length - 1;
    int leftMax = 0, rightMax = 0;
    while (left < right) {
        leftMax = Math.max(leftMax, height[left]);
        rightMax = Math.max(rightMax, height[right]);
        if (height[left] < height[right]) {
            ans += leftMax - height[left];
            ++left;
        } else {
            ans += rightMax - height[right];
            --right;
        }
    }
    return ans;
}

链表

相交链表

题目: 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

解析: 遍历headA将其所有节点放入hashset中,通过遍历headB,查看其节点是否存在于hashset中,存在则返回该节点。


public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    HashSet<ListNode> listNodes = new HashSet<>();
    ListNode temp = headA;
    while (temp!=null){
        listNodes.add(temp);
        temp = temp.next;
    }
    temp = headB;
    while (temp != null){
        if (listNodes.contains(temp)){
            return temp;
        }
        temp = temp.next;
    }
    return null;
}

环形链表

题目: 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 解析: 思路如相交链表。

public ListNode detectCycle(ListNode head) {
                ListNode pos = head;
        HashSet<ListNode> listNodes = new HashSet<>();
        while (pos!=null){
            if (listNodes.contains(pos)){
                return pos;
            }else {
                listNodes.add(pos);
            }
            pos = pos.next;
        }
        return null;
    }

删除链表的倒数第 N 个结点

题目: 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

解析: 这里提供两个思路:1.先循环遍历获取链表的长度,再根据链表长度和删除的节点,将当前指针指向删除节点的前一个节点(比如:长度为5,删除倒数第2个节点,则当前指针指向第三个节点),最后将当前节点的下一个节点指向下一个节点的下一个节点,返回链表,代码如下1 。2.使用快慢指针的方式,先将快指针移动 n次,再快慢指针同时遍历链表,直到快指针指向null,将慢指针的下一个节点指向下一个节点的下一个节点,代码如下2 。

// 方式1
public ListNode removeNthFromEnd(ListNode head, int n) {
    //新建一个带头节点的链表
    ListNode dummy = new ListNode(0,head);
    //记录链表的长度
    int length = 0;
    // 当前指针指向待头节点的链表
    ListNode cur = dummy;
    while (head != null){
        length++;
        head = head.next;
    }

    for (int i = 1; i <= length -n; i++) {
        cur = cur.next;
    }
    cur.next = cur.next.next;
    ListNode ans = dummy.next;
    return ans;
}

// 方式2
public ListNode removeNthFromEnd2(ListNode head, int n) {
    //新建一个带头节点的链表
    ListNode dummy = new ListNode(0, head);
    ListNode fast = dummy;
    ListNode slow = dummy;
    while (n>0 && fast != null) {
        fast = fast.next;
        n--;
    }
    while (fast.next != null) {
        fast = fast.next;
        slow = slow.next;
    }
    slow.next = slow.next.next;
    ListNode ans = dummy.next;
    return ans;
}