leetcode100 142-279

Code 142:环形链表 II

描述:判断链表是否有环,有则返回入环节点,无则返回空。

思路:这个题目其实是利用快慢指针的的遍历优势,如果链表是的无环,那么链表在遍历过程中,永远不会走空节点,如果有环,那么在这么遍历过程中,快指针与慢指针一定是会相遇的,那么就可以在遍历的过程中,根据快指针的进行判断,当快指针为空的时候,说明整个链表是无环的,如果不为空,说明链表一定有环,在这种情况下,有环链表,就在相遇的时候,让快指针或者慢指针回到头结点,一步一步的遍历下去,相遇的时候,这个节点就是入环的第一个节点

Java

public ListNode detectCycle(ListNode head) {
    // 快慢指针
    if (head == null || head.next == null) {
        return null;
    }
    ListNode slow = head.next;
    ListNode fast = head.next.next;
    while (slow != fast) {
        slow = slow.next;
        if (fast == null || fast.next == null) {
            return null;
        }
        fast = fast.next.next;
    }
    // 能跳出来说明有环
    slow = head;
    while (slow != fast) {
        slow = slow.next;
        fast = fast.next;
    }
    return slow;
}

Code 148:排序链表

描述:利用归并排序实现空间复杂度 O(1)(非严格,还有递归的空间消耗)的做法,这里找中点,其实找上中点或者下中点都无所谓,主要是能将链表的进行拆分重建,利用归并排序的特性,将链表重组成有序的链表

Java

public ListNode sortList(ListNode head) {
    if (head == null || head.next == null) return head;
    return process(head, null);
}

// 利用归并排序的特性 将整个链表修改成有序的链表
// 这个函数的含义是 在链表的范围内进行归并排序 并且返回经过归并排序的之后的新的头结点
public ListNode process(ListNode head, ListNode tail) {
    if (head == tail || head.next == null) {
        return head;
    }
    // 找到中点 从head到tail的终点 这点是上中点或者是下中点不是很重要
    ListNode slow = head;
    ListNode fast = head.next.next;
    // 快慢指针 快指针一次走两步 慢指针一次走一步 知道快指针来到了最后一个位置
    while (fast != tail) {
        slow = slow.next;
        if (fast == null || fast.next == null) {
            break;
        }
        fast = fast.next.next;
    }
    // 中点
    // 切断左右两段链表
    ListNode mid = slow.next;
    slow.next = null;
    // 利用归并排序的特性对链表进行排序
    ListNode l = process(head, null);
    ListNode r = process(mid, null);
    // 合并左右两侧链表
    return merge(l, r);
}

public ListNode merge(ListNode head1, ListNode head2) {
    ListNode dummyHead = new ListNode(0);
    ListNode cur = dummyHead;
    while (head1 != null && head2 != null) {
        if (head1.val <= head2.val) {
            cur.next = head1;
            head1 = head1.next;
        } else {
            cur.next = head2;
            head2 = head2.next;
        }
        cur = cur.next;
    }
    if (head1 != null) {
        cur.next = head1;
    }
    if (head2 != null) {
        cur.next = head2;
    }

    return dummyHead.next;
}

Code 152:乘积最大子数组

关联标注:思路参考了 Code 053 (和最大的子数组),即以当前位置作为开头/结尾的局部最优思想。

Java

// 思路1:暴力枚举
public int maxProduct(int[] nums) {
    // 必须以i位置开头
    int n = nums.length;
    int ans = nums[0];

    for (int i = n - 1; i >= 0; i--) {
        int curMul = nums[i];
        int curMax = curMul;
        for (int j = i + 1; j < n; j++) {
            curMul *= nums[j];
            if (curMul > curMax) {
                curMax = curMul;
            }
        }
        ans = Math.max(ans, curMax);
    }

    return ans;
}

// 思路2:使用辅助数组
public int maxProduct2(int[] nums) {
    int n = nums.length;
    int[] max = new int[n];
    int[] min = new int[n];
    max[n - 1] = nums[n - 1];
    min[n - 1] = nums[n - 1];
    for (int i = n - 2; i >= 0; i--) {
        max[i] = Math.max(nums[i], Math.max(min[i + 1] * nums[i], max[i + 1] * nums[i]));
        min[i] = Math.min(nums[i], Math.min(min[i + 1] * nums[i], max[i + 1] * nums[i]));
    }
    int ans = Integer.MIN_VALUE;
    for (int i = 0; i < n; i++) {
        if (max[i] > ans) {
            ans = max[i];
        }
    }
    return ans;
}

// 思路3:减少一次遍历
public int maxProduct3(int[] nums) {
    int n = nums.length;
    int[] max = new int[n];
    int[] min = new int[n];
    max[n - 1] = nums[n - 1];
    min[n - 1] = nums[n - 1];
    int ans = nums[n-1];
    for (int i = n - 2; i >= 0; i--) {
        max[i] = Math.max(nums[i], Math.max(min[i + 1] * nums[i], max[i + 1] * nums[i]));
        min[i] = Math.min(nums[i], Math.min(min[i + 1] * nums[i], max[i + 1] * nums[i]));
        ans = Math.max(ans, max[i]);
    }
    return ans;
}

// 思路4:压缩空间做法
public int maxProduct4(int[] nums) {
    int n = nums.length;
    int max = nums[n - 1], min = nums[n - 1];
    int ans = nums[n - 1];
    for (int i = n - 2; i >= 0; i--) {
        int mx = max;
        max = Math.max(nums[i], Math.max(max * nums[i], min * nums[i]));
        min = Math.min(nums[i], Math.min(mx * nums[i], min * nums[i]));
        ans = Math.max(ans, max);
    }
    return ans;
}

Code 153:寻找旋转排序数组中的最小值

描述:要求 O(log n)时间复杂度。

Java

// 方法1:与最后一个数 nums[n-1] 比较
public int findMin(int[] nums) {
    int n = nums.length;
    int left = 0;
    int right = n - 1; 
    while (left <= right) { 
        int mid = (left + right) >>> 1;
        if (nums[mid] <= nums[n - 1]) {
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    return nums[left];
}

// 方法2:与第一个数 nums[0] 比较
public int findMin2(int[] nums) {
    int n = nums.length;
    int left = 0;
    int num = nums[0];
    int right = n - 1;
    while (left <= right) {
        int mid = (left + right) >>> 1;
        if (nums[mid] >= num) {
            left = mid + 1;  
        } else {
            right = mid - 1; 
        }
    }
    return left == n ? nums[0] : nums[left];
}

// 方法3:与 nums[0] 比较并提前剔除单调递增场景
public int findMin3(int[] nums) {
    int n = nums.length;
    int left = 0;
    int num = nums[0];
    int right = n - 1;
    if(nums[right] >= nums[left]) {
        return nums[left];
    }
    while(left <= right) {
        int mid =  left + ((right - left) >> 1);
        if(nums[mid] >= num) {
            left = mid +1;
        }else{
            right = mid - 1;
        }
    }
    return nums[left];
}

Code 160:相交链表

描述:寻找两个链表相交的起始节点。

Java

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    int n1 = 0;
    ListNode cur1 = headA;
    while (cur1 != null) {
        n1++;
        cur1 = cur1.next;
    }
    ListNode cur2 = headB;
    while (cur2 != null) {
        n1--;
        cur2 = cur2.next;
    }
    if (n1 > 0) {
        cur1 = headA;
        cur2 = headB;
    } else {
        cur1 = headB;
        cur2 = headA;
    }
    n1 = Math.abs(n1);
    while (n1-- > 0) {
        cur1 = cur1.next;
    }
    while (cur1 != null && cur2 != null) {
        if (cur1 == cur2) {
            return cur1;
        }
        cur1 = cur1.next;
        cur2 = cur2.next;
    }
    return null;
}

Code 169:多数元素

描述:寻找数组中出现次数大于 n/2 的元素。

Java

// 思路1:哈希表统计
public int majorityElement1(int[] nums) {
    Map<Integer, Integer> map = new java.util.HashMap<>();
    for (int i = 0; i < nums.length; i++) {
        map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
        if (map.get(nums[i]) > nums.length / 2) {
            return nums[i];
        }
    }
    return -1;
}

// 思路2:排序法
public int majorityElement2(int[] nums) {
    java.util.Arrays.sort(nums);
    return nums[nums.length / 2]; 
}

// 思路3:摩尔投票法 (Boyer-Moore)
public int majorityElement3(int[] nums) {
    int times = 0;
    int ans = 0;
    for (int i = 0; i < nums.length; i++) {
        if (times == 0) {
            ans = nums[i];
            times++;
        } else if (nums[i] == ans) {
            times++;
        } else if (nums[i] != ans) {
            times--;
        }
    }
    return ans;
}

Code 189:轮转数组

描述:将数组中的元素向右轮转 k个位置。

Java

// 思路1:直接计算位置 + 辅助哈希表
public void rotate(int[] nums, int k) {
    int N = nums.length;
    Map<Integer, Integer> map = new HashMap<Integer, Integer>();
    for (int i = 0; i < N; i++) {
        map.put((i + k) % N, nums[i]);
    }
    for (int i = 0; i < N; i++) {
        nums[i] = map.get(i);
    }
}

// 思路2:三次翻转法
public void rotate2(int[] nums, int k) {
    int n = nums.length;
    k = (k % n);
    // 整体翻转
    reverse(nums, 0, n - 1);
    // 0-k-1进行翻转
    reverse(nums, 0, k - 1);
    // k -n-1进行翻转
    reverse(nums, k, n - 1);
}

public void reverse(int[] nums, int left, int right) {
    while (left < right) {
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
        left++;
        right--;
    }
}

Code 198:打家劫舍

描述:计算在不能偷窃相邻房屋的情况下,能够偷窃到的最高金额。

Java

// 方法1:暴力递归
public int rob(int[] nums) {
    return process(nums, nums.length, 0);
}

public int process(int[] nums, int n, int index) {
    if (index >= n) {
        return 0;
    }
    int p1 = process(nums, n, index + 1);
    int p2 = nums[index] + process(nums, n, index + 2);
    return Math.max(p1, p2);
}

// 方法2:动态规划
public int rob2(int[] nums) {
    int N = nums.length;
    int[] dp = new int[N + 1];
    for (int i = N - 1; i >= 0; i--) {
        int p1 = dp[i + 1];
        int p2 = nums[i] + ((i + 2 > N) ? 0 : dp[i + 2]);
        dp[i] = Math.max(p1, p2);
    }
    return dp[0];
}

// 方法3:空间压缩
public int rob3(int[] nums) {
    int N = nums.length;
    if (N == 0) return 0;
    int cur = nums[N - 1];
    int pre = 0;
    for (int i = N - 2; i >= 0; i--) {
        int temp = cur;
        cur = Math.max(pre + nums[i], cur);
        pre = temp;
    }
    return cur;
}

Code 200:岛屿数量

描述:计算网格中岛屿的数量('1'代表陆地,'0'代表水)。

Java

// 方法1:暴力递归 (感染法)
public int numIslands(char[][] grid) {
    if (grid == null || grid.length == 0 || grid[0].length == 0)
        return 0;
    int res = 0;
    for (int i = 0; i < grid.length; i++) {
        for (int j = 0; j < grid[0].length; j++) {
            if (grid[i][j] == '1') {
                res++;
                affect(grid, i, j);
            }
        }
    }
    return res;
}

private void affect(char[][] grid, int x, int y) {
    if (x < 0 || x >= grid.length || y < 0 || y >= grid[0].length)
        return;
    if (grid[x][y] == '1') {
        grid[x][y] = 2; // 标记已访问
        affect(grid, x - 1, y);
        affect(grid, x + 1, y);
        affect(grid, x, y - 1);
        affect(grid, x, y + 1);
    }
}

// 方法2:并查集
public int numIslands2(char[][] grid) {
    UnionSet unionSet = new UnionSet(grid);
    int row = grid.length;
    int col = grid[0].length;
    for (int i = 1; i < col; i++) {
        if (grid[0][i] == '1' && grid[0][i - 1] == '1') {
            unionSet.union(0, i, 0, i - 1);
        }
    }
    for (int i = 1; i < row; i++) {
        if (grid[i][0] == '1' && grid[i - 1][0] == '1') {
            unionSet.union(i, 0, i - 1, 0);
        }
    }
    for (int i = 1; i < row; i++) {
        for (int j = 1; j < col; j++) {
            if (grid[i][j] == '1') {
                if (grid[i - 1][j] == '1') unionSet.union(i, j, i - 1, j);
                if (grid[i][j - 1] == '1') unionSet.union(i, j, i, j - 1);
            }
        }
    }
    return unionSet.size;
}

class UnionSet {
    int col, size, limit;
    Map<Integer, Integer> fatherMap;
    Map<Integer, Integer> sizeMap;

    UnionSet(char[][] grid) {
        this.col = grid[0].length;
        fatherMap = new HashMap<>();
        sizeMap = new HashMap<>();
        size = 0;
        limit = grid.length * grid[0].length;
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                if (grid[i][j] == '1') {
                    int index = i * col + j;
                    fatherMap.put(index, index);
                    sizeMap.put(index, 1);
                    size += 1;
                }
            }
        }
    }

    public void union(int i1, int j1, int i2, int j2) {
        int f1 = findFather(i1, j1), f2 = findFather(i2, j2);
        if (f1 != f2) {
            if (sizeMap.get(f1) >= sizeMap.get(f2)) {
                sizeMap.put(f1, sizeMap.get(f1) + sizeMap.get(f2));
                fatherMap.put(f2, f1);
            } else {
                sizeMap.put(f2, sizeMap.get(f1) + sizeMap.get(f2));
                fatherMap.put(f1, f2);
            }
            size -= 1;
        }
    }

    public int findFather(int i, int j) {
        int index = i * col + j;
        List<Integer> path = new ArrayList<>();
        while (index != fatherMap.get(index)) {
            path.add(index);
            index = fatherMap.get(index);
        }
        for (int p : path) fatherMap.put(p, index);
        return index;
    }
}

Code 206:翻转链表

描述:反转一个单链表。

Java

public ListNode reverseList(ListNode head) {
    ListNode pre = null;
    while (head != null) {
        ListNode next = head.next; // 保存下一个节点
        head.next = pre; // 当前节点指向前一个节点
        pre = head; // 前一个节点更新为当前节点
        head = next; // 当前节点更新为下一个节点
    }
    return pre;
}

Code 207:课程表

描述:通过拓扑排序判断是否可以完成所有课程的学习。

Java

// 方法1:自定义图结构
public boolean canFinish1(int numCourses, int[][] prerequisites) {
    Graph graph = new Graph();
    Map<Integer, GraphNode> map = new HashMap<>();
    for (int i = 0; i < prerequisites.length; i++) {
        int from = prerequisites[i][1];
        int to = prerequisites[i][0];
        if (!map.containsKey(from)) {
            map.put(from, new GraphNode(from));
            graph.nodes.add(map.get(from));
        }
        if (!map.containsKey(to)) {
            map.put(to, new GraphNode(to));
            graph.nodes.add(map.get(to));
        }
        GraphNode fromNode = map.get(from);
        GraphNode toNode = map.get(to);
        fromNode.nexts.add(toNode);
        toNode.in++;
    }

    Queue<GraphNode> queue = new LinkedList<>();
    for (GraphNode node : map.values()) {
        if (node.in == 0) queue.add(node);
    }

    int count = 0;
    while (!queue.isEmpty()) {
        GraphNode node = queue.poll();
        count++;
        for (Object next : node.nexts) {
            GraphNode to = (GraphNode) next;
            to.in--;
            if (to.in == 0) queue.add(to);
        }
    }
    return count == map.size();
}

// 方法2:邻接表法 (更简洁)
public boolean canFinish2(int numCourses, int[][] prerequisites) {
    if (numCourses == 0 || prerequisites == null || prerequisites.length == 0) {
        return true; 
    }
    Map<Integer, List<Integer>> graph = new HashMap<>();
    Map<Integer, Integer> inMap = new HashMap<>();
    for (int[] p : prerequisites) {
        inMap.put(p[0], inMap.getOrDefault(p[0], 0) + 1);
        inMap.putIfAbsent(p[1], 0);
        graph.computeIfAbsent(p[1], k -> new ArrayList<>()).add(p[0]);
    }
    Queue<Integer> queue = new LinkedList<>();
    for (int key : inMap.keySet()) {
        if (inMap.get(key) == 0) queue.add(key);
    }
    int nodes = 0;
    while (!queue.isEmpty()) {
        nodes++;
        List<Integer> nexts = graph.get(queue.poll());
        if (nexts != null) {
            for (int n : nexts) {
                inMap.put(n, inMap.get(n) - 1);
                if (inMap.get(n) == 0) queue.add(n);
            }
        }
    }
    return nodes == inMap.size();
}

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

描述:在未排序的数组中找到第 k个最大的元素。

Java

// 思路1:小根堆
public int findKthLargest(int[] nums, int k) {
    PriorityQueue<Integer> pq = new PriorityQueue<>();
    for (int i = 0; i < nums.length; i++) {
        pq.add(nums[i]);
        if (pq.size() > k) pq.poll();
    }
    return pq.peek();
}

// 思路2:改写快排 (Quick Select)
public int findKthLargest2(int[] nums, int k) {
    return process(nums, 0, nums.length - 1, nums.length - k); // 找第k大即找第n-k小
}

public int process(int[] nums, int l, int r, int k) {
    if (l >= r) return nums[l];
    int pivot = nums[l + (int)(Math.random()*(r-l+1))];
    int[] range = partition(nums, l, r, pivot);
    if (k >= range[0] && k <= range[1]) return nums[k];
    else if (k < range[0]) return process(nums, l, range[0] - 1, k);
    else return process(nums, range[1] + 1, r, k);
}

public int[] partition(int[] nums, int l, int r, int val) {
    int left = l - 1, right = r + 1, i = l;
    while (i < right) {
        if (nums[i] == val) i++;
        else if (nums[i] < val) swap(nums, i++, ++left);
        else swap(nums, i, --right);
    }
    return new int[]{left + 1, right - 1};
}

// 思路3:BFPRT (确定性划分)
public int findKthLargest3(int[] nums, int k) {
    return bfprt(nums, 0, nums.length - 1, nums.length - k);
}

public int bfprt(int[] nums, int l, int r, int k) {
    if (l == r) return nums[l];
    int pivot = medianOfMedians(nums, l, r);
    int[] range = partition(nums, l, r, pivot);
    if (k >= range[0] && k <= range[1]) return nums[k];
    else if (k < range[0]) return bfprt(nums, l, range[0] - 1, k);
    else return bfprt(nums, range[1] + 1, r, k);
}

public int medianOfMedians(int[] nums, int l, int r) {
    int size = r - l + 1;
    int numMedians = size / 5 + (size % 5 == 0 ? 0 : 1);
    int[] medians = new int[numMedians];
    for (int i = 0; i < numMedians; i++) {
        int subL = l + i * 5;
        int subR = Math.min(r, subL + 4);
        medians[i] = getMedian(nums, subL, subR);
    }
    return bfprt(medians, 0, medians.length - 1, medians.length / 2);
}

public int getMedian(int[] nums, int l, int r) {
    insertionSort(nums, l, r);
    return nums[l + (r - l) / 2];
}

public void insertionSort(int[] nums, int l, int r) {
    for (int i = l + 1; i <= r; i++) {
        int key = nums[i], j = i - 1;
        while (j >= l && nums[j] > key) {
            nums[j + 1] = nums[j--];
        }
        nums[j + 1] = key;
    }
}

public void swap(int[] nums, int i, int j) {
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值